Skip to content

Commit

Permalink
Merge pull request #499 from CircleCI-Public/rename-namespace
Browse files Browse the repository at this point in the history
[CIRCLE-29573] add rename namespace admin command
  • Loading branch information
circlecai authored Oct 23, 2020
2 parents 94b7e37 + da8d88e commit 836fa18
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 2 deletions.
57 changes: 57 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ type ImportNamespaceResponse struct {
}
}

type RenameNamespaceResponse struct {
RenameNamespace struct {
Namespace struct {
CreatedAt string
ID string
}

Errors GQLErrorsCollection
}
}

// GetOrganizationResponse type wraps the GQL response for fetching an organization and ID.
type GetOrganizationResponse struct {
Organization struct {
Expand Down Expand Up @@ -891,6 +902,52 @@ func NamespaceExists(cl *graphql.Client, namespace string) (bool, error) {
return false, nil
}

func renameNamespaceWithNsID(cl *graphql.Client, id, newName string) (*RenameNamespaceResponse, error) {
var response RenameNamespaceResponse

query := `
mutation($namespaceId: UUID!, $newName: String!){
renameNamespace(
namespaceId: $namespaceId,
newName: $newName
){
namespace {
id
}
errors {
message
type
}
}
}`

request := graphql.NewRequest(query)
request.SetToken(cl.Token)

request.Var("namespaceId", id)
request.Var("newName", newName)

err := cl.Run(request, &response)

if len(response.RenameNamespace.Errors) > 0 {
return nil, response.RenameNamespace.Errors
}

if err != nil {
return nil, err
}

return &response, nil
}

func RenameNamespace(cl *graphql.Client, oldName, newName string) (*RenameNamespaceResponse, error) {
getNamespaceResponse, err := GetNamespace(cl, oldName)
if err != nil {
return nil, err
}
return renameNamespaceWithNsID(cl, getNamespaceResponse.RegistryNamespace.ID, newName)
}

func createOrbWithNsID(cl *graphql.Client, name string, namespaceID string) (*CreateOrbResponse, error) {
var response CreateOrbResponse

Expand Down
39 changes: 39 additions & 0 deletions cmd/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,29 @@ Please note that at this time all namespaces created in the registry are world-r
}
createCmd.Flags().BoolVar(&opts.noPrompt, "no-prompt", false, "Disable prompt to bypass interactive UI.")

renameCmd := &cobra.Command{
Use: "rename <old-name> <new-name>",
Short: "(server admin only) rename a namespace",
PreRunE: func(_ *cobra.Command, args []string) error {
opts.args = args
opts.cl = graphql.NewClient(config.Host, config.Endpoint, config.Token, config.Debug)

return validateToken(opts.cfg)
},
RunE: func(_ *cobra.Command, _ []string) error {
return renameNamespace(opts)
},
Args: cobra.ExactArgs(2),
Annotations: make(map[string]string),
Hidden: true,
}
renameCmd.Flags().BoolVar(&opts.noPrompt, "no-prompt", false, "Disable prompt to bypass interactive UI.")

renameCmd.Annotations["<old-name>"] = "The current name of the namespace"
renameCmd.Annotations["<new-name>"] = "The new name you want to give the namespace"

namespaceCmd.AddCommand(createCmd)
namespaceCmd.AddCommand(renameCmd)

return namespaceCmd
}
Expand Down Expand Up @@ -120,3 +142,20 @@ To change the namespace, you will have to contact CircleCI customer support.

return nil
}

func renameNamespace(opts namespaceOptions) error {
oldName := opts.args[0]
newName := opts.args[1]

confirm := fmt.Sprintf("Are you sure you wish to rename the namespace `%s` to `%s`?", oldName, newName)
if opts.noPrompt || opts.tty.askUserToConfirm(confirm) {
_, err := api.RenameNamespace(opts.cl, oldName, newName)

if err != nil {
return err
}

fmt.Printf("Namespace `%s` renamed to `%s`. `%s` is an alias for `%s` so existing usages will continue to work, unless you delete the %s alias with `namespace delete-alias %s`", oldName, newName, oldName, newName, oldName, oldName)
}
return nil
}
83 changes: 81 additions & 2 deletions cmd/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var _ = Describe("Namespace integration tests", func() {
tempSettings.Close()
})

Context("skipping prompts", func() {
Context("create, skipping prompts", func() {
Describe("registering a namespace", func() {
BeforeEach(func() {
command = exec.Command(pathCLI,
Expand Down Expand Up @@ -218,7 +218,7 @@ var _ = Describe("Namespace integration tests", func() {
})
})

Context("with interactive prompts", func() {
Context("create, with interactive prompts", func() {
Describe("registering a namespace", func() {
BeforeEach(func() {
command = exec.Command(pathCLI,
Expand Down Expand Up @@ -426,4 +426,83 @@ Please note that any orbs you publish in this namespace are open orbs and are wo
})
})
})

Describe("renaming a namespace", func() {
var (
gqlGetNsResponse string
expectedGetNsRequest string
expectedRenameRequest string
)
BeforeEach(func () {
command = exec.Command(pathCLI,
"namespace", "rename",
"ns-0", "ns-1",
"--skip-update-check",
"--token", token,
"--host", tempSettings.TestServer.URL(),
"--no-prompt",
)
gqlGetNsResponse = `{
"errors": [],
"registryNamespace": {
"id": "bb604b45-b6b0-4b81-ad80-796f15eddf87"
}
}`
expectedGetNsRequest = `{
"query": "\n\t\t\t\tquery($name: String!) {\n\t\t\t\t\tregistryNamespace(\n\t\t\t\t\t\tname: $name\n\t\t\t\t\t){\n\t\t\t\t\t\tid\n\t\t\t\t\t}\n\t\t\t }",
"variables": {
"name": "ns-0"
}
}`
expectedRenameRequest = `{
"query": "\n\t\tmutation($namespaceId: UUID!, $newName: String!){\n\t\t\trenameNamespace(\n\t\t\t\tnamespaceId: $namespaceId,\n\t\t\t\tnewName: $newName\n\t\t\t){\n\t\t\t\tnamespace {\n\t\t\t\t\tid\n\t\t\t\t}\n\t\t\t\terrors {\n\t\t\t\t\tmessage\n\t\t\t\t\ttype\n\t\t\t\t}\n\t\t\t}\n\t\t}",
"variables": {"newName": "ns-1", "namespaceId": "bb604b45-b6b0-4b81-ad80-796f15eddf87"}
}`
})

It("works in the basic case", func () {
By("setting up a mock server")
gqlRenameResponse := `{"data":{"renameNamespace":{"namespace":{"id":"4e377fe3-330d-4e4c-af62-821850fe9595"},"errors":[]}}}`
tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status:http.StatusOK,
Request: expectedGetNsRequest,
Response: gqlGetNsResponse})
tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status:http.StatusOK,
Request: expectedRenameRequest,
Response: gqlRenameResponse})

By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Out, "5s").Should(gbytes.Say("`ns-0` renamed to `ns-1`"))
Eventually(session).Should(gexec.Exit(0))
})

It("returns an error when renaming a namespace fails", func() {
By("setting up a mock server")
gqlRenameResponse := `{
"renameNamespace": {
"errors": [
{"message": "error1"},
{"message": "error2"}
],
"namespace": null
}
}`
tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status:http.StatusOK,
Request: expectedGetNsRequest,
Response: gqlGetNsResponse})
tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status:http.StatusOK,
Request: expectedRenameRequest,
Response: gqlRenameResponse})
By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Err).Should(gbytes.Say("Error: error1\nerror2"))
Eventually(session).ShouldNot(gexec.Exit(0))
})
})
})

0 comments on commit 836fa18

Please sign in to comment.