diff --git a/README.md b/README.md index 7e96e41..29f0d1c 100644 --- a/README.md +++ b/README.md @@ -33,28 +33,31 @@ If you'd like, make sure to include the following comment code tag in your READM build and deploy your sample: ```text -[//]: # ({sst-run-unix}) +[//]: # ({sst-run-bash}) ``` For example: ````text -[//]: # ({sst-run-unix}) +[//]: # ({sst-run-bash}) ``` gcloud builds submit --tag=gcr.io/${GOOGLE_CLOUD_PROJECT}/run-mysql ``` ```` When parsing the README for custom build and deploy commands, the serverless sample tester will include any commands -inside a code fence that is immediately preceded by a line containing `{sst-run-unix}`. You can use Markdown syntax -(e.g. `[//]: # ({sst-run-unix})`) to include this line without making it visible when rendered. +inside a code fence that is immediately preceded by a line containing `{sst-run-bash}`. You can use Markdown syntax +(e.g. `[//]: # ({sst-run-bash})`) to include this line without making it visible when rendered. -The parsed commands will not be run through a shell, meaning that the program will not perform any expansions, -pipelines, redirections or any other functions that shells are responsible for. This also means that popular shell -builtin commands like `cd`, `export`, and `echo` will not be available or may not work as expected. +The parsed commands will be run through the Bash shell, but each command will be run through a separate instance +of the shell, meaning that commands such as `cd` or `export` may not work as expected. All commands will be run in the +sample's root directory. -However, any environment variables referenced in the form of `$var` or `${var}` will expanded. In addition, bash-style -multiline commands (i.e. non-quoted backslashes at the end of a line that indicate a line continuation) will also be -supported. +However, any environment variables referenced in the form of `$var` or `${var}` will expanded. Each command's process +will share the same environment as the process you execute `sst` from. So environment variables you set in your shell +before calling `sst` will be available for the commands you specify in your README. + +In addition, bash-style multiline commands (i.e. non-quoted backslashes at the end of a line that indicate a line +continuation) will also be supported. Do not set the Cloud Run region you'd like to deploy to through the `--region` flag in the `gcloud run` commands. Instead, as mentioned above, do so by setting the `run/region` gcloud property. diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 027a68e..4d2cc05 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -20,6 +20,7 @@ import ( "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/util" "github.com/spf13/cobra" "log" + "os" "os/exec" "path/filepath" ) @@ -52,8 +53,9 @@ func Root(cmd *cobra.Command, args []string) error { log.Println("Getting identity token for gcloud auhtorized account") var identToken string - a := append(util.GcloudCommonFlags, "auth", "print-identity-token") - identToken, err = util.ExecCommand(exec.Command("gcloud", a...), s.Dir) + c := exec.Command("gcloud", "auth", "print-identity-token") + c.Env = append(os.Environ(), util.GcloudCommonEnv...) + identToken, err = util.ExecCommand(c, s.Dir) if err != nil { return fmt.Errorf("[cmd.Root] getting identity token for gcloud auhtorized account: %w", err) diff --git a/internal/gcloud/cloud_run_service.go b/internal/gcloud/cloud_run_service.go index a3c5715..ff81361 100644 --- a/internal/gcloud/cloud_run_service.go +++ b/internal/gcloud/cloud_run_service.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "fmt" "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/util" + "os" "os/exec" "strings" "unicode" @@ -37,8 +38,9 @@ type CloudRunService struct { // Delete calls the external gcloud SDK and deletes the Cloud Run Service associated with the current cloudRunService. func (s CloudRunService) Delete(sampleDir string) error { - a := append(util.GcloudCommonFlags, "run", "services", "delete", s.Name, "--platform=managed") - _, err := util.ExecCommand(exec.Command("gcloud", a...), sampleDir) + c := exec.Command("gcloud", "run", "services", "delete", s.Name, "--platform=managed") + c.Env = append(os.Environ(), util.GcloudCommonEnv...) + _, err := util.ExecCommand(c, sampleDir) if err != nil { return fmt.Errorf("[CloudRunService.delete] deleting Cloud Run Service: %w", err) @@ -54,9 +56,9 @@ func (s *CloudRunService) URL(sampleDir string) (string, error) { return s.url, nil } - a := append(util.GcloudCommonFlags, "run", "--platform=managed", "services", "describe", s.Name, - "--format=value(status.url)") - url, err := util.ExecCommand(exec.Command("gcloud", a...), sampleDir) + c := exec.Command("gcloud", "run", "--platform=managed", "services", "describe", s.Name, "--format=value(status.url)") + c.Env = append(os.Environ(), util.GcloudCommonEnv...) + url, err := util.ExecCommand(c, sampleDir) if err != nil { return "", fmt.Errorf("[CloudRunService.URL] getting Cloud Run Service URL: %w", err) diff --git a/internal/lifecycle/lifecycle.go b/internal/lifecycle/lifecycle.go index bb4f42a..7dc3966 100644 --- a/internal/lifecycle/lifecycle.go +++ b/internal/lifecycle/lifecycle.go @@ -87,14 +87,13 @@ func NewLifecycle(sampleDir, serviceName, gcrURL string) (Lifecycle, error) { // project. It uses `gcloud builds submit` for building the samples container image and submitting it to the container // and `gcloud run deploy` for deploying it to Cloud Run. func buildDefaultLifecycle(serviceName, gcrURL string) Lifecycle { - a0 := append(util.GcloudCommonFlags, "builds", "submit", fmt.Sprintf("--tag=%s", gcrURL)) - a1 := append(util.GcloudCommonFlags, "run", "deploy", serviceName, fmt.Sprintf("--image=%s", gcrURL), - "--platform=managed") + c0 := exec.Command("gcloud", "builds", "submit", fmt.Sprintf("--tag=%s", gcrURL)) + c0.Env = append(os.Environ(), util.GcloudCommonEnv...) - return Lifecycle{ - exec.Command("gcloud", a0...), - exec.Command("gcloud", a1...), - } + c1 := exec.Command("gcloud", "run", "deploy", serviceName, fmt.Sprintf("--image=%s", gcrURL), "--platform=managed") + c1.Env = append(os.Environ(), util.GcloudCommonEnv...) + + return Lifecycle{c0, c1} } // buildDefaultJavaLifecycle builds a build and deploy command lifecycle with reasonable defaults for Java diff --git a/internal/lifecycle/readme.go b/internal/lifecycle/readme.go index df41f16..560eb4d 100644 --- a/internal/lifecycle/readme.go +++ b/internal/lifecycle/readme.go @@ -27,7 +27,7 @@ import ( const ( // The tag that should appear immediately before code blocks in a README to indicate that the enclosed commands // are to be used by this program for building and deploying the sample. - codeTag = "{sst-run-unix}" + codeTag = "{sst-run-bash}" // A non-quoted backslash in bash at the end of a line indicates a line continuation from the current line to the // next line. @@ -49,9 +49,9 @@ var ( // terminal commands inside of a Markdown code block. type codeBlock []string -// toCommands extracts the terminal commands contained within the current codeBlock. It handles the expansion of -// environment variables and line continuations. It also detects Cloud Run service names Google Container Registry -// container image URLs and replaces them with the ones provided. +// toCommands extracts the terminal commands contained within the current codeBlock and creates `exec.Cmd`s for them +// that pass them through the Bash shell. It also detects Cloud Run service names Google Container Registry container +// image URLs and replaces them with the ones provided. func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) { var cmds []*exec.Cmd @@ -79,17 +79,12 @@ func (cb codeBlock) toCommands(serviceName, gcrURL string) ([]*exec.Cmd, error) line = line + l } - line = os.ExpandEnv(line) line = gcrURLRegexp.ReplaceAllString(line, gcrURL) line = replaceServiceName(line, serviceName) - sp := strings.Split(line, " ") - - var cmd *exec.Cmd - if sp[0] == "gcloud" { - a := append(util.GcloudCommonFlags, sp[1:]...) - cmd = exec.Command("gcloud", a...) - } else { - cmd = exec.Command(sp[0], sp[1:]...) + + cmd := exec.Command("bash", "-c", line) + if gcloudCommandRegexp.MatchString(line) { + cmd.Env = append(os.Environ(), util.GcloudCommonEnv...) } cmds = append(cmds, cmd) diff --git a/internal/sample/sample.go b/internal/sample/sample.go index 86cfb55..cd65a6b 100644 --- a/internal/sample/sample.go +++ b/internal/sample/sample.go @@ -19,6 +19,7 @@ import ( "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/gcloud" "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/lifecycle" "github.com/GoogleCloudPlatform/serverless-sample-tester/internal/util" + "os" "os/exec" "strings" "unicode" @@ -52,8 +53,9 @@ func NewSample(dir string) (*Sample, error) { return nil, fmt.Errorf("[sample.NewSample] generating Container Registry container image tag: %w", err) } - a := append(util.GcloudCommonFlags, "config", "get-value", "core/project") - projectID, err := util.ExecCommand(exec.Command("gcloud", a...), dir) + c := exec.Command("gcloud", "config", "get-value", "core/project") + c.Env = append(os.Environ(), util.GcloudCommonEnv...) + projectID, err := util.ExecCommand(c, dir) if err != nil { return nil, fmt.Errorf("[sample.NewSample] getting gcloud default project: %w", err) @@ -91,8 +93,9 @@ func sampleName(dir string) string { // DeleteCloudContainerImage deletes the sample's container image off of the Container Registry. func (s *Sample) DeleteCloudContainerImage() error { - a := append(util.GcloudCommonFlags, "container", "images", "delete", s.cloudContainerImageURL) - _, err := util.ExecCommand(exec.Command("gcloud", a...), s.Dir) + c := exec.Command("container", "images", "delete", s.cloudContainerImageURL) + c.Env = append(os.Environ(), util.GcloudCommonEnv...) + _, err := util.ExecCommand(c, s.Dir) if err != nil { return fmt.Errorf("[Sample.DeleteCloudContainerImage] deleting Container Registry container image: %w", err) diff --git a/internal/util/command.go b/internal/util/command.go index a5d1d1b..31f77cc 100644 --- a/internal/util/command.go +++ b/internal/util/command.go @@ -23,10 +23,10 @@ import ( "strings" ) -// GcloudCommonFlags is a slice of common flags that should be added as arguments to all executions of the external -// gcloud command. -var GcloudCommonFlags = []string{ - "--quiet", +// GcloudCommonEnv is a slice of common environment variables that should be added to the environment of all executions +// of the external gcloud command. +var GcloudCommonEnv = []string{ + "CLOUDSDK_CORE_DISABLE_PROMPTS=TRUE", } // ExecCommand executes an exec.Cmd. If the command exits successfully, its stdout will be returned. If there's an