diff --git a/api/api.go b/api/api.go index e8774256b..0e6680aa2 100644 --- a/api/api.go +++ b/api/api.go @@ -152,7 +152,12 @@ func OrbQuery(ctx context.Context, logger *logger.Logger, configPath string) (*C // OrbPublish publishes a new version of an orb func OrbPublish(ctx context.Context, logger *logger.Logger, - configPath string, orbVersion string, orbID string) (*PublishOrbResponse, error) { + name string, configPath string, orbVersion string) (*PublishOrbResponse, error) { + orbID, err := getOrbID(ctx, logger, name) + if err != nil { + return nil, err + } + var response struct { PublishOrb struct { PublishOrbResponse @@ -194,6 +199,37 @@ func OrbPublish(ctx context.Context, logger *logger.Logger, return &response.PublishOrb.PublishOrbResponse, err } +func getOrbID(ctx context.Context, logger *logger.Logger, name string) (string, error) { + var response struct { + Orb struct { + ID string + } + } + + query := `query($name: String!) { + orb(name: $name) { + id + } + }` + + request := client.NewAuthorizedRequest(viper.GetString("token"), query) + request.Var("name", name) + + graphQLclient := client.NewClient(viper.GetString("endpoint"), logger) + + err := graphQLclient.Run(ctx, request, &response) + + if err != nil { + return "", err + } + + if response.Orb.ID == "" { + return "", fmt.Errorf("the %s orb could not be found", name) + } + + return response.Orb.ID, nil +} + func createNamespaceWithOwnerID(ctx context.Context, logger *logger.Logger, name string, ownerID string) (*CreateNamespaceResponse, error) { var response struct { CreateNamespace struct { diff --git a/cmd/collapse.go b/cmd/collapse.go deleted file mode 100644 index f55df68b5..000000000 --- a/cmd/collapse.go +++ /dev/null @@ -1,36 +0,0 @@ -package cmd - -import ( - "github.com/CircleCI-Public/circleci-cli/filetree" - "github.com/pkg/errors" - "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v2" -) - -var root string - -func newCollapseCommand() *cobra.Command { - - collapseCommand := &cobra.Command{ - Use: "collapse", - Short: "Collapse your CircleCI configuration to a single file", - RunE: collapse, - } - collapseCommand.Flags().StringVarP(&root, "root", "r", ".", "path to your configuration (default is current path)") - - return collapseCommand -} - -func collapse(cmd *cobra.Command, args []string) error { - tree, err := filetree.NewTree(root) - if err != nil { - return errors.Wrap(err, "An error occurred trying to build the tree") - } - - y, err := yaml.Marshal(&tree) - if err != nil { - return errors.Wrap(err, "Failed trying to marshal the tree to YAML ") - } - Logger.Infof("%s\n", string(y)) - return nil -} diff --git a/cmd/collapse_test.go b/cmd/collapse_test.go deleted file mode 100644 index 28ef77f4f..000000000 --- a/cmd/collapse_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package cmd_test - -import ( - "io/ioutil" - "os/exec" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" -) - -var _ = Describe("collapse", func() { - var ( - command *exec.Cmd - results []byte - ) - - Describe("a .circleci folder with config.yml and local orbs folder containing the hugo orb", func() { - BeforeEach(func() { - var err error - command = exec.Command(pathCLI, "collapse", "-r", "testdata/hugo-collapse/.circleci") - results, err = ioutil.ReadFile("testdata/hugo-collapse/result.yml") - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("collapse all YAML contents as expected", func() { - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - session.Wait() - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err.Contents()).Should(BeEmpty()) - Eventually(session.Out.Contents()).Should(MatchYAML(results)) - Eventually(session).Should(gexec.Exit(0)) - }) - }) - - Describe("local orbs folder with mixed inline and local commands, jobs, etc", func() { - BeforeEach(func() { - var err error - var path string = "nested-orbs-and-local-commands-etc" - command = exec.Command(pathCLI, "collapse", "-r", filepath.Join("testdata", path, "test")) - results, err = ioutil.ReadFile(filepath.Join("testdata", path, "result.yml")) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("collapse all YAML contents as expected", func() { - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - session.Wait() - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err.Contents()).Should(BeEmpty()) - Eventually(session.Out.Contents()).Should(MatchYAML(results)) - Eventually(session).Should(gexec.Exit(0)) - }) - }) - - Describe("an orb containing local executors and commands in folder", func() { - BeforeEach(func() { - var err error - command = exec.Command(pathCLI, "collapse", "-r", "testdata/myorb/test") - results, err = ioutil.ReadFile("testdata/myorb/result.yml") - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("collapse all YAML contents as expected", func() { - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - session.Wait() - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err.Contents()).Should(BeEmpty()) - Eventually(session.Out.Contents()).Should(MatchYAML(results)) - Eventually(session).Should(gexec.Exit(0)) - }) - }) - - Describe("with a large nested config including rails orb", func() { - BeforeEach(func() { - var err error - var path string = "test-with-large-nested-rails-orb" - command = exec.Command(pathCLI, "collapse", "-r", filepath.Join("testdata", path, "test")) - results, err = ioutil.ReadFile(filepath.Join("testdata", path, "result.yml")) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("collapse all YAML contents as expected", func() { - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - session.Wait() - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err.Contents()).Should(BeEmpty()) - Eventually(session.Out.Contents()).Should(MatchYAML(results)) - Eventually(session).Should(gexec.Exit(0)) - }) - }) -}) diff --git a/cmd/config.go b/cmd/config.go index 27aa6c638..6ef709e27 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -4,7 +4,10 @@ import ( "context" "github.com/CircleCI-Public/circleci-cli/api" + "github.com/CircleCI-Public/circleci-cli/filetree" + "github.com/pkg/errors" "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v2" ) const defaultConfigPath = ".circleci/config.yml" @@ -15,6 +18,13 @@ func newConfigCommand() *cobra.Command { Short: "Operate on build config files", } + collapseCommand := &cobra.Command{ + Use: "collapse [path]", + Short: "Collapse your CircleCI configuration to a single file", + RunE: collapseConfig, + Args: cobra.MaximumNArgs(1), + } + validateCommand := &cobra.Command{ Use: "validate [config.yml]", Aliases: []string{"check"}, @@ -30,6 +40,7 @@ func newConfigCommand() *cobra.Command { Args: cobra.MaximumNArgs(1), } + configCmd.AddCommand(collapseCommand) configCmd.AddCommand(validateCommand) configCmd.AddCommand(expandCommand) @@ -37,7 +48,6 @@ func newConfigCommand() *cobra.Command { } func validateConfig(cmd *cobra.Command, args []string) error { - configPath := defaultConfigPath if len(args) == 1 { configPath = args[0] @@ -76,3 +86,21 @@ func expandConfig(cmd *cobra.Command, args []string) error { Logger.Info(response.OutputYaml) return nil } + +func collapseConfig(cmd *cobra.Command, args []string) error { + root := "." + if len(args) > 0 { + root = args[0] + } + tree, err := filetree.NewTree(root) + if err != nil { + return errors.Wrap(err, "An error occurred trying to build the tree") + } + + y, err := yaml.Marshal(&tree) + if err != nil { + return errors.Wrap(err, "Failed trying to marshal the tree to YAML ") + } + Logger.Infof("%s\n", string(y)) + return nil +} diff --git a/cmd/config_test.go b/cmd/config_test.go index d23e004b3..c10ac24c6 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -1,6 +1,7 @@ package cmd_test import ( + "io/ioutil" "net/http" "os" "os/exec" @@ -206,4 +207,85 @@ var _ = Describe("Config", func() { }) }) }) + + Describe("collapse", func() { + var ( + command *exec.Cmd + results []byte + ) + + Describe("a .circleci folder with config.yml and local orbs folder containing the hugo orb", func() { + BeforeEach(func() { + var err error + command = exec.Command(pathCLI, "config", "collapse", "testdata/hugo-collapse/.circleci") + results, err = ioutil.ReadFile("testdata/hugo-collapse/result.yml") + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("collapse all YAML contents as expected", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session.Wait() + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err.Contents()).Should(BeEmpty()) + Eventually(session.Out.Contents()).Should(MatchYAML(results)) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + + Describe("local orbs folder with mixed inline and local commands, jobs, etc", func() { + BeforeEach(func() { + var err error + var path string = "nested-orbs-and-local-commands-etc" + command = exec.Command(pathCLI, "config", "collapse", filepath.Join("testdata", path, "test")) + results, err = ioutil.ReadFile(filepath.Join("testdata", path, "result.yml")) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("collapse all YAML contents as expected", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session.Wait() + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err.Contents()).Should(BeEmpty()) + Eventually(session.Out.Contents()).Should(MatchYAML(results)) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + + Describe("an orb containing local executors and commands in folder", func() { + BeforeEach(func() { + var err error + command = exec.Command(pathCLI, "config", "collapse", "testdata/myorb/test") + results, err = ioutil.ReadFile("testdata/myorb/result.yml") + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("collapse all YAML contents as expected", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session.Wait() + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err.Contents()).Should(BeEmpty()) + Eventually(session.Out.Contents()).Should(MatchYAML(results)) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + + Describe("with a large nested config including rails orb", func() { + BeforeEach(func() { + var err error + var path string = "test-with-large-nested-rails-orb" + command = exec.Command(pathCLI, "config", "collapse", filepath.Join("testdata", path, "test")) + results, err = ioutil.ReadFile(filepath.Join("testdata", path, "result.yml")) + Expect(err).ShouldNot(HaveOccurred()) + }) + + It("collapse all YAML contents as expected", func() { + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + session.Wait() + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err.Contents()).Should(BeEmpty()) + Eventually(session.Out.Contents()).Should(MatchYAML(results)) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + }) }) diff --git a/cmd/diagnostic.go b/cmd/diagnostic.go index 2d18e7789..62e5d4dba 100644 --- a/cmd/diagnostic.go +++ b/cmd/diagnostic.go @@ -7,11 +7,13 @@ import ( ) func newDiagnosticCommand() *cobra.Command { - return &cobra.Command{ + diagnosticCommand := &cobra.Command{ Use: "diagnostic", Short: "Check the status of your CircleCI CLI.", RunE: diagnostic, } + + return diagnosticCommand } func diagnostic(cmd *cobra.Command, args []string) error { diff --git a/cmd/namespace.go b/cmd/namespace.go new file mode 100644 index 000000000..32bbece45 --- /dev/null +++ b/cmd/namespace.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "context" + "strings" + + "github.com/CircleCI-Public/circleci-cli/api" + "github.com/spf13/cobra" +) + +func newNamespaceCommand() *cobra.Command { + namespaceCmd := &cobra.Command{ + Use: "namespace", + Short: "Operate on namespaces", + } + + createCmd := &cobra.Command{ + Use: "create [name] [vcs] [org-name]", + Short: "create an namespace", + RunE: createNamespace, + Args: cobra.ExactArgs(3), + } + + // "org-name", "", "organization name (required)" + // "vcs", "github", "organization vcs, e.g. 'github', 'bitbucket'" + + namespaceCmd.AddCommand(createCmd) + + return namespaceCmd +} + +func createNamespace(cmd *cobra.Command, args []string) error { + var err error + ctx := context.Background() + + response, err := api.CreateNamespace(ctx, Logger, args[0], args[2], strings.ToUpper(args[1])) + + if err != nil { + return err + } + + if len(response.Errors) > 0 { + return response.ToError() + } + + Logger.Info("Namespace created") + return nil +} diff --git a/cmd/namespace_test.go b/cmd/namespace_test.go new file mode 100644 index 000000000..50e3e4603 --- /dev/null +++ b/cmd/namespace_test.go @@ -0,0 +1,216 @@ +package cmd_test + +import ( + "net/http" + "os/exec" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" + "github.com/onsi/gomega/ghttp" +) + +var _ = Describe("Namespace integration tests", func() { + var ( + testServer *ghttp.Server + token string = "testtoken" + command *exec.Cmd + ) + + BeforeEach(func() { + testServer = ghttp.NewServer() + }) + + AfterEach(func() { + testServer.Close() + }) + + Describe("registering a namespace", func() { + BeforeEach(func() { + command = exec.Command(pathCLI, + "namespace", "create", + "-t", token, + "-e", testServer.URL(), + "foo-ns", + "BITBUCKET", + "test-org", + ) + }) + + It("works with organizationName and organizationVcs", func() { + By("setting up a mock server") + + gqlOrganizationResponse := `{ + "organization": { + "name": "test-org", + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + expectedOrganizationRequest := `{ + "query": "\n\t\t\tquery($organizationName: String!, $organizationVcs: VCSType!) {\n\t\t\t\torganization(\n\t\t\t\t\tname: $organizationName\n\t\t\t\t\tvcsType: $organizationVcs\n\t\t\t\t) {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}", + "variables": { + "organizationName": "test-org", + "organizationVcs": "BITBUCKET" + } + }` + + gqlNsResponse := `{ + "createNamespace": { + "errors": [], + "namespace": { + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + } + }` + + expectedNsRequest := `{ + "query": "\n\t\t\tmutation($name: String!, $organizationId: UUID!) {\n\t\t\t\tcreateNamespace(\n\t\t\t\t\tname: $name,\n\t\t\t\t\torganizationId: $organizationId\n\t\t\t\t) {\n\t\t\t\t\tnamespace {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\terrors {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttype\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}", + "variables": { + "name": "foo-ns", + "organizationId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + appendPostHandler(testServer, token, MockRequestResponse{ + Status: http.StatusOK, + Request: expectedOrganizationRequest, + Response: gqlOrganizationResponse}) + appendPostHandler(testServer, token, MockRequestResponse{ + Status: http.StatusOK, + Request: expectedNsRequest, + Response: gqlNsResponse}) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Out).Should(gbytes.Say("Namespace created")) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + + Describe("when creating / reserving a namespace", func() { + BeforeEach(func() { + command = exec.Command(pathCLI, + "namespace", "create", + "-t", token, + "-e", testServer.URL(), + "foo-ns", + "BITBUCKET", + "test-org", + ) + }) + + It("works with organizationName and organizationVcs", func() { + By("setting up a mock server") + + gqlOrganizationResponse := `{ + "organization": { + "name": "test-org", + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + expectedOrganizationRequest := `{ + "query": "\n\t\t\tquery($organizationName: String!, $organizationVcs: VCSType!) {\n\t\t\t\torganization(\n\t\t\t\t\tname: $organizationName\n\t\t\t\t\tvcsType: $organizationVcs\n\t\t\t\t) {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}", + "variables": { + "organizationName": "test-org", + "organizationVcs": "BITBUCKET" + } + }` + + gqlNsResponse := `{ + "createNamespace": { + "errors": [], + "namespace": { + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + } + }` + + expectedNsRequest := `{ + "query": "\n\t\t\tmutation($name: String!, $organizationId: UUID!) {\n\t\t\t\tcreateNamespace(\n\t\t\t\t\tname: $name,\n\t\t\t\t\torganizationId: $organizationId\n\t\t\t\t) {\n\t\t\t\t\tnamespace {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\terrors {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttype\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}", + "variables": { + "name": "foo-ns", + "organizationId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + appendPostHandler(testServer, token, MockRequestResponse{ + Status: http.StatusOK, + Request: expectedOrganizationRequest, + Response: gqlOrganizationResponse}) + appendPostHandler(testServer, token, MockRequestResponse{ + Status: http.StatusOK, + Request: expectedNsRequest, + Response: gqlNsResponse}) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Out).Should(gbytes.Say("Namespace created")) + Eventually(session).Should(gexec.Exit(0)) + }) + + It("prints all errors returned by the GraphQL API", func() { + By("setting up a mock server") + + gqlOrganizationResponse := `{ + "organization": { + "name": "test-org", + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + expectedOrganizationRequest := `{ + "query": "\n\t\t\tquery($organizationName: String!, $organizationVcs: VCSType!) {\n\t\t\t\torganization(\n\t\t\t\t\tname: $organizationName\n\t\t\t\t\tvcsType: $organizationVcs\n\t\t\t\t) {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}", + "variables": { + "organizationName": "test-org", + "organizationVcs": "BITBUCKET" + } + }` + + gqlResponse := `{ + "createNamespace": { + "errors": [ + {"message": "error1"}, + {"message": "error2"} + ], + "namespace": null + } + }` + + expectedRequestJson := `{ + "query": "\n\t\t\tmutation($name: String!, $organizationId: UUID!) {\n\t\t\t\tcreateNamespace(\n\t\t\t\t\tname: $name,\n\t\t\t\t\torganizationId: $organizationId\n\t\t\t\t) {\n\t\t\t\t\tnamespace {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\terrors {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttype\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}", + "variables": { + "name": "foo-ns", + "organizationId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + appendPostHandler(testServer, token, + MockRequestResponse{ + Status: http.StatusOK, + Request: expectedOrganizationRequest, + Response: gqlOrganizationResponse, + }) + appendPostHandler(testServer, token, + MockRequestResponse{ + Status: http.StatusOK, + Request: expectedRequestJson, + Response: gqlResponse, + }) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err).Should(gbytes.Say("Error: error1: error2")) + Eventually(session).ShouldNot(gexec.Exit(0)) + }) + }) + +}) diff --git a/cmd/orb.go b/cmd/orb.go index 2b2dac251..330c79110 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -17,9 +17,6 @@ import ( ) var orbVersion string -var orbID string -var organizationName string -var organizationVcs string func newOrbCommand() *cobra.Command { @@ -44,19 +41,15 @@ func newOrbCommand() *cobra.Command { } publishCommand := &cobra.Command{ - Use: "publish [orb.yml]", + Use: "publish / ", Short: "publish a version of an orb", RunE: publishOrb, - Args: cobra.MaximumNArgs(1), + Args: cobra.ExactArgs(2), } publishCommand.Flags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish (required)") - publishCommand.Flags().StringVarP(&orbID, "orb-id", "i", "", "id of orb to publish (required)") - - for _, flag := range [2]string{"orb-version", "orb-id"} { - if err := publishCommand.MarkFlagRequired(flag); err != nil { - panic(err) - } + if err := publishCommand.MarkFlagRequired("orb-version"); err != nil { + panic(err) } sourceCommand := &cobra.Command{ @@ -73,25 +66,6 @@ func newOrbCommand() *cobra.Command { Args: cobra.ExactArgs(1), } - createNamespace := &cobra.Command{ - Use: "create ", - Short: "create an orb namespace", - RunE: createOrbNamespace, - Args: cobra.ExactArgs(1), - } - - createNamespace.PersistentFlags().StringVar(&organizationName, "org-name", "", "organization name (required)") - if err := createNamespace.MarkPersistentFlagRequired("org-name"); err != nil { - panic(err) - } - createNamespace.PersistentFlags().StringVar(&organizationVcs, "vcs", "github", "organization vcs, e.g. 'github', 'bitbucket'") - - namespaceCommand := &cobra.Command{ - Use: "ns", - Short: "Operate on orb namespaces (create, etc.)", - } - namespaceCommand.AddCommand(createNamespace) - orbCommand := &cobra.Command{ Use: "orb", Short: "Operate on orbs", @@ -99,12 +73,9 @@ func newOrbCommand() *cobra.Command { orbCommand.AddCommand(listCommand) orbCommand.AddCommand(orbCreate) - orbCommand.AddCommand(validateCommand) orbCommand.AddCommand(expandCommand) orbCommand.AddCommand(publishCommand) - - orbCommand.AddCommand(namespaceCommand) orbCommand.AddCommand(sourceCommand) return orbCommand @@ -289,12 +260,8 @@ 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) + response, err := api.OrbPublish(ctx, Logger, args[0], args[1], orbVersion) if err != nil { return err @@ -332,24 +299,6 @@ func createOrb(cmd *cobra.Command, args []string) error { return nil } -func createOrbNamespace(cmd *cobra.Command, args []string) error { - var err error - ctx := context.Background() - - response, err := api.CreateNamespace(ctx, Logger, args[0], organizationName, strings.ToUpper(organizationVcs)) - - if err != nil { - return err - } - - if len(response.Errors) > 0 { - return response.ToError() - } - - Logger.Info("Namespace created") - return nil -} - func showSource(cmd *cobra.Command, args []string) error { orb := args[0] source, err := api.OrbSource(context.Background(), Logger, orb) diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 381ed9a14..c571412b3 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -332,9 +332,9 @@ var _ = Describe("Orb integration tests", func() { "orb", "publish", "-t", token, "-e", testServer.URL(), + "my/orb", orb.Path, "--orb-version", "0.0.1", - "--orb-id", "bb604b45-b6b0-4b81-ad80-796f15eddf87", ) }) @@ -348,7 +348,20 @@ var _ = Describe("Orb integration tests", func() { // assert write to test file successful Expect(err).ToNot(HaveOccurred()) - gqlResponse := `{ + gqlOrbIDResponse := `{ + "orb": { + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + expectedOrbIDRequest := `{ + "query": "query($name: String!) {\n\t\t\t orb(name: $name) {\n\t\t\t id\n\t\t\t }\n\t\t }", + "variables": { + "name": "my/orb" + } + }` + + gqlPublishResponse := `{ "publishOrb": { "errors": [], "orb": { @@ -357,7 +370,7 @@ var _ = Describe("Orb integration tests", func() { } }` - expectedRequestJson := `{ + expectedPublishRequest := `{ "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version\n\t\t\t) {\n\t\t\t\torb {\n\t\t\t\t\tversion\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t", "variables": { "config": "some orb", @@ -368,9 +381,12 @@ var _ = Describe("Orb integration tests", func() { appendPostHandler(testServer, token, MockRequestResponse{ Status: http.StatusOK, - Request: expectedRequestJson, - Response: gqlResponse, - }) + Request: expectedOrbIDRequest, + Response: gqlOrbIDResponse}) + appendPostHandler(testServer, token, MockRequestResponse{ + Status: http.StatusOK, + Request: expectedPublishRequest, + Response: gqlPublishResponse}) By("running the command") session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -385,17 +401,30 @@ var _ = Describe("Orb integration tests", func() { err := orb.write(`some orb`) Expect(err).ToNot(HaveOccurred()) - gqlResponse := `{ - "publishOrb": { + gqlOrbIDResponse := `{ + "orb": { + "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" + } + }` + + expectedOrbIDRequest := `{ + "query": "query($name: String!) {\n\t\t\t orb(name: $name) {\n\t\t\t id\n\t\t\t }\n\t\t }", + "variables": { + "name": "my/orb" + } + }` + + gqlPublishResponse := `{ + "publishOrb": { "errors": [ {"message": "error1"}, {"message": "error2"} ], "orb": null - } - }` + } + }` - expectedRequestJson := `{ + expectedPublishRequest := `{ "query": "\n\t\tmutation($config: String!, $orbId: UUID!, $version: String!) {\n\t\t\tpublishOrb(\n\t\t\t\torbId: $orbId,\n\t\t\t\torbYaml: $config,\n\t\t\t\tversion: $version\n\t\t\t) {\n\t\t\t\torb {\n\t\t\t\t\tversion\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t", "variables": { "config": "some orb", @@ -406,132 +435,12 @@ var _ = Describe("Orb integration tests", func() { appendPostHandler(testServer, token, MockRequestResponse{ Status: http.StatusOK, - Request: expectedRequestJson, - Response: gqlResponse, - }) - - By("running the command") - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err).Should(gbytes.Say("Error: error1: error2")) - Eventually(session).ShouldNot(gexec.Exit(0)) - - }) - }) - - Describe("when creating / reserving a namespace", func() { - BeforeEach(func() { - command = exec.Command(pathCLI, - "orb", "ns", "create", - "-t", token, - "-e", testServer.URL(), - "foo-ns", - "--org-name", "test-org", - "--vcs", "BITBUCKET", - ) - }) - - It("works with organizationName and organizationVcs", func() { - By("setting up a mock server") - - gqlOrganizationResponse := `{ - "organization": { - "name": "test-org", - "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }` - - expectedOrganizationRequest := `{ - "query": "\n\t\t\tquery($organizationName: String!, $organizationVcs: VCSType!) {\n\t\t\t\torganization(\n\t\t\t\t\tname: $organizationName\n\t\t\t\t\tvcsType: $organizationVcs\n\t\t\t\t) {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}", - "variables": { - "organizationName": "test-org", - "organizationVcs": "BITBUCKET" - } - }` - - gqlNsResponse := `{ - "createNamespace": { - "errors": [], - "namespace": { - "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - } - }` - - expectedNsRequest := `{ - "query": "\n\t\t\tmutation($name: String!, $organizationId: UUID!) {\n\t\t\t\tcreateNamespace(\n\t\t\t\t\tname: $name,\n\t\t\t\t\torganizationId: $organizationId\n\t\t\t\t) {\n\t\t\t\t\tnamespace {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\terrors {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttype\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}", - "variables": { - "name": "foo-ns", - "organizationId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }` - - appendPostHandler(testServer, token, MockRequestResponse{ - Status: http.StatusOK, - Request: expectedOrganizationRequest, - Response: gqlOrganizationResponse}) + Request: expectedOrbIDRequest, + Response: gqlOrbIDResponse}) appendPostHandler(testServer, token, MockRequestResponse{ Status: http.StatusOK, - Request: expectedNsRequest, - Response: gqlNsResponse}) - - By("running the command") - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Out).Should(gbytes.Say("Namespace created")) - Eventually(session).Should(gexec.Exit(0)) - }) - - It("prints all errors returned by the GraphQL API", func() { - By("setting up a mock server") - - gqlOrganizationResponse := `{ - "organization": { - "name": "test-org", - "id": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }` - - expectedOrganizationRequest := `{ - "query": "\n\t\t\tquery($organizationName: String!, $organizationVcs: VCSType!) {\n\t\t\t\torganization(\n\t\t\t\t\tname: $organizationName\n\t\t\t\t\tvcsType: $organizationVcs\n\t\t\t\t) {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t}", - "variables": { - "organizationName": "test-org", - "organizationVcs": "BITBUCKET" - } - }` - - gqlResponse := `{ - "createNamespace": { - "errors": [ - {"message": "error1"}, - {"message": "error2"} - ], - "namespace": null - } - }` - - expectedRequestJson := `{ - "query": "\n\t\t\tmutation($name: String!, $organizationId: UUID!) {\n\t\t\t\tcreateNamespace(\n\t\t\t\t\tname: $name,\n\t\t\t\t\torganizationId: $organizationId\n\t\t\t\t) {\n\t\t\t\t\tnamespace {\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t\t\terrors {\n\t\t\t\t\t\tmessage\n\t\t\t\t\t\ttype\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}", - "variables": { - "name": "foo-ns", - "organizationId": "bb604b45-b6b0-4b81-ad80-796f15eddf87" - } - }` - - appendPostHandler(testServer, token, - MockRequestResponse{ - Status: http.StatusOK, - Request: expectedOrganizationRequest, - Response: gqlOrganizationResponse, - }) - appendPostHandler(testServer, token, - MockRequestResponse{ - Status: http.StatusOK, - Request: expectedRequestJson, - Response: gqlResponse, - }) + Request: expectedPublishRequest, + Response: gqlPublishResponse}) By("running the command") session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) @@ -539,6 +448,7 @@ var _ = Describe("Orb integration tests", func() { Expect(err).ShouldNot(HaveOccurred()) Eventually(session.Err).Should(gbytes.Say("Error: error1: error2")) Eventually(session).ShouldNot(gexec.Exit(0)) + }) }) diff --git a/cmd/root.go b/cmd/root.go index 2c793d64b..498f2481b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -37,15 +37,15 @@ func MakeCommands() *cobra.Command { Long: `Use CircleCI from the command line.`, } - rootCmd.AddCommand(newDiagnosticCommand()) rootCmd.AddCommand(newQueryCommand()) - rootCmd.AddCommand(newCollapseCommand()) - rootCmd.AddCommand(newConfigureCommand()) rootCmd.AddCommand(newConfigCommand()) rootCmd.AddCommand(newOrbCommand()) rootCmd.AddCommand(newBuildCommand()) rootCmd.AddCommand(newVersionCommand()) + rootCmd.AddCommand(newDiagnosticCommand()) + rootCmd.AddCommand(newSetupCommand()) rootCmd.AddCommand(newUpdateCommand()) + rootCmd.AddCommand(newNamespaceCommand()) 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") @@ -75,8 +75,7 @@ func bindCobraFlagToViper(command *cobra.Command, flag string) { } func init() { - - cobra.OnInitialize(setup) + cobra.OnInitialize(prepare) configDir := path.Join(settings.UserHomeDir(), ".circleci") @@ -92,9 +91,8 @@ func init() { if err := viper.ReadInConfig(); err != nil { panic(err) } - } -func setup() { +func prepare() { Logger = logger.NewLogger(viper.GetBool("verbose")) } diff --git a/cmd/configure.go b/cmd/setup.go similarity index 82% rename from cmd/configure.go rename to cmd/setup.go index 8d21665b5..31bcf513b 100644 --- a/cmd/configure.go +++ b/cmd/setup.go @@ -3,29 +3,27 @@ package cmd import ( "strings" - "github.com/pkg/errors" - "github.com/spf13/viper" - "github.com/manifoldco/promptui" + "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var testing = false -func newConfigureCommand() *cobra.Command { - - configureCommand := &cobra.Command{ - Use: "configure", - Short: "Configure the tool with your credentials", - RunE: configure, +func newSetupCommand() *cobra.Command { + setupCommand := &cobra.Command{ + Use: "setup", + Short: "Setup the CLI with your credentials", + RunE: setup, } - configureCommand.Flags().BoolVar(&testing, "testing", false, "Enable test mode to bypass interactive UI.") - if err := configureCommand.Flags().MarkHidden("testing"); err != nil { + setupCommand.Flags().BoolVar(&testing, "testing", false, "Enable test mode to bypass interactive UI.") + if err := setupCommand.Flags().MarkHidden("testing"); err != nil { panic(err) } - return configureCommand + return setupCommand } // We can't properly run integration tests on code that calls PromptUI. @@ -93,7 +91,7 @@ func shouldAskForToken(token string, ui userInterface) bool { return ui.askUserToConfirm("A CircleCI token is already set. Do you want to change it") } -func configure(cmd *cobra.Command, args []string) error { +func setup(cmd *cobra.Command, args []string) error { token := viper.GetString("token") var ui userInterface = interactiveUI{} @@ -121,6 +119,6 @@ func configure(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to save config file") } - Logger.Info("Configuration has been saved.") + Logger.Info("Setup complete. Your configuration has been saved.") return nil } diff --git a/cmd/configure_test.go b/cmd/setup_test.go similarity index 89% rename from cmd/configure_test.go rename to cmd/setup_test.go index d324671f1..aa42e9050 100644 --- a/cmd/configure_test.go +++ b/cmd/setup_test.go @@ -13,7 +13,7 @@ import ( "github.com/onsi/gomega/gexec" ) -var _ = Describe("Configure", func() { +var _ = Describe("Setup", func() { var ( tempHome string command *exec.Cmd @@ -24,7 +24,7 @@ var _ = Describe("Configure", func() { tempHome, err = ioutil.TempDir("", "circleci-cli-test-") Expect(err).ToNot(HaveOccurred()) - command = exec.Command(pathCLI, "configure", "--testing") + command = exec.Command(pathCLI, "setup", "--testing") command.Env = append(os.Environ(), fmt.Sprintf("HOME=%s", tempHome), fmt.Sprintf("USERPROFILE=%s", tempHome), // windows @@ -66,7 +66,7 @@ var _ = Describe("Configure", func() { Eventually(session.Out).Should(gbytes.Say("API token has been set.")) Eventually(session.Out).Should(gbytes.Say("CircleCI API End Point")) Eventually(session.Out).Should(gbytes.Say("API endpoint has been set.")) - Eventually(session.Out).Should(gbytes.Say("Configuration has been saved.")) + Eventually(session.Out).Should(gbytes.Say("Setup complete. Your configuration has been saved.")) Eventually(session).Should(gexec.Exit(0)) }) }) @@ -89,7 +89,7 @@ token: fooBarBaz Eventually(session.Out).Should(gbytes.Say("API token has been set.")) Eventually(session.Out).Should(gbytes.Say("CircleCI API End Point")) Eventually(session.Out).Should(gbytes.Say("API endpoint has been set.")) - Eventually(session.Out).Should(gbytes.Say("Configuration has been saved.")) + Eventually(session.Out).Should(gbytes.Say("Setup complete. Your configuration has been saved.")) Eventually(session).Should(gexec.Exit(0)) }) })