From 86057aabd45295c7ecd33f2de73c5f91b524defa Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 15:28:20 +0700 Subject: [PATCH 01/21] cleanup --- cmd/cmd.go | 182 +++++------------------------------------------ main.go | 30 ++++---- server/server.go | 4 +- 3 files changed, 34 insertions(+), 182 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 8ad043c..4006a39 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -39,8 +39,9 @@ type TLSCerts struct { type Cmd struct { opts Options - Client client.Interface - Server *server.Server + logger *slog.Logger + cl client.Interface + srv *server.Server } const ( @@ -49,8 +50,11 @@ const ( ) // Main is the CLI entry. -func Main(opts Options) (exitCode int, serverStats server.Stats, err error) { - cmd := &Cmd{opts: opts} +func Main( + logger *slog.Logger, + opts Options, +) (exitCode int, serverStats server.Stats, err error) { + cmd := &Cmd{opts: opts, logger: logger} if err = cmd.instantiateClient(); err != nil { return 1, server.Stats{}, fault.Wrap(err, fmsg.With("failed to create remote cache client"), @@ -79,7 +83,7 @@ func Main(opts Options) (exitCode int, serverStats server.Stats, err error) { } } - return exitCode, cmd.Server.GetStatistics(), nil + return exitCode, cmd.srv.GetStatistics(), nil } // instantiateClient creates the client connection and runs CheckCapabilities @@ -100,20 +104,19 @@ func (cmd *Cmd) instantiateClient() error { ctx, cancel := context.WithTimeout(context.Background(), cmd.opts.RemoteCacheTimeout) defer cancel() - slog.Debug("checking server capabilities") + cmd.logger.Debug("checking server capabilities") if err = cl.CheckCapabilities(ctx); err != nil { return err } - cmd.Client = cl - + cmd.cl = cl return nil } // startServer creates the server, starts HTTP listener in a goroutine, and uses HTTP GET // with retries to check that the server is up. func (cmd *Cmd) startServer() error { - srv := server.NewServer(cmd.Client, server.Options{}) // the token is not used + srv := server.NewServer(cmd.logger, cmd.cl, server.Options{}) // the token is not used addr := cmd.opts.BindAddr httpSrv := &http.Server{ @@ -122,12 +125,12 @@ func (cmd *Cmd) startServer() error { } go func() { - slog.Debug("starting HTTP server", slog.String("addr", addr)) + cmd.logger.Debug("starting HTTP server", slog.String("addr", addr)) if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { // we can't directly signal this error from the goroutine, but in case this happens, // the accessibility check will fail. - slog.Error(err.Error()) + cmd.logger.Error(err.Error()) } }() @@ -137,166 +140,13 @@ func (cmd *Cmd) startServer() error { return err } else { _ = resp.Body.Close() - slog.Debug("HTTP server is accessible", slog.Int("status", resp.StatusCode)) + cmd.logger.Debug("HTTP server is accessible", slog.Int("status", resp.StatusCode)) } - cmd.Server = srv + cmd.srv = srv return nil } -/* -const ( - TLSClientCertFlag = "tls_client_certificate" - TLSClientKeyFlag = "tls_client_key" - AddrFlag = "addr" - HostFlag = "host" - VerboseFlag = "verbose" - SummaryFlag = "summary" - TimeoutFlag = "timeout" - - defaultCacheTimeout = 30 * time.Second -) - -// CreateApp instantiates cli.App. -func CreateApp() *cli.App { - app := &cli.App{ - Name: "tbc", - Usage: "TurboRepo <--> Bazel Remote Cache Proxy", - Flags: []cli.Flag{ - &cli.StringFlag{Name: HostFlag, Usage: "Remote cache server `HOST`", Required: true, Aliases: []string{"H"}}, - &cli.StringFlag{Name: AddrFlag, Usage: "Address to bind to", Value: ":8080"}, - &cli.StringFlag{Name: TLSClientCertFlag, Usage: "TLS certificate `FILE`", TakesFile: true}, - &cli.StringFlag{Name: TLSClientKeyFlag, Usage: "TLS key `FILE`", TakesFile: true}, - &cli.BoolFlag{Name: VerboseFlag, Aliases: []string{"v"}, Usage: "Be more verbose"}, - &cli.BoolFlag{Name: SummaryFlag, Aliases: []string{"s"}, Usage: "Print server summary when the wrapped command exits"}, - &cli.DurationFlag{Name: TimeoutFlag, Usage: "Cache ops timeout", Value: defaultCacheTimeout}, - }, - Before: func(c *cli.Context) error { - if c.Bool(VerboseFlag) { - slog.SetLogLoggerLevel(slog.LevelDebug) - } - return nil - }, - Action: runProxy, - HideHelpCommand: true, - ArgsUsage: "command ", - Description: `Spin up a Turborepo-compatible remote cache server that forwards requests to a Bazel-compatible remote cache server -and execute the provided command. - -Examples: - -# Check the server with curl (by default, the server binds to 0.0.0.0:8080) -tbc --host bazel-cache-host:port curl http://localhost:8080/v8/artifacts/status - -# Run 'turbo build' -env TURBO_REMOTE_CACHE_SIGNATURE_KEY=super_secret \ - TURBO_API=http://localhost:8080 \ - TURBO_TOKEN=any \ # this is not actually used, but required to be set by turbo - TURBO_TEAM=any \ - tbc --host bazel-cache-host:port \ - --summary \ - pnpm turbo build -`, - } - return app -} - -// the main command body -func runProxy(c *cli.Context) error { - args := c.Args() - if !args.Present() { - return cli.Exit("command is not provided", 1) - } - - cl, err := newClient(c) - if err != nil { - return cli.Exit(fmt.Errorf("failed to create remote cache client: %w", err), 1) - } - - srv, err := newServer(c, cl) - if err != nil { - return cli.Exit(fmt.Errorf("failed to start proxy server: %w", err), 1) - } - - // Start the command in the background - cmd := exec.Command(c.Args().First(), c.Args().Tail()...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - err = cmd.Start() - if err != nil { - return cli.Exit(fmt.Errorf("error starting command: %w", err), 1) - } - - exitCode := 0 - - if err = cmd.Wait(); err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - exitCode = exitError.ExitCode() - } else { - return cli.Exit(fmt.Errorf("error running command: %w", err), 1) - } - } - - if c.Bool(SummaryFlag) { - slog.Info("server stats", srv.GetStatistics().SlogArgs()...) - } - - os.Exit(exitCode) - return nil -} - -// newClient creates the client connection and runs CheckCapabilities -func newClient(c *cli.Context) (client.Interface, error) { - cc, err := client.DialGrpc(c.String(HostFlag), c.String(TLSClientCertFlag), c.String(TLSClientKeyFlag)) - if err != nil { - return nil, err - } - cl := client.NewClient(cc) - - ctx, cancel := context.WithTimeout(context.Background(), c.Duration(TimeoutFlag)) - defer cancel() - - slog.Debug("checking server capabilities") - if err = cl.CheckCapabilities(ctx); err != nil { - return nil, err - } - return cl, nil -} - -// newServer creates the server, starts HTTP listener in a goroutine, and uses HTTP GET -// with retries to check that the server is up. -func newServer(c *cli.Context, cl client.Interface) (*server.Server, error) { - srv := server.NewServer(cl, server.Options{}) // the token is not used - - addr := c.String(AddrFlag) - httpSrv := &http.Server{ - Addr: addr, - Handler: srv.CreateHandler(), - } - - go func() { - slog.Debug("starting HTTP server", slog.String("addr", addr)) - - if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - // if listen has failed, we need to terminate the process - slog.Error(err.Error()) - os.Exit(1) - } - }() - - hc := retryablehttp.NewClient() - hc.Logger = nil - if resp, err := hc.Get(serverCheckURL(addr)); err != nil { - return nil, err - } else { - _ = resp.Body.Close() - slog.Debug("HTTP server is accessible", slog.Int("status", resp.StatusCode)) - } - return srv, nil -} -*/ - func serverCheckURL(addr string) string { if strings.HasPrefix(addr, ":") { addr = "localhost" + addr diff --git a/main.go b/main.go index 214962e..a99144e 100644 --- a/main.go +++ b/main.go @@ -10,10 +10,24 @@ import ( "github.com/urfave/cli/v2" ) +const ( + TLSClientCertFlag = "tls_client_certificate" + TLSClientKeyFlag = "tls_client_key" + AddrFlag = "addr" + HostFlag = "host" + VerboseFlag = "verbose" + SummaryFlag = "summary" + TimeoutFlag = "timeout" + + defaultCacheTimeout = 30 * time.Second +) + func main() { var ( opts cmd.Options certFile, keyFile string + + logger = slog.Default() ) app := &cli.App{ Name: "tbc", @@ -49,12 +63,12 @@ func main() { return nil }, Action: func(c *cli.Context) error { - exitCode, stats, err := cmd.Main(opts) + exitCode, stats, err := cmd.Main(logger, opts) if err != nil { return cli.Exit(err, exitCode) } if c.Bool(SummaryFlag) { - slog.Info("server stats", stats.SlogArgs()...) + logger.Info("server stats", stats.SlogArgs()...) } os.Exit(exitCode) return nil @@ -84,15 +98,3 @@ env TURBO_REMOTE_CACHE_SIGNATURE_KEY=super_secret \ slog.Error(err.Error()) } } - -const ( - TLSClientCertFlag = "tls_client_certificate" - TLSClientKeyFlag = "tls_client_key" - AddrFlag = "addr" - HostFlag = "host" - VerboseFlag = "verbose" - SummaryFlag = "summary" - TimeoutFlag = "timeout" - - defaultCacheTimeout = 30 * time.Second -) diff --git a/server/server.go b/server/server.go index 897825c..7656614 100644 --- a/server/server.go +++ b/server/server.go @@ -31,11 +31,11 @@ type Server struct { stats Stats } -func NewServer(client client.Interface, opts Options) *Server { +func NewServer(logger *slog.Logger, client client.Interface, opts Options) *Server { return &Server{ opts: opts, cl: client, - logger: slog.With(), + logger: logger, } } From 188302e5f9a8e2d7f8a5ad79a24caaed67ba429d Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 15:59:15 +0700 Subject: [PATCH 02/21] auto-env + try end2end --- .github/workflows/go.yml | 41 +++++++++++++++++++++++++++++++++++++++- cmd/cmd.go | 36 +++++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 59c9ae6..9fa1833 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,13 +28,52 @@ jobs: with: go-version-file: go.mod + - name: Build CLI + run: mkdir -p bin && go build -o bin/tbc . + + - name: Upload CLI + uses: actions/upload-artifact@v4 + with: + name: tbc + path: bin/tbc + - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: version: v1.59.1 - + - name: Run tests run: go test ./... - name: Run integration tests run: go test ./client -remote-cache-host localhost:9092 + + end2end: + runs-on: ubuntu-latest + needs: build + + services: + bazel-remote: + image: buchgr/bazel-remote-cache:v1.3.4 + ports: + - 9092:9092 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github + end2end + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: end2end/.tool-versions + cache: 'pnpm' + cache-dependency-path: end2end/monorepo/subdir/pnpm-lock.yaml + + - name: Grab tbc CLI + uses: actions/download-artifact@v4 + with: + name: tbc diff --git a/cmd/cmd.go b/cmd/cmd.go index 4006a39..ee1afe5 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -25,12 +25,19 @@ type Options struct { // Command's arguments. Args []string + // The remote cache host RemoteCacheHost string // Timeout used for remote cache operations RemoteCacheTimeout time.Duration - RemoteCacheTLS *TLSCerts // nil means insecure connection + // Certs for TLS (nil means insecure) + RemoteCacheTLS *TLSCerts + + // The address to bind to BindAddr string + + // If true, the command will set TURBO_API, TURBO_TOKEN, and TURBO_TEAM variables (unless they are already set) + AutoEnv bool } type TLSCerts struct { @@ -70,6 +77,7 @@ func Main( c := exec.Command(cmd.opts.Command, cmd.opts.Args...) c.Stdout = os.Stdout c.Stderr = os.Stderr + c.Env = cmd.commandEnvironment() if err = c.Start(); err != nil { return 1, server.Stats{}, fault.Wrap(err, fmsg.With("error starting command")) @@ -148,8 +156,32 @@ func (cmd *Cmd) startServer() error { } func serverCheckURL(addr string) string { + return serverBaseURL(addr) + "/v8/artifacts/status" +} + +func serverBaseURL(addr string) string { if strings.HasPrefix(addr, ":") { addr = "localhost" + addr } - return fmt.Sprintf("http://%s/v8/artifacts/status", addr) + return fmt.Sprintf("http://%s", addr) +} + +func (cmd *Cmd) commandEnvironment() []string { + if !cmd.opts.AutoEnv { + return nil + } + var ( + env = os.Environ() + ok bool + ) + if _, ok = os.LookupEnv("TURBO_API"); !ok { + env = append(env, fmt.Sprintf("TURBO_API=%s", serverBaseURL(cmd.opts.BindAddr))) + } + if _, ok = os.LookupEnv("TURBO_TOKEN"); !ok { + env = append(env, fmt.Sprintf("TURBO_TOKEN=ignore")) + } + if _, ok = os.LookupEnv("TURBO_TEAM"); !ok { + env = append(env, fmt.Sprintf("TURBO_TEAM=ignore")) + } + return env } From f1788a57df6b46d4aa687fe7fe3055aa293db093 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:01:02 +0700 Subject: [PATCH 03/21] fixup --- server/server_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/server_test.go b/server/server_test.go index f09d8a5..87118e0 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "io" + "log/slog" "net/http" "net/http/httptest" "os" @@ -16,7 +17,7 @@ import ( ) func TestBadToken(t *testing.T) { - s := NewServer(client.NewInMemoryClient(), Options{Token: "t0k3n"}) + s := NewServer(slog.Default(), client.NewInMemoryClient(), Options{Token: "t0k3n"}) r := s.CreateHandler() req, err := http.NewRequest("POST", "/v8/artifacts/events", nil) @@ -267,7 +268,7 @@ func createHandler(token string) (http.Handler, *Server) { } func createHandlerForClient(token string, cl client.Interface) (http.Handler, *Server) { - s := NewServer(cl, Options{Token: token}) + s := NewServer(slog.Default(), cl, Options{Token: token}) r := s.CreateHandler() return r, s } From 4e83f76c5b53401b703aab6c86266a2a30ab196a Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:02:14 +0700 Subject: [PATCH 04/21] fix --- cmd/cmd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index ee1afe5..9220dd3 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -178,10 +178,10 @@ func (cmd *Cmd) commandEnvironment() []string { env = append(env, fmt.Sprintf("TURBO_API=%s", serverBaseURL(cmd.opts.BindAddr))) } if _, ok = os.LookupEnv("TURBO_TOKEN"); !ok { - env = append(env, fmt.Sprintf("TURBO_TOKEN=ignore")) + env = append(env, "TURBO_TOKEN=ignore") } if _, ok = os.LookupEnv("TURBO_TEAM"); !ok { - env = append(env, fmt.Sprintf("TURBO_TEAM=ignore")) + env = append(env, "TURBO_TEAM=ignore") } return env } From 4e9e45923b7677f7588aa648fa9d2f2bef4f74df Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:06:22 +0700 Subject: [PATCH 05/21] install pnpm --- .github/workflows/go.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9fa1833..4df92d2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -66,6 +66,11 @@ jobs: .github end2end + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Set up Node uses: actions/setup-node@v4 with: From ee86812b42e5ed2cbf096cd9646e0813ef4e3ce5 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:08:05 +0700 Subject: [PATCH 06/21] iterate --- .github/workflows/go.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4df92d2..2d7fc57 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -70,13 +70,14 @@ jobs: uses: pnpm/action-setup@v4 with: run_install: false + package_json_file: end2end/monorepo/package.json - name: Set up Node uses: actions/setup-node@v4 with: node-version-file: end2end/.tool-versions cache: 'pnpm' - cache-dependency-path: end2end/monorepo/subdir/pnpm-lock.yaml + cache-dependency-path: end2end/monorepo/pnpm-lock.yaml - name: Grab tbc CLI uses: actions/download-artifact@v4 From 62d9d21cef44ab1772e5d578d19e3a7345ead31f Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:12:17 +0700 Subject: [PATCH 07/21] iterate --- .github/workflows/go.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2d7fc57..db7b979 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -79,6 +79,9 @@ jobs: cache: 'pnpm' cache-dependency-path: end2end/monorepo/pnpm-lock.yaml + - name: Install dependencies + run: pnpm install + - name: Grab tbc CLI uses: actions/download-artifact@v4 with: From 95a0bc440a6a70d1e5558748a3d387c6de27d46f Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:14:55 +0700 Subject: [PATCH 08/21] iterate --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index db7b979..d67870d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -80,7 +80,7 @@ jobs: cache-dependency-path: end2end/monorepo/pnpm-lock.yaml - name: Install dependencies - run: pnpm install + run: pnpm -C end2end/monorepo install - name: Grab tbc CLI uses: actions/download-artifact@v4 From d747ac04bf7ce1bd4703b048a254f6f5bb6338bf Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:27:04 +0700 Subject: [PATCH 09/21] iterate --- .github/workflows/go.yml | 13 ++++++++ main.go | 72 ++++++++++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d67870d..85dcd97 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -48,6 +48,8 @@ jobs: - name: Run integration tests run: go test ./client -remote-cache-host localhost:9092 + ############################################################################ + end2end: runs-on: ubuntu-latest needs: build @@ -58,6 +60,14 @@ jobs: ports: - 9092:9092 + env: + # This is required by turbo with "remoteCache": { "signature": true } + # tbc --auto-env does not set this variable. + TURBO_REMOTE_CACHE_SIGNATURE_KEY: super_secret + TBC_AUTO_ENV: true + TBC_SUMMARY: true + TBC_HOST: localhost:9092 + steps: - name: Checkout code uses: actions/checkout@v4 @@ -86,3 +96,6 @@ jobs: uses: actions/download-artifact@v4 with: name: tbc + + - name: Build monorepo for the first time + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build diff --git a/main.go b/main.go index a99144e..7eff7ea 100644 --- a/main.go +++ b/main.go @@ -11,13 +11,8 @@ import ( ) const ( - TLSClientCertFlag = "tls_client_certificate" - TLSClientKeyFlag = "tls_client_key" - AddrFlag = "addr" - HostFlag = "host" - VerboseFlag = "verbose" - SummaryFlag = "summary" - TimeoutFlag = "timeout" + VerboseFlag = "verbose" + SummaryFlag = "summary" defaultCacheTimeout = 30 * time.Second ) @@ -33,13 +28,62 @@ func main() { Name: "tbc", Usage: "TurboRepo <--> Bazel Remote Cache Proxy", Flags: []cli.Flag{ - &cli.StringFlag{Name: HostFlag, Usage: "Remote cache server `HOST`", Required: true, Aliases: []string{"H"}, Destination: &opts.RemoteCacheHost}, - &cli.StringFlag{Name: AddrFlag, Usage: "Address to bind to", Value: ":8080", Destination: &opts.BindAddr}, - &cli.StringFlag{Name: TLSClientCertFlag, Usage: "TLS certificate `FILE`", TakesFile: true, Destination: &certFile}, - &cli.StringFlag{Name: TLSClientKeyFlag, Usage: "TLS key `FILE`", TakesFile: true, Destination: &keyFile}, - &cli.BoolFlag{Name: VerboseFlag, Aliases: []string{"v"}, Usage: "Be more verbose"}, - &cli.BoolFlag{Name: SummaryFlag, Aliases: []string{"s"}, Usage: "Print server summary when the wrapped command exits"}, - &cli.DurationFlag{Name: TimeoutFlag, Usage: "Cache ops timeout", Value: defaultCacheTimeout, Destination: &opts.RemoteCacheTimeout}, + &cli.StringFlag{ + Name: "host", + EnvVars: []string{"TBC_HOST"}, + Usage: "Remote cache server `HOST`", + Required: true, + Aliases: []string{"H"}, + Destination: &opts.RemoteCacheHost, + }, + &cli.StringFlag{ + Name: "addr", + EnvVars: []string{"TBC_ADDR"}, + Usage: "Address to bind to", + Value: ":8080", + Destination: &opts.BindAddr, + }, + &cli.StringFlag{ + Name: "tls_client_certificate", + EnvVars: []string{"TBC_CLIENT_CERT"}, + Usage: "TLS certificate `FILE`", + TakesFile: true, + Destination: &certFile, + }, + &cli.StringFlag{ + Name: "tls_client_key", + EnvVars: []string{"TBC_CLIENT_KEY"}, + Usage: "TLS key `FILE`", + TakesFile: true, + Destination: &keyFile, + }, + &cli.DurationFlag{ + Name: "timeout", + EnvVars: []string{"TBC_CLIENT_TIMEOUT"}, + Usage: "Cache ops timeout", + Value: defaultCacheTimeout, + Destination: &opts.RemoteCacheTimeout, + }, + &cli.BoolFlag{ + Name: "auto-env", + EnvVars: []string{"TBC_AUTO_ENV"}, + Usage: "Set up environment for turbo", + Value: true, + Destination: &opts.AutoEnv, + }, + + &cli.BoolFlag{ + Name: VerboseFlag, + EnvVars: []string{"TBC_VERBOSE"}, + Aliases: []string{"v"}, + Usage: "Be more verbose", + }, + &cli.BoolFlag{ + Name: SummaryFlag, + EnvVars: []string{"TBC_SUMMARY"}, + Aliases: []string{"s"}, + Usage: "Print server summary when the wrapped command exits", + }, }, Before: func(c *cli.Context) error { if c.Bool(VerboseFlag) { From a8f02ea62a49a928ca0f8d7e7f43406c2f31c275 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:29:32 +0700 Subject: [PATCH 10/21] iterate --- .github/workflows/go.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 85dcd97..81e82a0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -97,5 +97,8 @@ jobs: with: name: tbc + - name: Restore exec bit for tbc + run: chmod +x $GITHUB_WORKSPACE/tbc + - name: Build monorepo for the first time run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build From 510f1e8e01dc2931625a4a5ae1ba89b3a97ca982 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:33:52 +0700 Subject: [PATCH 11/21] facepalm --- main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.go b/main.go index 7eff7ea..55bcfba 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,9 @@ func main() { opts.RemoteCacheTLS = &cmd.TLSCerts{CertPEM: certPEMBlock, KeyPEM: keyPEMBlock} } + opts.Command = c.Args().First() + opts.Args = c.Args().Tail() + return nil }, Action: func(c *cli.Context) error { From 3357e86f528f51ac2f473ba8616d08ae3fc6c19e Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:41:40 +0700 Subject: [PATCH 12/21] iterate --- .github/workflows/go.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 81e82a0..8eef245 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -101,4 +101,10 @@ jobs: run: chmod +x $GITHUB_WORKSPACE/tbc - name: Build monorepo for the first time - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee first_build.log + + - name: Check that cache worked correctly + run: grep "server stats uploads=2 downloads_not_found=2" first_build.log + + - name: Build monorepo for the second time to check that artifacts are cached + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee second_build.log From 21c17719d30f77921d0c6b57ca23650081c5561a Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:44:05 +0700 Subject: [PATCH 13/21] iterate --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8eef245..093aa9a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -101,10 +101,10 @@ jobs: run: chmod +x $GITHUB_WORKSPACE/tbc - name: Build monorepo for the first time - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee first_build.log + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/first_build.log - name: Check that cache worked correctly run: grep "server stats uploads=2 downloads_not_found=2" first_build.log - name: Build monorepo for the second time to check that artifacts are cached - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee second_build.log + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log From e368f7f5b4b7d67993846ffa41291d2496c9616f Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:48:09 +0700 Subject: [PATCH 14/21] final iteration? --- .github/workflows/go.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 093aa9a..d4b431a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -92,7 +92,7 @@ jobs: - name: Install dependencies run: pnpm -C end2end/monorepo install - - name: Grab tbc CLI + - name: Grab tbc uses: actions/download-artifact@v4 with: name: tbc @@ -108,3 +108,6 @@ jobs: - name: Build monorepo for the second time to check that artifacts are cached run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log + + - name: Check that cache worked correctly for the second tim + run: grep "FULL TURBO" first_build.log && grep "server stats cache_requests=0" first_build.log From b50e338f92efe61c0c3f5c597565284c36c89cbb Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:50:09 +0700 Subject: [PATCH 15/21] fix --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d4b431a..ef2dbfd 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -109,5 +109,5 @@ jobs: - name: Build monorepo for the second time to check that artifacts are cached run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - - name: Check that cache worked correctly for the second tim - run: grep "FULL TURBO" first_build.log && grep "server stats cache_requests=0" first_build.log + - name: Check that cache worked correctly for the second time + run: grep "FULL TURBO" first_build.log && grep "server stats cache_requests=0" second_build.log From 89faf5545c5ba21112de80d8d273a3139a6fdc40 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 16:50:23 +0700 Subject: [PATCH 16/21] fix --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ef2dbfd..2f407d7 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -110,4 +110,4 @@ jobs: run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - name: Check that cache worked correctly for the second time - run: grep "FULL TURBO" first_build.log && grep "server stats cache_requests=0" second_build.log + run: grep "FULL TURBO" second_build.log && grep "server stats cache_requests=0" second_build.log From 4e2f985cd9f336cd7251ea0423c93e00483d8f89 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 17:18:42 +0700 Subject: [PATCH 17/21] iterate --- .github/workflows/go.yml | 15 +++++++++-- cmd/cmd.go | 56 ++++++++++++++++++++++++---------------- main.go | 18 ++++++++++--- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2f407d7..17eee75 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -64,7 +64,6 @@ jobs: # This is required by turbo with "remoteCache": { "signature": true } # tbc --auto-env does not set this variable. TURBO_REMOTE_CACHE_SIGNATURE_KEY: super_secret - TBC_AUTO_ENV: true TBC_SUMMARY: true TBC_HOST: localhost:9092 @@ -106,8 +105,20 @@ jobs: - name: Check that cache worked correctly run: grep "server stats uploads=2 downloads_not_found=2" first_build.log - - name: Build monorepo for the second time to check that artifacts are cached + - name: Wipe local cache + run: pnpm -C end2end/monorepo wipe + + - name: Build monorepo for the second time to check that artifacts were taken from the remote cache run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - name: Check that cache worked correctly for the second time run: grep "FULL TURBO" second_build.log && grep "server stats cache_requests=0" second_build.log + + - name: Check the --ignore-failures mode (invalid cache host) + env: + TBC_HOST: example.org:9093 # invalid + TBC_IGNORE_FAILURES: true + run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/third_build.log + + - name: Check the log + run: grep "FULL TURBO" third_build.log && grep "cache proxy failed, just running the command" third_build.log \ No newline at end of file diff --git a/cmd/cmd.go b/cmd/cmd.go index 9220dd3..6ecd85f 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -13,7 +13,6 @@ import ( "github.com/Southclaws/fault" "github.com/Southclaws/fault/fmsg" - "github.com/Southclaws/fault/ftag" "github.com/be9/tbc/client" "github.com/be9/tbc/server" "github.com/hashicorp/go-retryablehttp" @@ -35,9 +34,10 @@ type Options struct { // The address to bind to BindAddr string - // If true, the command will set TURBO_API, TURBO_TOKEN, and TURBO_TEAM variables (unless they are already set) AutoEnv bool + // If remote cache connection or proxy server start fails, just run the command. + IgnoreFailures bool } type TLSCerts struct { @@ -51,47 +51,59 @@ type Cmd struct { srv *server.Server } -const ( - ClientFailure ftag.Kind = "REMOTE_CACHE_CLIENT_FAILURE" - ServerFailure ftag.Kind = "PROXY_SERVER_FAILURE" -) - // Main is the CLI entry. func Main( logger *slog.Logger, opts Options, -) (exitCode int, serverStats server.Stats, err error) { +) (exitCode int, serverStats server.Stats, errorsIgnored bool, err error) { cmd := &Cmd{opts: opts, logger: logger} - if err = cmd.instantiateClient(); err != nil { - return 1, server.Stats{}, fault.Wrap(err, - fmsg.With("failed to create remote cache client"), - ftag.With(ClientFailure)) - } - if err = cmd.startServer(); err != nil { - return 1, server.Stats{}, fault.Wrap(err, - fmsg.With("failed to start proxy server"), - ftag.With(ServerFailure)) + + clientServerErr := func() error { + var err error + if err = cmd.instantiateClient(); err != nil { + return fault.Wrap(err, fmsg.With("failed to create remote cache client")) + } + if err = cmd.startServer(); err != nil { + return fault.Wrap(err, fmsg.With("failed to start proxy server")) + } + return nil + }() + + if clientServerErr != nil { + if cmd.opts.IgnoreFailures { + logger.Error("cache proxy failed, just running the command", + slog.String("err", clientServerErr.Error())) + + errorsIgnored = true + } else { + return 1, serverStats, false, clientServerErr + } } // Start the command in the background c := exec.Command(cmd.opts.Command, cmd.opts.Args...) c.Stdout = os.Stdout c.Stderr = os.Stderr - c.Env = cmd.commandEnvironment() + + if !errorsIgnored { + c.Env = cmd.commandEnvironment() + } if err = c.Start(); err != nil { - return 1, server.Stats{}, fault.Wrap(err, fmsg.With("error starting command")) + return 1, server.Stats{}, false, fault.Wrap(err, fmsg.With("error starting command")) } if err = c.Wait(); err != nil { if exitError, ok := err.(*exec.ExitError); ok { exitCode = exitError.ExitCode() } else { - return 1, server.Stats{}, fault.Wrap(err, fmsg.With("error running command")) + return 1, server.Stats{}, false, fault.Wrap(err, fmsg.With("error running command")) } } - - return exitCode, cmd.srv.GetStatistics(), nil + if !errorsIgnored { + serverStats = cmd.srv.GetStatistics() + } + return } // instantiateClient creates the client connection and runs CheckCapabilities diff --git a/main.go b/main.go index 55bcfba..55bd116 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,12 @@ func main() { Value: true, Destination: &opts.AutoEnv, }, + &cli.BoolFlag{ + Name: "ignore-failures", + EnvVars: []string{"TBC_IGNORE_ERRORS"}, + Usage: "Just run turbo without the proxy if proxy fails", + Destination: &opts.IgnoreFailures, + }, &cli.BoolFlag{ Name: VerboseFlag, @@ -110,11 +116,11 @@ func main() { return nil }, Action: func(c *cli.Context) error { - exitCode, stats, err := cmd.Main(logger, opts) + exitCode, stats, errorsIgnored, err := cmd.Main(logger, opts) if err != nil { return cli.Exit(err, exitCode) } - if c.Bool(SummaryFlag) { + if !errorsIgnored && c.Bool(SummaryFlag) { logger.Info("server stats", stats.SlogArgs()...) } os.Exit(exitCode) @@ -130,13 +136,19 @@ Examples: # Check the server with curl (by default, the server binds to 0.0.0.0:8080) tbc --host bazel-cache-host:port curl http://localhost:8080/v8/artifacts/status -# Run 'turbo build' +# Run 'turbo build' with auto-set variables; if cache doesn't work, run the command ignoring the cache: +env TURBO_REMOTE_CACHE_SIGNATURE_KEY=super_secret \ + tbc --host bazel-cache-host:port --auto-env --ignore-failures --summary \ + pnpm turbo build + +# Run 'turbo build' with manually set vars: env TURBO_REMOTE_CACHE_SIGNATURE_KEY=super_secret \ TURBO_API=http://localhost:8080 \ TURBO_TOKEN=any \ # this is not actually used, but required to be set by turbo TURBO_TEAM=any \ tbc --host bazel-cache-host:port \ --summary \ + --auto-env=false \ pnpm turbo build `, } From 9be213e4c9856b9bb1ecd24f7d1e34fec2458b20 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 17:20:55 +0700 Subject: [PATCH 18/21] iterate --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 17eee75..dc4cbe0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -112,7 +112,7 @@ jobs: run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - name: Check that cache worked correctly for the second time - run: grep "FULL TURBO" second_build.log && grep "server stats cache_requests=0" second_build.log + run: grep "FULL TURBO" second_build.log && grep "server stats downloads=2" second_build.log - name: Check the --ignore-failures mode (invalid cache host) env: From 16a855181b14be600611353d93da6720e710ed15 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 17:24:16 +0700 Subject: [PATCH 19/21] fix --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 55bd116..3366802 100644 --- a/main.go +++ b/main.go @@ -73,7 +73,7 @@ func main() { }, &cli.BoolFlag{ Name: "ignore-failures", - EnvVars: []string{"TBC_IGNORE_ERRORS"}, + EnvVars: []string{"TBC_IGNORE_FAILURES"}, Usage: "Just run turbo without the proxy if proxy fails", Destination: &opts.IgnoreFailures, }, From 73a032e070f088dc2c240578a27f18f5d974959c Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 17:34:38 +0700 Subject: [PATCH 20/21] rename/fix --- .github/workflows/{go.yml => ci.yml} | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) rename .github/workflows/{go.yml => ci.yml} (79%) diff --git a/.github/workflows/go.yml b/.github/workflows/ci.yml similarity index 79% rename from .github/workflows/go.yml rename to .github/workflows/ci.yml index dc4cbe0..28fa957 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/ci.yml @@ -100,25 +100,35 @@ jobs: run: chmod +x $GITHUB_WORKSPACE/tbc - name: Build monorepo for the first time - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/first_build.log + run: | + cd end2end/monorepo + $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/first_build.log - name: Check that cache worked correctly - run: grep "server stats uploads=2 downloads_not_found=2" first_build.log + run: grep -q "server stats uploads=2 downloads_not_found=2" first_build.log - name: Wipe local cache run: pnpm -C end2end/monorepo wipe - name: Build monorepo for the second time to check that artifacts were taken from the remote cache - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log + run: | + cd end2end/monorepo + $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - name: Check that cache worked correctly for the second time - run: grep "FULL TURBO" second_build.log && grep "server stats downloads=2" second_build.log + run: | + grep -q "FULL TURBO" second_build.log && + grep -q "server stats downloads=2" second_build.log - name: Check the --ignore-failures mode (invalid cache host) env: TBC_HOST: example.org:9093 # invalid TBC_IGNORE_FAILURES: true - run: cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/third_build.log + run: | + cd end2end/monorepo + $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/third_build.log - name: Check the log - run: grep "FULL TURBO" third_build.log && grep "cache proxy failed, just running the command" third_build.log \ No newline at end of file + run: | + grep -q "FULL TURBO" third_build.log && + grep -q "cache proxy failed, just running the command" third_build.log From 50f9d5e1e71230cb452369089ea807103bfebb81 Mon Sep 17 00:00:00 2001 From: Oleg Dashevskii Date: Mon, 8 Jul 2024 17:39:01 +0700 Subject: [PATCH 21/21] fix --- .github/workflows/ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28fa957..42804e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,7 @@ jobs: # This is required by turbo with "remoteCache": { "signature": true } # tbc --auto-env does not set this variable. TURBO_REMOTE_CACHE_SIGNATURE_KEY: super_secret + TURBO_TELEMETRY_DISABLED: 1 TBC_SUMMARY: true TBC_HOST: localhost:9092 @@ -100,8 +101,8 @@ jobs: run: chmod +x $GITHUB_WORKSPACE/tbc - name: Build monorepo for the first time - run: | - cd end2end/monorepo + run: > + cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/first_build.log - name: Check that cache worked correctly @@ -111,12 +112,12 @@ jobs: run: pnpm -C end2end/monorepo wipe - name: Build monorepo for the second time to check that artifacts were taken from the remote cache - run: | - cd end2end/monorepo + run: > + cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/second_build.log - name: Check that cache worked correctly for the second time - run: | + run: > grep -q "FULL TURBO" second_build.log && grep -q "server stats downloads=2" second_build.log @@ -124,11 +125,11 @@ jobs: env: TBC_HOST: example.org:9093 # invalid TBC_IGNORE_FAILURES: true - run: | - cd end2end/monorepo + run: > + cd end2end/monorepo && $GITHUB_WORKSPACE/tbc pnpm build 2>&1 | tee $GITHUB_WORKSPACE/third_build.log - name: Check the log - run: | + run: > grep -q "FULL TURBO" third_build.log && grep -q "cache proxy failed, just running the command" third_build.log