diff --git a/go/cmd/yolo/main.go b/go/cmd/yolo/main.go index cf48eb35..3a68ca13 100644 --- a/go/cmd/yolo/main.go +++ b/go/cmd/yolo/main.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + "fmt" "log" "math/rand" "net/http" @@ -13,6 +14,7 @@ import ( "berty.tech/yolo/v2/go/pkg/bintray" "go.uber.org/zap" "golang.org/x/oauth2" + "moul.io/climan" "moul.io/hcfilters" "moul.io/zapconfig" @@ -26,18 +28,22 @@ import ( circleci "github.com/jszwedko/go-circleci" "github.com/peterbourgon/diskv" ff "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" ) -var ( - verbose bool - logFormat string - dbStorePath string - withPreloading bool -) +type flagsBuilder func(fs *flag.FlagSet) + +type GlobalOptions struct { + verbose bool + logFormat string + dbStorePath string + + server server +} + +var optsGlobal = &GlobalOptions{} func main() { - err := yolo(os.Args) + err := yolo(os.Args[1:]) if err != nil { log.Fatalf("err: %+v", err) os.Exit(1) @@ -49,25 +55,33 @@ func yolo(args []string) error { rootFlagSet := flag.NewFlagSet("yolo", flag.ExitOnError) rand.Seed(time.Now().UnixNano()) rootFlagSet.SetOutput(os.Stderr) - rootFlagSet.BoolVar(&verbose, "v", false, "increase log verbosity") - rootFlagSet.StringVar(&logFormat, "log-format", "console", strings.Join(zapconfig.AvailablePresets, ", ")) - - root := &ffcli.Command{ - ShortUsage: `server [flags] `, - FlagSet: rootFlagSet, - Subcommands: []*ffcli.Command{ - serverCommand(), - dumpObjectsCommand(), - infoCommand(), - treeCommand(), + + commonFlagsBuilder := func(fs *flag.FlagSet) { + fs.BoolVar(&optsGlobal.verbose, "v", false, "increase log verbosity") + fs.StringVar(&optsGlobal.logFormat, "log-format", "console", strings.Join(zapconfig.AvailablePresets, ", ")) + fs.StringVar(&optsGlobal.dbStorePath, "db-path", ":memory:", "DB Store path") + } + + root := &climan.Command{ + ShortUsage: `server [flags] `, + FlagSetBuilder: commonFlagsBuilder, + Subcommands: []*climan.Command{ + serverCommand(commonFlagsBuilder), + dumpObjectsCommand(commonFlagsBuilder), + infoCommand(commonFlagsBuilder), + treeCommand(commonFlagsBuilder), }, - Options: []ff.Option{ff.WithEnvVarNoPrefix()}, + FFOptions: []ff.Option{ff.WithEnvVarNoPrefix()}, Exec: func(_ context.Context, _ []string) error { return flag.ErrHelp }, } - return root.ParseAndRun(context.Background(), os.Args[1:]) + if err := root.Parse(args); err != nil { + return fmt.Errorf("parse error: %w", err) + } + + return root.Run(context.Background()) } func bintrayClientFromArgs(username, token string, logger *zap.Logger) (*bintray.Client, error) { diff --git a/go/cmd/yolo/server.go b/go/cmd/yolo/server.go index 98e60479..c095d99c 100644 --- a/go/cmd/yolo/server.go +++ b/go/cmd/yolo/server.go @@ -10,87 +10,89 @@ import ( "berty.tech/yolo/v2/go/pkg/bintray" "berty.tech/yolo/v2/go/pkg/yolosvc" + "moul.io/climan" "github.com/buildkite/go-buildkite/buildkite" "github.com/jszwedko/go-circleci" "github.com/oklog/run" "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" "github.com/tevino/abool" ) -func serverCommand() *ffcli.Command { - fs := flag.NewFlagSet("server", flag.ExitOnError) - - var ( - devMode bool - withCache bool - maxBuilds int - buildkiteToken string - githubToken string - githubRepos string - bintrayUsername string - bintrayToken string - artifactsCachePath string - circleciToken string - grpcBind string - httpBind string - corsAllowedOrigins string - requestTimeout time.Duration - shutdownTimeout time.Duration - basicAuth string - authSalt string - httpCachePath string - realm string - once bool - iosPrivkeyPath string - iosProvPath string - iosPrivkeyPass string - ) - - fs.BoolVar(&devMode, "dev-mode", false, "enable insecure helpers") - fs.BoolVar(&withCache, "with-cache", false, "enable API caching") - fs.StringVar(&buildkiteToken, "buildkite-token", "", "BuildKite API Token") - fs.StringVar(&bintrayUsername, "bintray-username", "", "Bintray username") - fs.StringVar(&bintrayToken, "bintray-token", "", "Bintray API Token") - fs.StringVar(&circleciToken, "circleci-token", "", "CircleCI API Token") - fs.StringVar(&githubToken, "github-token", "", "GitHub API Token") - fs.StringVar(&githubRepos, "github-repos", "berty/berty", "GitHub repositories to watch") - fs.StringVar(&dbStorePath, "db-path", ":memory:", "DB Store path") - fs.StringVar(&artifactsCachePath, "artifacts-cache-path", "", "Artifacts caching path") - fs.IntVar(&maxBuilds, "max-builds", 100, "maximum builds to fetch from external services (pagination)") - fs.StringVar(&httpBind, "http-bind", ":8000", "HTTP bind address") - fs.StringVar(&grpcBind, "grpc-bind", ":9000", "gRPC bind address") - fs.StringVar(&corsAllowedOrigins, "cors-allowed-origins", "", "CORS allowed origins (*.domain.tld)") - fs.DurationVar(&requestTimeout, "request-timeout", 5*time.Second, "request timeout") - fs.DurationVar(&shutdownTimeout, "shutdown-timeout", 6*time.Second, "server shutdown timeout") - fs.StringVar(&basicAuth, "basic-auth-password", "", "if set, enables basic authentication") - fs.StringVar(&realm, "realm", "Yolo", "authentication Realm") - fs.StringVar(&authSalt, "auth-salt", "", "salt used to generate authentication tokens at the end of the URLs") - fs.StringVar(&httpCachePath, "http-cache-path", "", "if set, will cache http client requests") - fs.BoolVar(&once, "once", false, "just run workers once") - fs.StringVar(&iosPrivkeyPath, "ios-privkey", "", "iOS signing: path to private key or p12 file (PEM or DER format)") - fs.StringVar(&iosProvPath, "ios-prov", "", "iOS signing: path to mobile provisioning profile") - fs.StringVar(&iosPrivkeyPass, "ios-pass", "", "iOS signing: password for private key or p12 file") - - return &ffcli.Command{ +type server struct { + devMode bool + withCache bool + maxBuilds int + buildkiteToken string + githubToken string + githubRepos string + bintrayUsername string + bintrayToken string + artifactsCachePath string + circleciToken string + grpcBind string + httpBind string + corsAllowedOrigins string + requestTimeout time.Duration + shutdownTimeout time.Duration + basicAuth string + authSalt string + httpCachePath string + realm string + once bool + iosPrivkeyPath string + iosProvPath string + iosPrivkeyPass string +} + +func (s server) parse(fs *flag.FlagSet) { + fs.BoolVar(&optsGlobal.server.devMode, "dev-mode", false, "enable insecure helpers") + fs.BoolVar(&optsGlobal.server.withCache, "with-cache", false, "enable API caching") + fs.StringVar(&optsGlobal.server.buildkiteToken, "buildkite-token", "", "BuildKite API Token") + fs.StringVar(&optsGlobal.server.bintrayUsername, "bintray-username", "", "Bintray username") + fs.StringVar(&optsGlobal.server.bintrayToken, "bintray-token", "", "Bintray API Token") + fs.StringVar(&optsGlobal.server.circleciToken, "circleci-token", "", "CircleCI API Token") + fs.StringVar(&optsGlobal.server.githubToken, "github-token", "", "GitHub API Token") + fs.StringVar(&optsGlobal.server.githubRepos, "github-repos", "berty/berty", "GitHub repositories to watch") + fs.StringVar(&optsGlobal.server.artifactsCachePath, "artifacts-cache-path", "", "Artifacts caching path") + fs.IntVar(&optsGlobal.server.maxBuilds, "max-builds", 100, "maximum builds to fetch from external services (pagination)") + fs.StringVar(&optsGlobal.server.httpBind, "http-bind", ":8000", "HTTP bind address") + fs.StringVar(&optsGlobal.server.grpcBind, "grpc-bind", ":9000", "gRPC bind address") + fs.StringVar(&optsGlobal.server.corsAllowedOrigins, "cors-allowed-origins", "", "CORS allowed origins (*.domain.tld)") + fs.DurationVar(&optsGlobal.server.requestTimeout, "request-timeout", 5*time.Second, "request timeout") + fs.DurationVar(&optsGlobal.server.shutdownTimeout, "shutdown-timeout", 6*time.Second, "server shutdown timeout") + fs.StringVar(&optsGlobal.server.basicAuth, "basic-auth-password", "", "if set, enables basic authentication") + fs.StringVar(&optsGlobal.server.realm, "realm", "Yolo", "authentication realm") + fs.StringVar(&optsGlobal.server.authSalt, "auth-salt", "", "salt used to generate authentication tokens at the end of the URLs") + fs.StringVar(&optsGlobal.server.httpCachePath, "http-cache-path", "", "if set, will cache http client requests") + fs.BoolVar(&optsGlobal.server.once, "once", false, "just run workers once") + fs.StringVar(&optsGlobal.server.iosPrivkeyPath, "ios-privkey", "", "iOS signing: path to private key or p12 file (PEM or DER format)") + fs.StringVar(&optsGlobal.server.iosProvPath, "ios-prov", "", "iOS signing: path to mobile provisioning profile") + fs.StringVar(&optsGlobal.server.iosPrivkeyPass, "ios-pass", "", "iOS signing: password for private key or p12 file") +} + +func serverCommand(commonFlagsBuilder flagsBuilder) *climan.Command { + return &climan.Command{ Name: `server`, ShortHelp: `Start a Yolo Server`, - FlagSet: fs, - Options: []ff.Option{ff.WithEnvVarNoPrefix()}, + FFOptions: []ff.Option{ff.WithEnvVarNoPrefix()}, + FlagSetBuilder: func(fs *flag.FlagSet) { + commonFlagsBuilder(fs) + optsGlobal.server.parse(fs) + }, Exec: func(ctx context.Context, _ []string) error { - logger, err := loggerFromArgs(verbose, logFormat) + logger, err := loggerFromArgs(optsGlobal.verbose, optsGlobal.logFormat) if err != nil { return err } ctx, cancel := context.WithCancel(ctx) defer cancel() - roundTripper, rtCloser := roundTripperFromArgs(ctx, httpCachePath, logger) + roundTripper, rtCloser := roundTripperFromArgs(ctx, optsGlobal.server.httpCachePath, logger) defer rtCloser() http.DefaultTransport = roundTripper - db, err := dbFromArgs(dbStorePath, logger) + db, err := dbFromArgs(optsGlobal.dbStorePath, logger) if err != nil { return err } @@ -103,37 +105,37 @@ func serverCommand() *ffcli.Command { // service conns var bkc *buildkite.Client - if buildkiteToken != "" { - bkc, err = buildkiteClientFromArgs(buildkiteToken) + if optsGlobal.server.buildkiteToken != "" { + bkc, err = buildkiteClientFromArgs(optsGlobal.server.buildkiteToken) if err != nil { return err } } var ccc *circleci.Client - if circleciToken != "" { - ccc, err = circleciClientFromArgs(circleciToken) + if optsGlobal.server.circleciToken != "" { + ccc, err = circleciClientFromArgs(optsGlobal.server.circleciToken) if err != nil { return err } } var btc *bintray.Client - if bintrayToken != "" && bintrayUsername != "" { - btc, err = bintrayClientFromArgs(bintrayUsername, bintrayToken, logger) + if optsGlobal.server.bintrayToken != "" && optsGlobal.server.bintrayUsername != "" { + btc, err = bintrayClientFromArgs(optsGlobal.server.bintrayUsername, optsGlobal.server.bintrayToken, logger) if err != nil { return err } } - ghc, err := githubClientFromArgs(githubToken) + ghc, err := githubClientFromArgs(optsGlobal.server.githubToken) if err != nil { return err } - if devMode { + if optsGlobal.server.devMode { logger.Warn("--dev-mode: insecure helpers are enabled") } - if artifactsCachePath != "" { - if err := os.MkdirAll(artifactsCachePath, 0o755); err != nil { + if optsGlobal.server.artifactsCachePath != "" { + if err := os.MkdirAll(optsGlobal.server.artifactsCachePath, 0o755); err != nil { return err } } @@ -145,12 +147,12 @@ func serverCommand() *ffcli.Command { CircleciClient: ccc, BintrayClient: btc, GithubClient: ghc, - AuthSalt: authSalt, - DevMode: devMode, - ArtifactsCachePath: artifactsCachePath, - IOSPrivkeyPath: iosPrivkeyPath, - IOSProvPath: iosProvPath, - IOSPrivkeyPass: iosPrivkeyPass, + AuthSalt: optsGlobal.server.authSalt, + DevMode: optsGlobal.server.devMode, + ArtifactsCachePath: optsGlobal.server.artifactsCachePath, + IOSPrivkeyPath: optsGlobal.server.iosPrivkeyPath, + IOSProvPath: optsGlobal.server.iosProvPath, + IOSPrivkeyPass: optsGlobal.server.iosPrivkeyPass, }) if err != nil { return err @@ -158,39 +160,39 @@ func serverCommand() *ffcli.Command { // service workers if bkc != nil { - opts := yolosvc.BuildkiteWorkerOpts{Logger: logger, MaxBuilds: maxBuilds, ClearCache: cc, Once: once} + opts := yolosvc.BuildkiteWorkerOpts{Logger: logger, MaxBuilds: optsGlobal.server.maxBuilds, ClearCache: cc, Once: optsGlobal.server.once} gr.Add(func() error { return svc.BuildkiteWorker(ctx, opts) }, func(_ error) { cancel() }) } if ccc != nil { - opts := yolosvc.CircleciWorkerOpts{Logger: logger, MaxBuilds: maxBuilds, ClearCache: cc, Once: once} + opts := yolosvc.CircleciWorkerOpts{Logger: logger, MaxBuilds: optsGlobal.server.maxBuilds, ClearCache: cc, Once: optsGlobal.server.once} gr.Add(func() error { return svc.CircleciWorker(ctx, opts) }, func(_ error) { cancel() }) } if btc != nil { - opts := yolosvc.BintrayWorkerOpts{Logger: logger, ClearCache: cc, Once: once} + opts := yolosvc.BintrayWorkerOpts{Logger: logger, ClearCache: cc, Once: optsGlobal.server.once} gr.Add(func() error { return svc.BintrayWorker(ctx, opts) }, func(_ error) { cancel() }) } - if !once { // disable pkgman when running with --once - opts := yolosvc.PkgmanWorkerOpts{Logger: logger, ClearCache: cc, Once: once} + if !optsGlobal.server.once { // disable pkgman when running with --once + opts := yolosvc.PkgmanWorkerOpts{Logger: logger, ClearCache: cc, Once: optsGlobal.server.once} gr.Add(func() error { return svc.PkgmanWorker(ctx, opts) }, func(_ error) { cancel() }) } - if githubToken != "" { - opts := yolosvc.GithubWorkerOpts{Logger: logger, MaxBuilds: maxBuilds, ClearCache: cc, Once: once, ReposFilter: githubRepos, Token: githubToken} + if optsGlobal.server.githubToken != "" { + opts := yolosvc.GithubWorkerOpts{Logger: logger, MaxBuilds: optsGlobal.server.maxBuilds, ClearCache: cc, Once: optsGlobal.server.once, ReposFilter: optsGlobal.server.githubRepos, Token: optsGlobal.server.githubToken} gr.Add(func() error { return svc.GitHubWorker(ctx, opts) }, func(_ error) { cancel() }) } // server/API server, err := yolosvc.NewServer(ctx, svc, yolosvc.ServerOpts{ Logger: logger, - GRPCBind: grpcBind, - HTTPBind: httpBind, - RequestTimeout: requestTimeout, - ShutdownTimeout: shutdownTimeout, - CORSAllowedOrigins: corsAllowedOrigins, - BasicAuth: basicAuth, - Realm: realm, - AuthSalt: authSalt, - DevMode: devMode, - WithCache: withCache, + GRPCBind: optsGlobal.server.grpcBind, + HTTPBind: optsGlobal.server.httpBind, + RequestTimeout: optsGlobal.server.requestTimeout, + ShutdownTimeout: optsGlobal.server.shutdownTimeout, + CORSAllowedOrigins: optsGlobal.server.corsAllowedOrigins, + BasicAuth: optsGlobal.server.basicAuth, + Realm: optsGlobal.server.realm, + AuthSalt: optsGlobal.server.authSalt, + DevMode: optsGlobal.server.devMode, + WithCache: optsGlobal.server.withCache, ClearCache: cc, }) if err != nil { diff --git a/go/cmd/yolo/store.go b/go/cmd/yolo/store.go index 58b27f18..49b3c46f 100644 --- a/go/cmd/yolo/store.go +++ b/go/cmd/yolo/store.go @@ -2,37 +2,27 @@ package main import ( "context" - "flag" "fmt" "berty.tech/yolo/v2/go/pkg/yolopb" "berty.tech/yolo/v2/go/pkg/yolosvc" + "moul.io/climan" "moul.io/godev" "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" ) -func storeFlagSet() *flag.FlagSet { - fs := flag.NewFlagSet("store", flag.ExitOnError) - - fs.StringVar(&dbStorePath, "db-path", ":memory:", "DB Store path") - fs.BoolVar(&withPreloading, "with-preloading", false, "with auto DB preloading") - - return fs -} - -func dumpObjectsCommand() *ffcli.Command { - return &ffcli.Command{ - Name: `dump-objects`, - FlagSet: storeFlagSet(), - Options: []ff.Option{ff.WithEnvVarNoPrefix()}, +func dumpObjectsCommand(commonFlagsBuilder flagsBuilder) *climan.Command { + return &climan.Command{ + Name: `dump-objects`, + FlagSetBuilder: commonFlagsBuilder, + FFOptions: []ff.Option{ff.WithEnvVarNoPrefix()}, Exec: func(_ context.Context, _ []string) error { - logger, err := loggerFromArgs(verbose, logFormat) + logger, err := loggerFromArgs(optsGlobal.verbose, optsGlobal.logFormat) if err != nil { return err } - db, err := dbFromArgs(dbStorePath, logger) + db, err := dbFromArgs(optsGlobal.dbStorePath, logger) if err != nil { return err } @@ -61,17 +51,17 @@ func dumpObjectsCommand() *ffcli.Command { } } -func treeCommand() *ffcli.Command { - return &ffcli.Command{ - Name: `tree`, - FlagSet: storeFlagSet(), - Options: []ff.Option{ff.WithEnvVarNoPrefix()}, +func treeCommand(commonFlagsBuilder flagsBuilder) *climan.Command { + return &climan.Command{ + Name: `tree`, + FlagSetBuilder: commonFlagsBuilder, + FFOptions: []ff.Option{ff.WithEnvVarNoPrefix()}, Exec: func(ctx context.Context, _ []string) error { - logger, err := loggerFromArgs(verbose, logFormat) + logger, err := loggerFromArgs(optsGlobal.verbose, optsGlobal.logFormat) if err != nil { return err } - db, err := dbFromArgs(dbStorePath, logger) + db, err := dbFromArgs(optsGlobal.dbStorePath, logger) if err != nil { return err } @@ -99,17 +89,17 @@ func treeCommand() *ffcli.Command { } } -func infoCommand() *ffcli.Command { - return &ffcli.Command{ - Name: `info`, - FlagSet: storeFlagSet(), - Options: []ff.Option{ff.WithEnvVarNoPrefix()}, +func infoCommand(commonFlagsBuilder flagsBuilder) *climan.Command { + return &climan.Command{ + Name: `info`, + FlagSetBuilder: commonFlagsBuilder, + FFOptions: []ff.Option{ff.WithEnvVarNoPrefix()}, Exec: func(_ context.Context, _ []string) error { - logger, err := loggerFromArgs(verbose, logFormat) + logger, err := loggerFromArgs(optsGlobal.verbose, optsGlobal.logFormat) if err != nil { return err } - db, err := dbFromArgs(dbStorePath, logger) + db, err := dbFromArgs(optsGlobal.dbStorePath, logger) if err != nil { return err }