diff --git a/go/cmd/remotetool/BUILD.bazel b/go/cmd/remotetool/BUILD.bazel index c8696f908..db9d98509 100644 --- a/go/cmd/remotetool/BUILD.bazel +++ b/go/cmd/remotetool/BUILD.bazel @@ -7,8 +7,8 @@ go_library( visibility = ["//visibility:private"], deps = [ "//go/pkg/flags", - "//go/pkg/outerr", "//go/pkg/tool", + "//go/cmd/remotetool/embeddedtool", "@com_github_golang_glog//:go_default_library", ], ) diff --git a/go/cmd/remotetool/embeddedtool/BUILD.bazel b/go/cmd/remotetool/embeddedtool/BUILD.bazel new file mode 100644 index 000000000..6e7559fe3 --- /dev/null +++ b/go/cmd/remotetool/embeddedtool/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "embeddedtool", + srcs = ["embeddedtool.go"], + importpath = "github.com/bazelbuild/remote-apis-sdks/go/cmd/remotetool/embeddedtool", + visibility = ["//visibility:public"], + deps = [ + "//go/pkg/outerr", + "//go/pkg/tool", + "@com_github_golang_glog//:glog", + ], +) diff --git a/go/cmd/remotetool/embeddedtool/embeddedtool.go b/go/cmd/remotetool/embeddedtool/embeddedtool.go new file mode 100644 index 000000000..3518ac1f2 --- /dev/null +++ b/go/cmd/remotetool/embeddedtool/embeddedtool.go @@ -0,0 +1,154 @@ +// Package embeddedtool is a library that can be embedded into a go binary +// such that the binary now supports the various operations the remotetool +// supports (e.g., show_action, upload_blob, etc). +// A canonical usage of this library is the outer remotetool binary. +package embeddedtool + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + + "github.com/bazelbuild/remote-apis-sdks/go/pkg/outerr" + "github.com/bazelbuild/remote-apis-sdks/go/pkg/tool" + + log "github.com/golang/glog" +) + +var ( + digest = flag.String("digest", "", "Digest in format.") + pathPrefix = flag.String("path", "", "Path to which outputs should be downloaded to.") + overwrite = flag.Bool("overwrite", false, "Overwrite the output path if it already exist.") + actionRoot = flag.String("action_root", "", "For execute_action: the root of the action spec, containing ac.textproto (Action proto), cmd.textproto (Command proto), and input/ (root of the input tree).") + execAttempts = flag.Int("exec_attempts", 10, "For check_determinism: the number of times to remotely execute the action and check for mismatches.") + jsonOutput = flag.String("json", "", "Path to output operation result as JSON. Currently supported for \"upload_dir\", and includes various upload metadata (see UploadStats).") + _ = flag.String("input_root", "", "Deprecated. Use action root instead.") +) + +// OpType denotes the type of operation to perform. +type OpType string + +const ( + downloadActionResult OpType = "download_action_result" + showAction OpType = "show_action" + downloadAction OpType = "download_action" + downloadBlob OpType = "download_blob" + downloadDir OpType = "download_dir" + executeAction OpType = "execute_action" + checkDeterminism OpType = "check_determinism" + uploadBlob OpType = "upload_blob" + uploadBlobV2 OpType = "upload_blob_v2" + uploadDir OpType = "upload_dir" +) + +// SupportedOps denote the list of operations supported by this remotetool. +var SupportedOps = []OpType{ + downloadActionResult, + showAction, + downloadAction, + downloadBlob, + downloadDir, + executeAction, + checkDeterminism, + uploadBlob, + uploadDir, +} + +// RemoteToolOperations maps each supported operation to a function that performs +// the operation. +var RemoteToolOperations = map[OpType]func(ctx context.Context, c *tool.Client){ + downloadActionResult: func(ctx context.Context, c *tool.Client) { + if err := c.DownloadActionResult(ctx, getDigestFlag(), getPathFlag()); err != nil { + log.Exitf("error downloading action result for digest %v: %v", digest, err) + } + }, + downloadBlob: func(ctx context.Context, c *tool.Client) { + res, err := c.DownloadBlob(ctx, getDigestFlag(), getPathFlag()) + if err != nil { + log.Exitf("error downloading blob for digest %v: %v", getDigestFlag(), err) + } + os.Stdout.Write([]byte(res)) + }, + downloadDir: func(ctx context.Context, c *tool.Client) { + if err := c.DownloadDirectory(ctx, getDigestFlag(), getPathFlag()); err != nil { + log.Exitf("error downloading directory for digest %v: %v", getDigestFlag(), err) + } + }, + showAction: func(ctx context.Context, c *tool.Client) { + res, err := c.ShowAction(ctx, getDigestFlag()) + if err != nil { + log.Exitf("error fetching action %v: %v", digest, err) + } + os.Stdout.Write([]byte(res)) + }, + downloadAction: func(ctx context.Context, c *tool.Client) { + err := c.DownloadAction(ctx, getDigestFlag(), getPathFlag(), *overwrite) + if err != nil { + log.Exitf("error fetching action %v: %v", getDigestFlag(), err) + } + fmt.Printf("Action downloaded to %v\n", getPathFlag()) + }, + executeAction: func(ctx context.Context, c *tool.Client) { + if _, err := c.ExecuteAction(ctx, *digest, *actionRoot, getPathFlag(), outerr.SystemOutErr); err != nil { + log.Exitf("error executing action: %v", err) + } + }, + checkDeterminism: func(ctx context.Context, c *tool.Client) { + if err := c.CheckDeterminism(ctx, *digest, *actionRoot, *execAttempts); err != nil { + log.Exitf("error checking determinism: %v", err) + } + }, + uploadBlob: func(ctx context.Context, c *tool.Client) { + if err := c.UploadBlob(ctx, getPathFlag()); err != nil { + log.Exitf("error uploading blob for digest %v: %v", getDigestFlag(), err) + } + }, + uploadBlobV2: func(ctx context.Context, c *tool.Client) { + if err := c.UploadBlobV2(ctx, getPathFlag()); err != nil { + log.Exitf("error uploading blob for digest %v: %v", getDigestFlag(), err) + } + }, + uploadDir: func(ctx context.Context, c *tool.Client) { + us, err := c.UploadDirectory(ctx, getPathFlag()) + if *jsonOutput != "" { + js, _ := json.MarshalIndent(us, "", " ") + if *jsonOutput == "-" { + fmt.Printf("%s\n", js) + } else { + log.Infof("Outputting JSON results to %s", *jsonOutput) + if err := os.WriteFile(*jsonOutput, []byte(js), 0o666); err != nil { + log.Exitf("Error writing JSON output to file: %v", err) + } + } + } + if err != nil { + log.Exitf("error uploading directory for path %s: %v", getPathFlag(), err) + } + }, +} + +// ValidateFlags validates the command line flags associated with this embedded remote tool. +func ValidateFlags() { + if !flag.Parsed() { + flag.Parse() + } + if *execAttempts <= 0 { + log.Exitf("--exec_attempts must be >= 1.") + } +} + +func getDigestFlag() string { + if *digest == "" { + log.Exitf("--digest must be specified.") + } + return *digest +} + +func getPathFlag() string { + if *pathPrefix == "" { + log.Exitf("--path must be specified.") + } + return *pathPrefix +} diff --git a/go/cmd/remotetool/main.go b/go/cmd/remotetool/main.go index 4ecdb3f0f..9019ea476 100644 --- a/go/cmd/remotetool/main.go +++ b/go/cmd/remotetool/main.go @@ -21,56 +21,20 @@ package main import ( "context" - "encoding/json" "flag" "fmt" "os" "path" - "github.com/bazelbuild/remote-apis-sdks/go/pkg/outerr" + "github.com/bazelbuild/remote-apis-sdks/go/cmd/remotetool/embeddedtool" "github.com/bazelbuild/remote-apis-sdks/go/pkg/tool" rflags "github.com/bazelbuild/remote-apis-sdks/go/pkg/flags" log "github.com/golang/glog" ) -// OpType denotes the type of operation to perform. -type OpType string - -const ( - downloadActionResult OpType = "download_action_result" - showAction OpType = "show_action" - downloadAction OpType = "download_action" - downloadBlob OpType = "download_blob" - downloadDir OpType = "download_dir" - executeAction OpType = "execute_action" - checkDeterminism OpType = "check_determinism" - uploadBlob OpType = "upload_blob" - uploadBlobV2 OpType = "upload_blob_v2" - uploadDir OpType = "upload_dir" -) - -var supportedOps = []OpType{ - downloadActionResult, - showAction, - downloadAction, - downloadBlob, - downloadDir, - executeAction, - checkDeterminism, - uploadBlob, - uploadDir, -} - var ( - operation = flag.String("operation", "", fmt.Sprintf("Specifies the operation to perform. Supported values: %v", supportedOps)) - digest = flag.String("digest", "", "Digest in format.") - pathPrefix = flag.String("path", "", "Path to which outputs should be downloaded to.") - overwrite = flag.Bool("overwrite", false, "Overwrite the output path if it already exist.") - actionRoot = flag.String("action_root", "", "For execute_action: the root of the action spec, containing ac.textproto (Action proto), cmd.textproto (Command proto), and input/ (root of the input tree).") - execAttempts = flag.Int("exec_attempts", 10, "For check_determinism: the number of times to remotely execute the action and check for mismatches.") - jsonOutput = flag.String("json", "", "Path to output operation result as JSON. Currently supported for \"upload_dir\", and includes various upload metadata (see UploadStats).") - _ = flag.String("input_root", "", "Deprecated. Use action root instead.") + operation = flag.String("operation", "", fmt.Sprintf("Specifies the operation to perform. Supported values: %v", embeddedtool.SupportedOps)) ) func main() { @@ -82,9 +46,7 @@ func main() { if *operation == "" { log.Exitf("--operation must be specified.") } - if *execAttempts <= 0 { - log.Exitf("--exec_attempts must be >= 1.") - } + embeddedtool.ValidateFlags() ctx := context.Background() grpcClient, err := rflags.NewClientFromFlags(ctx) @@ -94,90 +56,9 @@ func main() { defer grpcClient.Close() c := &tool.Client{GrpcClient: grpcClient} - switch OpType(*operation) { - case downloadActionResult: - if err := c.DownloadActionResult(ctx, getDigestFlag(), getPathFlag()); err != nil { - log.Exitf("error downloading action result for digest %v: %v", getDigestFlag(), err) - } - - case downloadBlob: - res, err := c.DownloadBlob(ctx, getDigestFlag(), getPathFlag()) - if err != nil { - log.Exitf("error downloading blob for digest %v: %v", getDigestFlag(), err) - } - os.Stdout.Write([]byte(res)) - - case downloadDir: - if err := c.DownloadDirectory(ctx, getDigestFlag(), getPathFlag()); err != nil { - log.Exitf("error downloading directory for digest %v: %v", getDigestFlag(), err) - } - - case showAction: - res, err := c.ShowAction(ctx, getDigestFlag()) - if err != nil { - log.Exitf("error fetching action %v: %v", getDigestFlag(), err) - } - os.Stdout.Write([]byte(res)) - - case downloadAction: - err := c.DownloadAction(ctx, getDigestFlag(), getPathFlag(), *overwrite) - if err != nil { - log.Exitf("error fetching action %v: %v", getDigestFlag(), err) - } - fmt.Printf("Action downloaded to %v\n", getPathFlag()) - - case executeAction: - if _, err := c.ExecuteAction(ctx, *digest, *actionRoot, getPathFlag(), outerr.SystemOutErr); err != nil { - log.Exitf("error executing action: %v", err) - } - - case checkDeterminism: - if err := c.CheckDeterminism(ctx, *digest, *actionRoot, *execAttempts); err != nil { - log.Exitf("error checking determinism: %v", err) - } - - case uploadBlob: - if err := c.UploadBlob(ctx, getPathFlag()); err != nil { - log.Exitf("error uploading blob for digest %v: %v", getDigestFlag(), err) - } - - case uploadBlobV2: - if err := c.UploadBlobV2(ctx, getPathFlag()); err != nil { - log.Exitf("error uploading blob for digest %v: %v", getDigestFlag(), err) - } - - case uploadDir: - us, err := c.UploadDirectory(ctx, getPathFlag()) - if *jsonOutput != "" { - js, _ := json.MarshalIndent(us, "", " ") - if *jsonOutput == "-" { - fmt.Printf("%s\n", js) - } else { - log.Infof("Outputting JSON results to %s", *jsonOutput) - if err := os.WriteFile(*jsonOutput, []byte(js), 0o666); err != nil { - log.Exitf("Error writing JSON output to file: %v", err) - } - } - } - if err != nil { - log.Exitf("error uploading directory for path %s: %v", getPathFlag(), err) - } - - default: - log.Exitf("unsupported operation %v. Supported operations:\n%v", *operation, supportedOps) - } -} - -func getDigestFlag() string { - if *digest == "" { - log.Exitf("--digest must be specified.") - } - return *digest -} - -func getPathFlag() string { - if *pathPrefix == "" { - log.Exitf("--path must be specified.") + fn, ok := embeddedtool.RemoteToolOperations[embeddedtool.OpType(*operation)] + if !ok { + log.Exitf("unsupported operation %v. Supported operations:\n%v", *operation, embeddedtool.SupportedOps) } - return *pathPrefix + fn(ctx, c) }