github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/cmd/rexec/main.go (about)

     1  // Main package for the rexec binary.
     2  //
     3  // This tool executes a command remotely, downloading the command outputs and propagating its
     4  // stdout and stderr.
     5  //
     6  // Example usage:
     7  //
     8  //	rexec --alsologtostderr --v 1 \
     9  //	  --service remotebuildexecution.googleapis.com:443 \
    10  //	  --instance $INSTANCE \
    11  //	  --credential_file $CRED_FILE \
    12  //	  --exec_root $HOME/example \
    13  //	  --platform container-image $CONTAINER \
    14  //	  --inputs a/hello,a/goodbye
    15  //	  --working_directory foo/bar
    16  //	  --output_files foo/bar/out
    17  //	  -- /bin/bash -c 'cat hello goodbye > out'
    18  package main
    19  
    20  import (
    21  	"context"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"path"
    26  
    27  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/command"
    28  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/filemetadata"
    29  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/moreflag"
    30  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/outerr"
    31  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/rexec"
    32  
    33  	rflags "github.com/bazelbuild/remote-apis-sdks/go/pkg/flags"
    34  	log "github.com/golang/glog"
    35  )
    36  
    37  func initFlags(cmd *command.Command, opt *command.ExecutionOptions) {
    38  	flag.StringVar(&cmd.Identifiers.CommandID, "command_id", "", "An identifier for the command for debugging.")
    39  	flag.StringVar(&cmd.Identifiers.InvocationID, "invocation_id", "", "An identifier for a group of commands for debugging.")
    40  	flag.StringVar(&cmd.Identifiers.ToolName, "tool_name", "", "The name of the tool to associate with executed commands.")
    41  	flag.StringVar(&cmd.ExecRoot, "exec_root", "", "The exec root of the command. The path from which all inputs and outputs are defined relatively.")
    42  	flag.StringVar(&cmd.WorkingDir, "working_directory", "", "The working directory, relative to the exec root, for the command to run in. It must be a directory which exists in the input tree. If it is left empty, then the action is run in the exec root.")
    43  	flag.Var((*moreflag.StringListValue)(&cmd.InputSpec.Inputs), "inputs", "Comma-separated command input paths, relative to exec root.")
    44  	flag.Var((*moreflag.StringListValue)(&cmd.OutputFiles), "output_files", "Comma-separated command output file paths, relative to exec root.")
    45  	flag.Var((*moreflag.StringListValue)(&cmd.OutputDirs), "output_directories", "Comma-separated command output directory paths, relative to exec root.")
    46  	flag.DurationVar(&cmd.Timeout, "exec_timeout", 0, "Timeout for the command. Value of 0 means no timeout.")
    47  	flag.Var((*moreflag.StringMapValue)(&cmd.Platform), "platform", "Comma-separated key value pairs in the form key=value. This is used to identify remote platform settings like the docker image to use to run the command.")
    48  	flag.Var((*moreflag.StringMapValue)(&cmd.InputSpec.EnvironmentVariables), "environment_variables", "Environment variables to pass through to remote execution, as comma-separated key value pairs in the form key=value.")
    49  	flag.BoolVar(&opt.AcceptCached, "accept_cached", true, "Boolean indicating whether to accept remote cache hits.")
    50  	flag.BoolVar(&opt.DoNotCache, "do_not_cache", false, "Boolean indicating whether to skip caching the command result remotely.")
    51  	flag.BoolVar(&opt.DownloadOutputs, "download_outputs", true, "Boolean indicating whether to download outputs after the command is executed.")
    52  	flag.BoolVar(&opt.DownloadOutErr, "download_outerr", true, "Boolean indicating whether to download stdout and stderr after the command is executed.")
    53  }
    54  
    55  func main() {
    56  	cmd := &command.Command{InputSpec: &command.InputSpec{}, Identifiers: &command.Identifiers{}}
    57  	opt := &command.ExecutionOptions{}
    58  	initFlags(cmd, opt)
    59  	flag.Usage = func() {
    60  		fmt.Fprintf(flag.CommandLine.Output(), "Usage: %v [-flags] -- command arguments ...\n", path.Base(os.Args[0]))
    61  		flag.PrintDefaults()
    62  	}
    63  	flag.Parse()
    64  	cmd.Args = flag.Args()
    65  	if err := cmd.Validate(); err != nil {
    66  		flag.Usage()
    67  		log.Exitf("Invalid command provided: %v", err)
    68  	}
    69  
    70  	ctx := context.Background()
    71  	grpcClient, err := rflags.NewClientFromFlags(ctx)
    72  	if err != nil {
    73  		log.Exitf("error connecting to remote execution client: %v", err)
    74  	}
    75  	defer grpcClient.Close()
    76  	c := &rexec.Client{
    77  		FileMetadataCache: filemetadata.NewNoopCache(),
    78  		GrpcClient:        grpcClient,
    79  	}
    80  	res, _ := c.Run(ctx, cmd, opt, outerr.SystemOutErr)
    81  	switch res.Status {
    82  	case command.NonZeroExitResultStatus:
    83  		fmt.Fprintf(os.Stderr, "Remote action FAILED with exit code %d.\n", res.ExitCode)
    84  	case command.TimeoutResultStatus:
    85  		fmt.Fprintf(os.Stderr, "Remote action TIMED OUT after %0f seconds.\n", cmd.Timeout.Seconds())
    86  	case command.InterruptedResultStatus:
    87  		fmt.Fprintf(os.Stderr, "Remote execution was interrupted.\n")
    88  	case command.RemoteErrorResultStatus:
    89  		fmt.Fprintf(os.Stderr, "Remote execution error: %v.\n", res.Err)
    90  	case command.LocalErrorResultStatus:
    91  		fmt.Fprintf(os.Stderr, "Local error: %v.\n", res.Err)
    92  	}
    93  }