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/cmd_suite_test.go b/cmd/cmd_suite_test.go index 7ad0862f2..ba4acf517 100644 --- a/cmd/cmd_suite_test.go +++ b/cmd/cmd_suite_test.go @@ -71,19 +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 - } - - 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 { diff --git a/cmd/config.go b/cmd/config.go index f686076f0..27aa6c638 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", @@ -14,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) @@ -34,6 +38,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 +59,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..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() }) @@ -44,7 +50,7 @@ var _ = Describe("Config", func() { "config", "validate", "-t", token, "-e", testServer.URL(), - "-c", config.Path, + config.Path, ) }) @@ -120,7 +126,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 c19e72a04..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 @@ -28,23 +27,25 @@ func newOrbCommand() *cobra.Command { } orbValidateCommand := &cobra.Command{ - Use: "validate", + Use: "validate [orb.yml]", Short: "validate an orb.yml", RunE: validateOrb, + Args: cobra.MaximumNArgs(1), } orbExpandCommand := &cobra.Command{ - Use: "expand", + Use: "expand [orb.yml]", Short: "expand an orb.yml", RunE: expandOrb, + Args: cobra.MaximumNArgs(1), } 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") @@ -55,10 +56,8 @@ func newOrbCommand() *cobra.Command { orbCommand.AddCommand(orbListCommand) - orbValidateCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbCommand.AddCommand(orbValidateCommand) - orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file") orbCommand.AddCommand(orbExpandCommand) orbCommand.AddCommand(orbPublishCommand) @@ -180,8 +179,14 @@ query ListOrbs ($after: String!) { return nil } +const defaultOrbPath = "orb.yml" + func validateOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() + orbPath := defaultOrbPath + if len(args) == 1 { + orbPath = args[0] + } response, err := api.OrbQuery(ctx, Logger, orbPath) if err != nil { @@ -198,7 +203,10 @@ func validateOrb(cmd *cobra.Command, args []string) error { func expandOrb(cmd *cobra.Command, args []string) error { ctx := context.Background() - + orbPath := defaultOrbPath + if len(args) == 1 { + orbPath = args[0] + } response, err := api.OrbQuery(ctx, Logger, orbPath) if err != nil { @@ -215,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 fd016553d..781ddbcd7 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -1,10 +1,14 @@ package cmd_test import ( + "encoding/json" "net/http" + "os" "os/exec" "path/filepath" + "io" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" @@ -19,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() @@ -31,16 +39,130 @@ var _ = Describe("Orb integration tests", func() { AfterEach(func() { orb.close() + os.RemoveAll(tmpDir) testServer.Close() }) + Describe("when using STDIN", func() { + 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": [] + } + }` + + response := struct { + Query string `json:"query"` + Variables struct { + Config string `json:"config"` + } `json:"variables"` + }{ + 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, string(expected), 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 using default path", func() { + 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() + os.Remove(orb.Path) + }) + + 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, "orb", "validate", "-t", token, "-e", testServer.URL(), - "-p", orb.Path, + orb.Path, ) }) @@ -113,7 +235,7 @@ var _ = Describe("Orb integration tests", func() { "orb", "expand", "-t", token, "-e", testServer.URL(), - "-p", orb.Path, + orb.Path, ) }) @@ -188,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", ) diff --git a/cmd/root.go b/cmd/root.go index 4d1ba8372..32999d725 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,9 +13,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 +48,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") for _, flag := range []string{"endpoint", "token", "verbose"} { bindCobraFlagToViper(rootCmd, flag)