From e43a4931305a4e1975a289b3b4daabc82d0df22c Mon Sep 17 00:00:00 2001 From: Integralist Date: Mon, 14 Aug 2023 17:42:19 +0100 Subject: [PATCH 01/11] feat(compute/init): support post_init --- pkg/commands/compute/build.go | 6 +- pkg/commands/compute/init.go | 64 ++++++++++++++++++++++ pkg/commands/compute/language_toolchain.go | 36 +++--------- pkg/errors/errors.go | 13 ++++- pkg/exec/exec.go | 35 ++++++++++++ pkg/manifest/file.go | 1 + 6 files changed, 122 insertions(+), 33 deletions(-) diff --git a/pkg/commands/compute/build.go b/pkg/commands/compute/build.go index d7b219871..5f708fcc0 100644 --- a/pkg/commands/compute/build.go +++ b/pkg/commands/compute/build.go @@ -24,9 +24,9 @@ import ( // IgnoreFilePath is the filepath name of the Fastly ignore file. const IgnoreFilePath = ".fastlyignore" -// CustomPostBuildScriptMessage is the message displayed to a user when there is a -// custom post build script. -const CustomPostBuildScriptMessage = "This project has a custom post build script defined in the fastly.toml manifest" +// CustomPostScriptMessage is the message displayed to a user when there is +// either a post_init or post_build script defined. +const CustomPostScriptMessage = "This project has a custom post %s script defined in the fastly.toml manifest" // Flags represents the flags defined for the command. type Flags struct { diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index b2a77ab5e..752c77e5b 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -20,6 +20,7 @@ import ( "github.com/fastly/cli/pkg/cmd" "github.com/fastly/cli/pkg/config" fsterr "github.com/fastly/cli/pkg/errors" + fstexec "github.com/fastly/cli/pkg/exec" "github.com/fastly/cli/pkg/file" "github.com/fastly/cli/pkg/filesystem" "github.com/fastly/cli/pkg/global" @@ -225,6 +226,50 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { return fmt.Errorf("error initializing package: %w", err) } + var md manifest.Data + err = md.File.Read(manifest.Filename) + if err != nil { + return fmt.Errorf("failed to read manifest after initialisation: %w", err) + } + + postInit := md.File.Scripts.PostInit + if postInit != "" { + if !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive { + msg := fmt.Sprintf(CustomPostScriptMessage, "init") + err := promptForPostInitContinue(msg, postInit, out, in) + if err != nil { + if errors.Is(err, fsterr.ErrPostInitStopped) { + displayOutput(mf.Name, dst, language.Name, out) + return nil + } + return err + } + } + + err = spinner.Start() + if err != nil { + return err + } + msg := "Running [scripts.post_init]..." + spinner.Message(msg) + + s := Shell{} + cmd, args := s.Build(postInit) + noTimeout := 0 // zero indicates no timeout + err := fstexec.Command( + cmd, args, msg, out, spinner, c.Globals.Flags.Verbose, noTimeout, c.Globals.ErrLog, + ) + if err != nil { + return err + } + + spinner.StopMessage(msg) + err = spinner.Stop() + if err != nil { + return err + } + } + displayOutput(mf.Name, dst, language.Name, out) return nil } @@ -1141,6 +1186,25 @@ func initializeLanguage(spinner text.Spinner, language *Language, languages []*L return language, nil } +// promptForPostInitContinue ensures the user is happy to continue with running +// the define post_init script in the fastly.toml manifest file. +func promptForPostInitContinue(msg, script string, out io.Writer, in io.Reader) error { + text.Info(out, "%s:\n", msg) + text.Break(out) + text.Indent(out, 4, "%s", script) + + label := "\nAre you sure you want to continue with the post init step? [y/N] " + answer, err := text.AskYesNo(out, label, in) + if err != nil { + return err + } + if !answer { + return fsterr.ErrPostInitStopped + } + text.Break(out) + return nil +} + // displayOutput of package information and useful links. func displayOutput(name, dst, language string, out io.Writer) { text.Break(out) diff --git a/pkg/commands/compute/language_toolchain.go b/pkg/commands/compute/language_toolchain.go index 0aa9d5e9f..19d11d287 100644 --- a/pkg/commands/compute/language_toolchain.go +++ b/pkg/commands/compute/language_toolchain.go @@ -5,7 +5,6 @@ import ( "io" "os" "strings" - "time" fsterr "github.com/fastly/cli/pkg/errors" fstexec "github.com/fastly/cli/pkg/exec" @@ -148,7 +147,8 @@ func (bt BuildToolchain) Build() error { if bt.postBuild != "" { if !bt.autoYes && !bt.nonInteractive { - err := bt.promptForBuildContinue(CustomPostBuildScriptMessage, bt.postBuild, bt.out, bt.in) + msg := fmt.Sprintf(CustomPostScriptMessage, "build") + err := bt.promptForPostBuildContinue(msg, bt.postBuild, bt.out, bt.in) if err != nil { return err } @@ -194,32 +194,14 @@ func (bt BuildToolchain) handleError(err error) error { // This causes the spinner message to be displayed twice with different status. // By passing in the spinner and message we can short-circuit the spinner. func (bt BuildToolchain) execCommand(cmd string, args []string, spinMessage string) error { - s := fstexec.Streaming{ - Command: cmd, - Args: args, - Env: os.Environ(), - Output: bt.out, - Spinner: bt.spinner, - SpinnerMessage: spinMessage, - Verbose: bt.verbose, - } - if bt.verbose { - s.ForceOutput = true - } - if bt.timeout > 0 { - s.Timeout = time.Duration(bt.timeout) * time.Second - } - if err := s.Exec(); err != nil { - bt.errlog.Add(err) - return err - } - return nil + return fstexec.Command( + cmd, args, spinMessage, bt.out, bt.spinner, bt.verbose, bt.timeout, bt.errlog, + ) } -// promptForBuildContinue ensures the user is happy to continue with the build -// when there is either a custom build or post build in the fastly.toml -// manifest file. -func (bt BuildToolchain) promptForBuildContinue(msg, script string, out io.Writer, in io.Reader) error { +// promptForPostBuildContinue ensures the user is happy to continue with the build +// when there is a post_build in the fastly.toml manifest file. +func (bt BuildToolchain) promptForPostBuildContinue(msg, script string, out io.Writer, in io.Reader) error { text.Info(out, "%s:\n", msg) text.Break(out) text.Indent(out, 4, "%s", script) @@ -230,7 +212,7 @@ func (bt BuildToolchain) promptForBuildContinue(msg, script string, out io.Write return err } if !answer { - return fsterr.ErrBuildStopped + return fsterr.ErrPostBuildStopped } text.Break(out) return nil diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index ff560e60f..cd54bd9a1 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -93,11 +93,18 @@ var ErrInvalidArchive = RemediationError{ Remediation: "Ensure the archive contains all required package files (such as a 'fastly.toml' manifest, and a 'src' folder etc).", } -// ErrBuildStopped means the user stopped the build because they were unhappy +// ErrPostInitStopped means the user stopped the init process because they were +// unhappy with the custom post_init defined in the fastly.toml manifest file. +var ErrPostInitStopped = RemediationError{ + Inner: fmt.Errorf("init process stopped by user"), + Remediation: "Check the [scripts.post_init] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.", +} + +// ErrPostBuildStopped means the user stopped the build because they were unhappy // with the custom build defined in the fastly.toml manifest file. -var ErrBuildStopped = RemediationError{ +var ErrPostBuildStopped = RemediationError{ Inner: fmt.Errorf("build process stopped by user"), - Remediation: "Check the [scripts.build] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.", + Remediation: "Check the [scripts.post_build] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.", } // ErrInvalidVerboseJSONCombo means the user provided both a --verbose and diff --git a/pkg/exec/exec.go b/pkg/exec/exec.go index b8b047641..1733e3712 100644 --- a/pkg/exec/exec.go +++ b/pkg/exec/exec.go @@ -10,6 +10,7 @@ import ( "syscall" "time" + fsterr "github.com/fastly/cli/pkg/errors" "github.com/fastly/cli/pkg/text" "github.com/fastly/cli/pkg/threadsafe" ) @@ -155,3 +156,37 @@ func (s *Streaming) Signal(sig os.Signal) error { } return nil } + +// Command is an abstraction over a Streaming type. It is used by both the +// `compute init` and `compute build` commands to run post init/build scripts. +func Command( + cmd string, + args []string, + spinMessage string, + out io.Writer, + spinner text.Spinner, + verbose bool, + timeout int, + errLog fsterr.LogInterface, +) error { + s := Streaming{ + Command: cmd, + Args: args, + Env: os.Environ(), + Output: out, + Spinner: spinner, + SpinnerMessage: spinMessage, + Verbose: verbose, + } + if verbose { + s.ForceOutput = true + } + if timeout > 0 { + s.Timeout = time.Duration(timeout) * time.Second + } + if err := s.Exec(); err != nil { + errLog.Add(err) + return err + } + return nil +} diff --git a/pkg/manifest/file.go b/pkg/manifest/file.go index 19b3c688c..b705c8662 100644 --- a/pkg/manifest/file.go +++ b/pkg/manifest/file.go @@ -190,4 +190,5 @@ func appendSpecRef(w io.Writer) error { type Scripts struct { Build string `toml:"build,omitempty"` PostBuild string `toml:"post_build,omitempty"` + PostInit string `toml:"post_init,omitempty"` } From 4b02bfa82b5c8b8e5a03140218c91c874067984d Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:17:21 +0100 Subject: [PATCH 02/11] refactor(compute/init): remove wording that implies danger --- pkg/commands/compute/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index 752c77e5b..ce5247c50 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -1193,7 +1193,7 @@ func promptForPostInitContinue(msg, script string, out io.Writer, in io.Reader) text.Break(out) text.Indent(out, 4, "%s", script) - label := "\nAre you sure you want to continue with the post init step? [y/N] " + label := "\nContinue with the post init step? [y/N] " answer, err := text.AskYesNo(out, label, in) if err != nil { return err From c11d477097b2f174a2c276be4b26e93889130ace Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:20:11 +0100 Subject: [PATCH 03/11] fix(compute/init): use thread safe buffer in tests --- pkg/commands/compute/build_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/commands/compute/build_test.go b/pkg/commands/compute/build_test.go index 872e788d2..07d43dc17 100644 --- a/pkg/commands/compute/build_test.go +++ b/pkg/commands/compute/build_test.go @@ -185,7 +185,7 @@ func TestBuildRust(t *testing.T) { } defer os.Chdir(pwd) - var stdout bytes.Buffer + var stdout threadsafe.Buffer opts := testutil.NewRunOpts(testcase.args, &stdout) opts.ConfigFile = testcase.applicationConfig err = app.Run(opts) @@ -345,7 +345,7 @@ func TestBuildGo(t *testing.T) { } defer os.Chdir(pwd) - var stdout bytes.Buffer + var stdout threadsafe.Buffer opts := testutil.NewRunOpts(testcase.args, &stdout) opts.ConfigFile = testcase.applicationConfig err = app.Run(opts) @@ -651,7 +651,7 @@ func TestBuildAssemblyScript(t *testing.T) { } } - var stdout bytes.Buffer + var stdout threadsafe.Buffer opts := testutil.NewRunOpts(testcase.args, &stdout) err = app.Run(opts) t.Log(stdout.String()) From 60c335ee80c9cebe6d5287b17a6b856684b298c0 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:21:56 +0100 Subject: [PATCH 04/11] fix(compute): add underscore to post messaging to align with toml field --- pkg/commands/compute/build.go | 2 +- pkg/commands/compute/init.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/compute/build.go b/pkg/commands/compute/build.go index 5f708fcc0..5f89b1f27 100644 --- a/pkg/commands/compute/build.go +++ b/pkg/commands/compute/build.go @@ -26,7 +26,7 @@ const IgnoreFilePath = ".fastlyignore" // CustomPostScriptMessage is the message displayed to a user when there is // either a post_init or post_build script defined. -const CustomPostScriptMessage = "This project has a custom post %s script defined in the fastly.toml manifest" +const CustomPostScriptMessage = "This project has a custom post_%s script defined in the fastly.toml manifest" // Flags represents the flags defined for the command. type Flags struct { diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index ce5247c50..cec84a7f5 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -1193,7 +1193,7 @@ func promptForPostInitContinue(msg, script string, out io.Writer, in io.Reader) text.Break(out) text.Indent(out, 4, "%s", script) - label := "\nContinue with the post init step? [y/N] " + label := "\nContinue with the post_init step? [y/N] " answer, err := text.AskYesNo(out, label, in) if err != nil { return err From 33beb5b655c0741e03373e0641ce8e3d4062304a Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:24:34 +0100 Subject: [PATCH 05/11] ci: add verbose flag to test suite job --- .github/workflows/pr_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 7c6ece293..3311a7c49 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -153,6 +153,7 @@ jobs: env: # NOTE: The following lets us focus the test run while debugging. # TEST_ARGS: "-run TestDeploy ./pkg/commands/compute/..." + TEST_ARGS: "-v" TEST_COMPUTE_INIT: true TEST_COMPUTE_BUILD: true TEST_COMPUTE_DEPLOY: true From 91877752383a1fb0950902e0244cc0cbc016a174 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:30:42 +0100 Subject: [PATCH 06/11] refactor(compute/init): rephrase prompt to continue with post_init --- pkg/commands/compute/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index cec84a7f5..237d2d677 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -1193,7 +1193,7 @@ func promptForPostInitContinue(msg, script string, out io.Writer, in io.Reader) text.Break(out) text.Indent(out, 4, "%s", script) - label := "\nContinue with the post_init step? [y/N] " + label := "\nDo you want to run this now? [y/N] " answer, err := text.AskYesNo(out, label, in) if err != nil { return err From f10b21d0a8fdce51afbb621ace8ba1b862b0ac64 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:33:07 +0100 Subject: [PATCH 07/11] refactor(compute/build): rephrase prompt to continue with post_build --- pkg/commands/compute/language_toolchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/compute/language_toolchain.go b/pkg/commands/compute/language_toolchain.go index 19d11d287..cad5b68d0 100644 --- a/pkg/commands/compute/language_toolchain.go +++ b/pkg/commands/compute/language_toolchain.go @@ -206,7 +206,7 @@ func (bt BuildToolchain) promptForPostBuildContinue(msg, script string, out io.W text.Break(out) text.Indent(out, 4, "%s", script) - label := "\nAre you sure you want to continue with the post build step? [y/N] " + label := "\nDo you want to run this now? [y/N] " answer, err := text.AskYesNo(out, label, in) if err != nil { return err From 570f72d642f994ecc9d994bfabcaa42c7f7472dc Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:33:29 +0100 Subject: [PATCH 08/11] fix(compute/init): avoid import shadowing bug --- pkg/commands/compute/init.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/compute/init.go b/pkg/commands/compute/init.go index 237d2d677..8786fb5c0 100644 --- a/pkg/commands/compute/init.go +++ b/pkg/commands/compute/init.go @@ -254,10 +254,10 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) { spinner.Message(msg) s := Shell{} - cmd, args := s.Build(postInit) + command, args := s.Build(postInit) noTimeout := 0 // zero indicates no timeout err := fstexec.Command( - cmd, args, msg, out, spinner, c.Globals.Flags.Verbose, noTimeout, c.Globals.ErrLog, + command, args, msg, out, spinner, c.Globals.Flags.Verbose, noTimeout, c.Globals.ErrLog, ) if err != nil { return err From 6887dd527c44834ed535ea41c7415b0192288eff Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:52:41 +0100 Subject: [PATCH 09/11] fix(compute): tests --- pkg/commands/compute/build_test.go | 9 ++++----- pkg/commands/compute/compute_test.go | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/commands/compute/build_test.go b/pkg/commands/compute/build_test.go index 07d43dc17..75fd49ee7 100644 --- a/pkg/commands/compute/build_test.go +++ b/pkg/commands/compute/build_test.go @@ -1,7 +1,6 @@ package compute_test import ( - "bytes" "fmt" "os" "os/exec" @@ -726,7 +725,7 @@ func TestBuildOther(t *testing.T) { stdin: "N", wantOutput: []string{ "echo doing a post build", - "Are you sure you want to continue with the post build step?", + "Do you want to run this now?", }, wantError: "build process stopped by user", }, @@ -743,7 +742,7 @@ func TestBuildOther(t *testing.T) { stdin: "Y", wantOutput: []string{ "echo doing a post build", - "Are you sure you want to continue with the post build step?", + "Do you want to run this now?", "Built package", }, }, @@ -760,7 +759,7 @@ func TestBuildOther(t *testing.T) { stdin: "Y", wantOutput: []string{ "echo doing a post build", - "Are you sure you want to continue with the post build step?", + "Do you want to run this now?", "Built package", }, }, @@ -777,7 +776,7 @@ func TestBuildOther(t *testing.T) { "doing a post build with no confirmation prompt", }, dontWantOutput: []string{ - "Are you sure you want to continue with the build step?", + "Do you want to run this now?", }, wantError: "exit status 1", // because we have to trigger an error to see the post_build output }, diff --git a/pkg/commands/compute/compute_test.go b/pkg/commands/compute/compute_test.go index d02fae6d7..3870c5716 100644 --- a/pkg/commands/compute/compute_test.go +++ b/pkg/commands/compute/compute_test.go @@ -6,13 +6,14 @@ import ( "reflect" "testing" + "github.com/fastly/kingpin" + "github.com/mholt/archiver/v3" + "github.com/fastly/cli/pkg/commands/compute" "github.com/fastly/cli/pkg/github" "github.com/fastly/cli/pkg/global" "github.com/fastly/cli/pkg/manifest" "github.com/fastly/cli/pkg/testutil" - "github.com/fastly/kingpin" - "github.com/mholt/archiver/v3" ) // TestPublishFlagDivergence validates that the manually curated list of flags From 719a9962451ac543549f6bb868c9dafc5f1dbba5 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:58:01 +0100 Subject: [PATCH 10/11] ci: fix test suite by passing complete args --- .github/workflows/pr_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 3311a7c49..a3357a6fa 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -153,7 +153,7 @@ jobs: env: # NOTE: The following lets us focus the test run while debugging. # TEST_ARGS: "-run TestDeploy ./pkg/commands/compute/..." - TEST_ARGS: "-v" + TEST_ARGS: "-v -timeout 15m ./..." TEST_COMPUTE_INIT: true TEST_COMPUTE_BUILD: true TEST_COMPUTE_DEPLOY: true From dc7ee8c9f61bdcc281c7b374610112d12b0b9bc6 Mon Sep 17 00:00:00 2001 From: Integralist Date: Wed, 16 Aug 2023 09:59:31 +0100 Subject: [PATCH 11/11] ci: move verbose flag to Makefile --- .github/workflows/pr_test.yml | 1 - Makefile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index a3357a6fa..7c6ece293 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -153,7 +153,6 @@ jobs: env: # NOTE: The following lets us focus the test run while debugging. # TEST_ARGS: "-run TestDeploy ./pkg/commands/compute/..." - TEST_ARGS: "-v -timeout 15m ./..." TEST_COMPUTE_INIT: true TEST_COMPUTE_BUILD: true TEST_COMPUTE_DEPLOY: true diff --git a/Makefile b/Makefile index 253632525..d68181544 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ build: config ## Compile program (CGO disabled) GO_BIN ?= go ## Allows overriding go executable. TEST_COMMAND ?= $(GO_BIN) test ## Enables support for tools such as https://github.com/rakyll/gotest -TEST_ARGS ?= -timeout 15m ./... ## The compute tests can sometimes exceed the default 10m limit +TEST_ARGS ?= -v -timeout 15m ./... ## The compute tests can sometimes exceed the default 10m limit GOHOSTOS ?= $(shell go env GOHOSTOS || echo unknown) GOHOSTARCH ?= $(shell go env GOHOSTARCH || echo unknown)