From affc4b5f2a568d147d0cf58af9f2a0095aa0b2c0 Mon Sep 17 00:00:00 2001 From: John Swanson Date: Mon, 16 Jul 2018 16:02:11 -0700 Subject: [PATCH 01/12] allow reading config from stdin --- api/api.go | 10 ++++++++-- cmd/orb.go | 4 ++-- cmd/root.go | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/api/api.go b/api/api.go index 1d3832563..960b67dc9 100644 --- a/api/api.go +++ b/api/api.go @@ -3,6 +3,7 @@ package api import ( "context" "io/ioutil" + "os" "strings" "github.com/CircleCI-Public/circleci-cli/client" @@ -54,8 +55,13 @@ func (response GQLResponseErrors) ToError() error { } func loadYaml(path string) (string, error) { - - config, err := ioutil.ReadFile(path) + var err error + var config []byte + if path == "-" { + config, err = ioutil.ReadAll(os.Stdin) + } else { + config, err = ioutil.ReadFile(path) + } if err != nil { return "", errors.Wrapf(err, "Could not load config file at %s", path) diff --git a/cmd/orb.go b/cmd/orb.go index c19e72a04..2568a0b1e 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -55,10 +55,10 @@ func newOrbCommand() *cobra.Command { orbCommand.AddCommand(orbListCommand) - orbValidateCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") + orbValidateCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file ('-' for STDIN)") orbCommand.AddCommand(orbValidateCommand) - orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") + orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file ('-' for STDIN)") orbCommand.AddCommand(orbExpandCommand) orbCommand.AddCommand(orbPublishCommand) diff --git a/cmd/root.go b/cmd/root.go index 4d1ba8372..3b79ba961 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,7 +51,7 @@ func MakeCommands() *cobra.Command { rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose logging.") rootCmd.PersistentFlags().StringP("endpoint", "e", defaultEndpoint, "the endpoint of your CircleCI GraphQL API") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") - rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", configPath, "path to build config") + rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", configPath, "path to build config. '-' reads from STDIN") for _, flag := range []string{"endpoint", "token", "verbose"} { bindCobraFlagToViper(rootCmd, flag) From bfb14f189ec0cdf0aad311f64f9d8dbeab2068d6 Mon Sep 17 00:00:00 2001 From: John Swanson Date: Tue, 17 Jul 2018 15:48:31 -0700 Subject: [PATCH 02/12] make configPath and orbPath positional arguments - configPath has a default value of .circleci/config.yml - orbPath is required --- cmd/config.go | 11 ++++++++++- cmd/config_test.go | 4 ++-- cmd/orb.go | 10 +++++----- cmd/orb_test.go | 4 ++-- cmd/root.go | 3 --- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index f686076f0..8bc28b713 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" ) +const defaultConfigPath = ".circleci/config.yml" + func newConfigCommand() *cobra.Command { configCmd := &cobra.Command{ Use: "config", @@ -34,6 +36,10 @@ func newConfigCommand() *cobra.Command { func validateConfig(cmd *cobra.Command, args []string) error { + configPath := defaultConfigPath + if len(args) == 1 { + configPath = args[0] + } ctx := context.Background() response, err := api.ConfigQuery(ctx, Logger, configPath) @@ -51,7 +57,10 @@ func validateConfig(cmd *cobra.Command, args []string) error { func expandConfig(cmd *cobra.Command, args []string) error { ctx := context.Background() - + configPath := defaultConfigPath + if len(args) == 1 { + configPath = args[0] + } response, err := api.ConfigQuery(ctx, Logger, configPath) if err != nil { diff --git a/cmd/config_test.go b/cmd/config_test.go index 19edfde45..76ab12959 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -44,7 +44,7 @@ var _ = Describe("Config", func() { "config", "validate", "-t", token, "-e", testServer.URL(), - "-c", config.Path, + config.Path, ) }) @@ -120,7 +120,7 @@ var _ = Describe("Config", func() { "config", "expand", "-t", token, "-e", testServer.URL(), - "-c", config.Path, + config.Path, ) }) diff --git a/cmd/orb.go b/cmd/orb.go index 2568a0b1e..4f0a2a2cf 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -31,12 +31,14 @@ func newOrbCommand() *cobra.Command { Use: "validate", Short: "validate an orb.yml", RunE: validateOrb, + Args: cobra.ExactArgs(1), } orbExpandCommand := &cobra.Command{ Use: "expand", Short: "expand an orb.yml", RunE: expandOrb, + Args: cobra.ExactArgs(1), } orbPublishCommand := &cobra.Command{ @@ -55,10 +57,8 @@ func newOrbCommand() *cobra.Command { orbCommand.AddCommand(orbListCommand) - orbValidateCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file ('-' for STDIN)") orbCommand.AddCommand(orbValidateCommand) - orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file ('-' for STDIN)") orbCommand.AddCommand(orbExpandCommand) orbCommand.AddCommand(orbPublishCommand) @@ -182,7 +182,7 @@ query ListOrbs ($after: String!) { func validateOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - response, err := api.OrbQuery(ctx, Logger, orbPath) + response, err := api.OrbQuery(ctx, Logger, args[0]) if err != nil { return err @@ -192,14 +192,14 @@ func validateOrb(cmd *cobra.Command, args []string) error { return response.ToError() } - Logger.Infof("Orb at %s is valid", orbPath) + Logger.Infof("Orb at %s is valid", args[0]) return nil } func expandOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - response, err := api.OrbQuery(ctx, Logger, orbPath) + response, err := api.OrbQuery(ctx, Logger, args[0]) if err != nil { return err diff --git a/cmd/orb_test.go b/cmd/orb_test.go index fd016553d..974cdf399 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -40,7 +40,7 @@ var _ = Describe("Orb integration tests", func() { "orb", "validate", "-t", token, "-e", testServer.URL(), - "-p", orb.Path, + orb.Path, ) }) @@ -113,7 +113,7 @@ var _ = Describe("Orb integration tests", func() { "orb", "expand", "-t", token, "-e", testServer.URL(), - "-p", orb.Path, + orb.Path, ) }) diff --git a/cmd/root.go b/cmd/root.go index 3b79ba961..3d1e1ae66 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,8 +14,6 @@ import ( var defaultEndpoint = "https://circleci.com/graphql-unstable" // Path to the config.yml file to operate on. -var configPath = ".circleci/config.yml" - // Execute adds all child commands to rootCmd and // sets flags appropriately. This function is called // by main.main(). It only needs to happen once to @@ -51,7 +49,6 @@ func MakeCommands() *cobra.Command { rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose logging.") rootCmd.PersistentFlags().StringP("endpoint", "e", defaultEndpoint, "the endpoint of your CircleCI GraphQL API") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") - rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", configPath, "path to build config. '-' reads from STDIN") for _, flag := range []string{"endpoint", "token", "verbose"} { bindCobraFlagToViper(rootCmd, flag) From f7c85a4b3f3feb73a87d2d3b15e80977c51da2b0 Mon Sep 17 00:00:00 2001 From: John Swanson Date: Tue, 17 Jul 2018 16:19:09 -0700 Subject: [PATCH 03/12] add tests for reading orb from stdin --- cmd/orb_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 974cdf399..fddfc89a7 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -10,6 +10,7 @@ import ( "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/ghttp" + "io" ) var _ = Describe("Orb integration tests", func() { @@ -34,6 +35,56 @@ var _ = Describe("Orb integration tests", func() { testServer.Close() }) + Describe("when using STDIN", func() { + var ( + token string + command *exec.Cmd + ) + + BeforeEach(func() { + token = "testtoken" + command = exec.Command(pathCLI, + "orb", "validate", + "-t", token, + "-e", testServer.URL(), + "-", + ) + stdin, err := command.StdinPipe() + Expect(err).ToNot(HaveOccurred()) + go func() { + defer stdin.Close() + io.WriteString(stdin, "{}") + }() + }) + + It("works", func() { + By("setting up a mock server") + + gqlResponse := `{ + "orbConfig": { + "sourceYaml": "{}", + "valid": true, + "errors": [] + } + }` + + expectedRequestJson := ` { + "query": "\n\t\tquery ValidateOrb ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + "variables": { + "config": "{}" + } + }` + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + // the .* is because the full path with temp dir is printed + Eventually(session.Out).Should(gbytes.Say("Orb at - is valid")) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + Describe("when validating orb", func() { BeforeEach(func() { command = exec.Command(pathCLI, From b0e3d2a5a20e352c443f565c85ae0e2b68837c6d Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Mon, 23 Jul 2018 12:09:39 +0900 Subject: [PATCH 04/12] Clean up expected json response data and match formatting from API --- cmd/orb_test.go | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index fddfc89a7..baaac25c8 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -1,16 +1,18 @@ package cmd_test import ( + "encoding/json" "net/http" "os/exec" "path/filepath" + "io" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/ghttp" - "io" ) var _ = Describe("Orb integration tests", func() { @@ -68,14 +70,33 @@ var _ = Describe("Orb integration tests", func() { } }` - expectedRequestJson := ` { - "query": "\n\t\tquery ValidateOrb ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", - "variables": { - "config": "{}" - } - }` + type requestJson struct { + Query string `json:"query"` + Variables struct { + Config string `json:"config"` + } `json:"variables"` + } + + response := requestJson{ + Query: ` + query ValidateOrb ($config: String!) { + orbConfig(orbYaml: $config) { + valid, + errors { message }, + sourceYaml, + outputYaml + } + }`, + Variables: struct { + Config string `json:"config"` + }{ + Config: "{}", + }, + } + expected, err := json.Marshal(response) + Expect(err).ShouldNot(HaveOccurred()) - appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + appendPostHandler(testServer, token, http.StatusOK, string(expected), gqlResponse) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).ShouldNot(HaveOccurred()) From 7b0267ea27d8e70be1b195c5c3733cb9c7b5c15f Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Mon, 23 Jul 2018 12:12:28 +0900 Subject: [PATCH 05/12] Remove irrelevant comment (makes linter happy) --- cmd/root.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 3d1e1ae66..32999d725 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,7 +13,6 @@ import ( var defaultEndpoint = "https://circleci.com/graphql-unstable" -// Path to the config.yml file to operate on. // Execute adds all child commands to rootCmd and // sets flags appropriately. This function is called // by main.main(). It only needs to happen once to From 8f46b58c5695edd3d785420570b4492863c2c7ee Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Mon, 23 Jul 2018 12:16:24 +0900 Subject: [PATCH 06/12] :nail_care: clean up unused type --- cmd/orb_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index baaac25c8..0587a2586 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -70,14 +70,12 @@ var _ = Describe("Orb integration tests", func() { } }` - type requestJson struct { + response := struct { Query string `json:"query"` Variables struct { Config string `json:"config"` } `json:"variables"` - } - - response := requestJson{ + }{ Query: ` query ValidateOrb ($config: String!) { orbConfig(orbYaml: $config) { From cd5966802a767a97f4a7fac55a6c28c69f6a7d5d Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Mon, 23 Jul 2018 20:52:27 +0900 Subject: [PATCH 07/12] Support default path for orb commands Also changes config commands to use a positional arg max of 1, and include it in usage documentation for `--help`. Ensure temp files and dirs are removed Also adds a test for testing orb validate with default path --- cmd/cmd_suite_test.go | 23 ++++++++++++++++ cmd/config.go | 6 +++-- cmd/orb.go | 23 +++++++++++----- cmd/orb_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/cmd/cmd_suite_test.go b/cmd/cmd_suite_test.go index 7ad0862f2..93d698b8d 100644 --- a/cmd/cmd_suite_test.go +++ b/cmd/cmd_suite_test.go @@ -1,6 +1,7 @@ package cmd_test import ( + "io" "io/ioutil" "net/http" "os" @@ -81,6 +82,7 @@ func openTmpFile(path string) (tmpFile, error) { if err != nil { return config, err } + defer os.RemoveAll(tmpDir) config.RootDir = tmpDir config.Path = filepath.Join(tmpDir, path) @@ -99,8 +101,29 @@ func openTmpFile(path string) (tmpFile, error) { if err != nil { return config, err } + defer deferClose(file) config.File = file return config, nil } + +func deferClose(closer io.Closer) { + err := closer.Close() + Expect(err).NotTo(HaveOccurred()) +} + +func copyTo(source tmpFile, destination string) error { + in, err := os.Open(source.Path) + Expect(err).NotTo(HaveOccurred()) + defer deferClose(in) + + dst := filepath.Join(destination, filepath.Base(source.File.Name())) + out, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, 0600) + Expect(err).NotTo(HaveOccurred()) + defer deferClose(out) + + _, err = io.Copy(out, in) + Expect(err).NotTo(HaveOccurred()) + return out.Close() +} diff --git a/cmd/config.go b/cmd/config.go index 8bc28b713..27aa6c638 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -16,16 +16,18 @@ func newConfigCommand() *cobra.Command { } validateCommand := &cobra.Command{ - Use: "validate", + Use: "validate [config.yml]", Aliases: []string{"check"}, Short: "Check that the config file is well formed.", RunE: validateConfig, + Args: cobra.MaximumNArgs(1), } expandCommand := &cobra.Command{ - Use: "expand", + Use: "expand [config.yml]", Short: "Expand the config.", RunE: expandConfig, + Args: cobra.MaximumNArgs(1), } configCmd.AddCommand(validateCommand) diff --git a/cmd/orb.go b/cmd/orb.go index 4f0a2a2cf..d352a93b0 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -28,17 +28,17 @@ func newOrbCommand() *cobra.Command { } orbValidateCommand := &cobra.Command{ - Use: "validate", + Use: "validate [orb.yml]", Short: "validate an orb.yml", RunE: validateOrb, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), } orbExpandCommand := &cobra.Command{ - Use: "expand", + Use: "expand [orb.yml]", Short: "expand an orb.yml", RunE: expandOrb, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), } orbPublishCommand := &cobra.Command{ @@ -180,9 +180,15 @@ query ListOrbs ($after: String!) { return nil } +const defaultOrbPath = "orb.yml" + func validateOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - response, err := api.OrbQuery(ctx, Logger, args[0]) + orbPath := defaultOrbPath + if len(args) == 1 { + orbPath = args[0] + } + response, err := api.OrbQuery(ctx, Logger, orbPath) if err != nil { return err @@ -198,8 +204,11 @@ func validateOrb(cmd *cobra.Command, args []string) error { func expandOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - - response, err := api.OrbQuery(ctx, Logger, args[0]) + orbPath := defaultOrbPath + if len(args) == 1 { + orbPath = args[0] + } + response, err := api.OrbQuery(ctx, Logger, orbPath) if err != nil { return err diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 0587a2586..d062a9df4 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -104,6 +104,67 @@ var _ = Describe("Orb integration tests", func() { }) }) + Describe("when using default path", func() { + var ( + token string + command *exec.Cmd + ) + + BeforeEach(func() { + var err error + orb, err = openTmpFile("orb.yml") + Expect(err).ToNot(HaveOccurred()) + + orbCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli") + Expect(err).ToNot(HaveOccurred()) + + err = copyTo(orb, filepath.Dir(orbCLI)) + Expect(err).ToNot(HaveOccurred()) + + token = "testtoken" + command = exec.Command(pathCLI, + "orb", "validate", + "-t", token, + "-e", testServer.URL(), + ) + }) + + AfterEach(func() { + orb.close() + }) + + FIt("works", func() { + By("setting up a mock server") + err := orb.write(`{}`) + Expect(err).ToNot(HaveOccurred()) + + gqlResponse := `{ + "orbConfig": { + "sourceYaml": "{}", + "valid": true, + "errors": [] + } + }` + + expectedRequestJson := ` { + "query": "\n\t\tquery ValidateOrb ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + "variables": { + "config": "{}" + } + }` + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + // the .* is because the full path with temp dir is printed + Eventually(session.Out).Should(gbytes.Say("Orb at .*orb.yml is valid")) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + Describe("when validating orb", func() { BeforeEach(func() { command = exec.Command(pathCLI, From 59441337aea678e6496f371dd486806695e1c566 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 24 Jul 2018 17:01:29 +0900 Subject: [PATCH 08/12] Fix segfault when args aren't set ``` panic: runtime error: index out of range goroutine 1 [running]: github.com/CircleCI-Public/circleci-cli/cmd.validateOrb(0xc420183680, 0xb4aac8, 0x0, 0x0, 0x0, 0x0) /home/zzak/work/go/src/github.com/CircleCI-Public/circleci-cli/cmd/orb.go:137 +0x1a0 ``` --- cmd/orb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/orb.go b/cmd/orb.go index d352a93b0..db5168e67 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -198,7 +198,7 @@ func validateOrb(cmd *cobra.Command, args []string) error { return response.ToError() } - Logger.Infof("Orb at %s is valid", args[0]) + Logger.Infof("Orb at %s is valid", orbPath) return nil } From c72a523722f4a0a4475ab1d927c8941d6efb7878 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 24 Jul 2018 17:05:02 +0900 Subject: [PATCH 09/12] :hocho: this test Adding files into the working directory of an executable built by gomega is too hard, they create a temporary directory for each time and compile the binary to there each time you instruct it to. So creating a situation where a file exists at the current $PWD when executing the test binary, e.g. `.circleci/config.yml` would require you to create that file inside the temp directory owned by gomega. --- cmd/cmd_suite_test.go | 15 ----------- cmd/orb_test.go | 61 ------------------------------------------- 2 files changed, 76 deletions(-) diff --git a/cmd/cmd_suite_test.go b/cmd/cmd_suite_test.go index 93d698b8d..77948cc0e 100644 --- a/cmd/cmd_suite_test.go +++ b/cmd/cmd_suite_test.go @@ -112,18 +112,3 @@ func deferClose(closer io.Closer) { err := closer.Close() Expect(err).NotTo(HaveOccurred()) } - -func copyTo(source tmpFile, destination string) error { - in, err := os.Open(source.Path) - Expect(err).NotTo(HaveOccurred()) - defer deferClose(in) - - dst := filepath.Join(destination, filepath.Base(source.File.Name())) - out, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE, 0600) - Expect(err).NotTo(HaveOccurred()) - defer deferClose(out) - - _, err = io.Copy(out, in) - Expect(err).NotTo(HaveOccurred()) - return out.Close() -} diff --git a/cmd/orb_test.go b/cmd/orb_test.go index d062a9df4..0587a2586 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -104,67 +104,6 @@ var _ = Describe("Orb integration tests", func() { }) }) - Describe("when using default path", func() { - var ( - token string - command *exec.Cmd - ) - - BeforeEach(func() { - var err error - orb, err = openTmpFile("orb.yml") - Expect(err).ToNot(HaveOccurred()) - - orbCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli") - Expect(err).ToNot(HaveOccurred()) - - err = copyTo(orb, filepath.Dir(orbCLI)) - Expect(err).ToNot(HaveOccurred()) - - token = "testtoken" - command = exec.Command(pathCLI, - "orb", "validate", - "-t", token, - "-e", testServer.URL(), - ) - }) - - AfterEach(func() { - orb.close() - }) - - FIt("works", func() { - By("setting up a mock server") - err := orb.write(`{}`) - Expect(err).ToNot(HaveOccurred()) - - gqlResponse := `{ - "orbConfig": { - "sourceYaml": "{}", - "valid": true, - "errors": [] - } - }` - - expectedRequestJson := ` { - "query": "\n\t\tquery ValidateOrb ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", - "variables": { - "config": "{}" - } - }` - - appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) - - By("running the command") - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - - Expect(err).ShouldNot(HaveOccurred()) - // the .* is because the full path with temp dir is printed - Eventually(session.Out).Should(gbytes.Say("Orb at .*orb.yml is valid")) - Eventually(session).Should(gexec.Exit(0)) - }) - }) - Describe("when validating orb", func() { BeforeEach(func() { command = exec.Command(pathCLI, From 026323f8b86c0f9ddf922951afb860a38d19d013 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 24 Jul 2018 17:27:44 +0900 Subject: [PATCH 10/12] Have my :cake: and add a test for the default path on orb validate By allowing us to specify the path when creating a temp file, we can write directly to the working directory of the test executable. This resolves my comments on 74deab2. Also make sure temp files and folders are properly disposed of, this is something we're leaving up to who opened them first. Since using `defer` properly won't work because context is left between tests -- we should use `AfterEach` to close them ourselves. --- cmd/cmd_suite_test.go | 30 ++++++++++----------- cmd/config_test.go | 8 +++++- cmd/orb_test.go | 63 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/cmd/cmd_suite_test.go b/cmd/cmd_suite_test.go index 77948cc0e..ba4acf517 100644 --- a/cmd/cmd_suite_test.go +++ b/cmd/cmd_suite_test.go @@ -1,7 +1,6 @@ package cmd_test import ( - "io" "io/ioutil" "net/http" "os" @@ -72,20 +71,25 @@ func (f tmpFile) write(fileContent string) error { return err } -func openTmpFile(path string) (tmpFile, error) { +func openTmpDir(prefix string) (string, error) { + var dir string + if prefix == "" { + dir = "circleci-cli-test-" + } else { + dir = prefix + } + tmpDir, err := ioutil.TempDir("", dir) + return tmpDir, err +} + +func openTmpFile(directory string, path string) (tmpFile, error) { var ( config tmpFile = tmpFile{} err error ) - tmpDir, err := ioutil.TempDir("", "circleci-cli-test-") - if err != nil { - return config, err - } - defer os.RemoveAll(tmpDir) - - config.RootDir = tmpDir - config.Path = filepath.Join(tmpDir, path) + config.RootDir = directory + config.Path = filepath.Join(directory, path) err = os.MkdirAll(filepath.Dir(config.Path), 0700) if err != nil { @@ -101,14 +105,8 @@ func openTmpFile(path string) (tmpFile, error) { if err != nil { return config, err } - defer deferClose(file) config.File = file return config, nil } - -func deferClose(closer io.Closer) { - err := closer.Close() - Expect(err).NotTo(HaveOccurred()) -} diff --git a/cmd/config_test.go b/cmd/config_test.go index 76ab12959..d7c046360 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -2,6 +2,7 @@ package cmd_test import ( "net/http" + "os" "os/exec" "path/filepath" @@ -17,11 +18,15 @@ var _ = Describe("Config", func() { var ( testServer *ghttp.Server config tmpFile + tmpDir string ) BeforeEach(func() { var err error - config, err = openTmpFile(filepath.Join(".circleci", "config.yaml")) + tmpDir, err = openTmpDir("") + Expect(err).ToNot(HaveOccurred()) + + config, err = openTmpFile(tmpDir, filepath.Join(".circleci", "config.yaml")) Expect(err).ToNot(HaveOccurred()) testServer = ghttp.NewServer() @@ -29,6 +34,7 @@ var _ = Describe("Config", func() { AfterEach(func() { config.close() + os.RemoveAll(tmpDir) testServer.Close() }) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 0587a2586..a49927923 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -3,6 +3,7 @@ package cmd_test import ( "encoding/json" "net/http" + "os" "os/exec" "path/filepath" @@ -22,11 +23,15 @@ var _ = Describe("Orb integration tests", func() { orb tmpFile token string = "testtoken" command *exec.Cmd + tmpDir string ) BeforeEach(func() { var err error - orb, err = openTmpFile(filepath.Join("myorb", "orb.yml")) + tmpDir, err = openTmpDir("") + Expect(err).ToNot(HaveOccurred()) + + orb, err = openTmpFile(tmpDir, filepath.Join("myorb", "orb.yml")) Expect(err).ToNot(HaveOccurred()) testServer = ghttp.NewServer() @@ -34,6 +39,7 @@ var _ = Describe("Orb integration tests", func() { AfterEach(func() { orb.close() + os.RemoveAll(tmpDir) testServer.Close() }) @@ -104,6 +110,61 @@ var _ = Describe("Orb integration tests", func() { }) }) + Describe("when using default path", func() { + var ( + token string + command *exec.Cmd + ) + + BeforeEach(func() { + var err error + token = "testtoken" + command = exec.Command(pathCLI, + "orb", "validate", + "-t", token, + "-e", testServer.URL(), + ) + + orb, err = openTmpFile(command.Dir, "orb.yml") + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + orb.close() + }) + + It("works", func() { + By("setting up a mock server") + err := orb.write(`{}`) + Expect(err).ToNot(HaveOccurred()) + + gqlResponse := `{ + "orbConfig": { + "sourceYaml": "{}", + "valid": true, + "errors": [] + } + }` + + expectedRequestJson := ` { + "query": "\n\t\tquery ValidateOrb ($config: String!) {\n\t\t\torbConfig(orbYaml: $config) {\n\t\t\t\tvalid,\n\t\t\t\terrors { message },\n\t\t\t\tsourceYaml,\n\t\t\t\toutputYaml\n\t\t\t}\n\t\t}", + "variables": { + "config": "{}" + } + }` + + appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + // the .* is because the full path with temp dir is printed + Eventually(session.Out).Should(gbytes.Say("Orb at .*orb.yml is valid")) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + Describe("when validating orb", func() { BeforeEach(func() { command = exec.Command(pathCLI, From 1c600eaec59f3c63688668b38f7e6753e88de3bb Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 24 Jul 2018 17:36:42 +0900 Subject: [PATCH 11/12] Ensure temp orb is removed from default path test --- cmd/orb_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index a49927923..2914c15f8 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -131,6 +131,7 @@ var _ = Describe("Orb integration tests", func() { AfterEach(func() { orb.close() + os.Remove(orb.Path) }) It("works", func() { From f70fabd5c628850ee32420ed6664d3f953aadad2 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 24 Jul 2018 18:15:17 +0900 Subject: [PATCH 12/12] Allow default path for orb publish command as positional arg --- cmd/orb.go | 9 ++++++--- cmd/orb_test.go | 12 +----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/cmd/orb.go b/cmd/orb.go index db5168e67..25826f321 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -15,7 +15,6 @@ import ( "gopkg.in/yaml.v2" ) -var orbPath string var orbVersion string var orbID string @@ -42,11 +41,11 @@ func newOrbCommand() *cobra.Command { } orbPublishCommand := &cobra.Command{ - Use: "publish", + Use: "publish [orb.yml]", Short: "publish a version of an orb", RunE: publishOrb, + Args: cobra.MaximumNArgs(1), } - orbPublishCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbPublishCommand.PersistentFlags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish") orbPublishCommand.PersistentFlags().StringVarP(&orbID, "orb-id", "i", "", "id of orb to publish") @@ -224,6 +223,10 @@ func expandOrb(cmd *cobra.Command, args []string) error { func publishOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() + orbPath := defaultOrbPath + if len(args) == 1 { + orbPath = args[0] + } response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbID) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 2914c15f8..781ddbcd7 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -44,11 +44,6 @@ var _ = Describe("Orb integration tests", func() { }) Describe("when using STDIN", func() { - var ( - token string - command *exec.Cmd - ) - BeforeEach(func() { token = "testtoken" command = exec.Command(pathCLI, @@ -111,11 +106,6 @@ var _ = Describe("Orb integration tests", func() { }) Describe("when using default path", func() { - var ( - token string - command *exec.Cmd - ) - BeforeEach(func() { var err error token = "testtoken" @@ -320,7 +310,7 @@ var _ = Describe("Orb integration tests", func() { "orb", "publish", "-t", token, "-e", testServer.URL(), - "-p", orb.Path, + orb.Path, "--orb-version", "0.0.1", "--orb-id", "bb604b45-b6b0-4b81-ad80-796f15eddf87", )