Skip to content

Commit

Permalink
Merge pull request #28 from CircleCI-Public/create-namespace
Browse files Browse the repository at this point in the history
[CIRCLE-12217] Create namespace
  • Loading branch information
Zachary Scott authored Jul 25, 2018
2 parents 7643b5e + 4356630 commit 41b2030
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 30 deletions.
100 changes: 100 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"strings"

"fmt"

"github.com/CircleCI-Public/circleci-cli/client"
"github.com/CircleCI-Public/circleci-cli/logger"
"github.com/pkg/errors"
Expand Down Expand Up @@ -42,6 +44,17 @@ type PublishOrbResponse struct {
GQLResponseErrors
}

// CreateNamespaceResponse type matches the data shape of the GQL response for
// creating a namespace
type CreateNamespaceResponse struct {
Namespace struct {
CreatedAt string
ID string
}

GQLResponseErrors
}

// ToError returns all GraphQL errors for a single response concatenated, or
// nil.
func (response GQLResponseErrors) ToError() error {
Expand Down Expand Up @@ -170,3 +183,90 @@ func OrbPublish(ctx context.Context, logger *logger.Logger,
}
return &response.PublishOrb.PublishOrbResponse, err
}

func createNamespaceByID(ctx context.Context, logger *logger.Logger, name string, ownerID string) (*CreateNamespaceResponse, error) {
var response struct {
CreateNamespace struct {
CreateNamespaceResponse
}
}

query := `
mutation($name: String!, $organizationId: UUID!) {
createNamespace(
name: $name,
organizationId: $organizationId
) {
namespace {
createdAt
id
}
errors {
message
type
}
}
}`

request := client.NewAuthorizedRequest(viper.GetString("token"), query)
request.Var("name", name)
request.Var("organizationId", ownerID)

graphQLclient := client.NewClient(viper.GetString("endpoint"), logger)

err := graphQLclient.Run(ctx, request, &response)

if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to create namespace %s for ownerId %s", name, ownerID))
}

return &response.CreateNamespace.CreateNamespaceResponse, err
}

func getOrganization(ctx context.Context, logger *logger.Logger, organizationName string, organizationVcs string) (string, error) {
var response struct {
Organization struct {
ID string
}
}

query := `
query($organizationName: String!, $organizationVcs: VCSType!) {
organization(
name: $organizationName
vcsType: $organizationVcs
) {
id
}
}`

request := client.NewAuthorizedRequest(viper.GetString("token"), query)
request.Var("organizationName", organizationName)
request.Var("organizationVcs", organizationVcs)

graphQLclient := client.NewClient(viper.GetString("endpoint"), logger)

err := graphQLclient.Run(ctx, request, &response)

if err != nil || response.Organization.ID == "" {
err = errors.Wrap(err, fmt.Sprintf("Unable to find organization %s of vcs-type %s", organizationName, organizationVcs))
}

return response.Organization.ID, err
}

// CreateNamespace creates (reserves) a namespace for an organization
func CreateNamespace(ctx context.Context, logger *logger.Logger, name string, organizationName string, organizationVcs string) (*CreateNamespaceResponse, error) {
organizationID, err := getOrganization(ctx, logger, organizationName, organizationVcs)
if err != nil {
return nil, err
}

namespace, err := createNamespaceByID(ctx, logger, name, organizationID)

if err != nil {
return nil, err
}

return namespace, err
}
48 changes: 28 additions & 20 deletions cmd/cmd_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,36 @@ func TestCmd(t *testing.T) {
RunSpecs(t, "Cmd Suite")
}

type MockRequestResponse struct {
Request string
Status int
Response string
}

// Test helpers

func appendPostHandler(server *ghttp.Server, authToken string, statusCode int, expectedRequestJson string, responseBody string) {
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/"),
ghttp.VerifyHeader(http.Header{
"Authorization": []string{authToken},
}),
ghttp.VerifyContentType("application/json; charset=utf-8"),
// From Gomegas ghttp.VerifyJson to avoid the
// VerifyContentType("application/json") check
// that fails with "application/json; charset=utf-8"
func(w http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
req.Body.Close()
Expect(err).ShouldNot(HaveOccurred())
Expect(body).Should(MatchJSON(expectedRequestJson), "JSON Mismatch")
},
ghttp.RespondWith(statusCode, `{ "data": `+responseBody+`}`),
),
)
func appendPostHandler(server *ghttp.Server, authToken string, combineHandlers ...MockRequestResponse) {
for _, handler := range combineHandlers {
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/"),
ghttp.VerifyHeader(http.Header{
"Authorization": []string{authToken},
}),
ghttp.VerifyContentType("application/json; charset=utf-8"),
// From Gomegas ghttp.VerifyJson to avoid the
// VerifyContentType("application/json") check
// that fails with "application/json; charset=utf-8"
func(w http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
req.Body.Close()
Expect(err).ShouldNot(HaveOccurred())
Expect(body).Should(MatchJSON(handler.Request), "JSON Mismatch")
},
ghttp.RespondWith(handler.Status, `{ "data": `+handler.Response+`}`),
),
)
}
}

type tmpFile struct {
Expand Down
24 changes: 20 additions & 4 deletions cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ var _ = Describe("Config", func() {
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse)
appendPostHandler(testServer, token, MockRequestResponse{
Status: http.StatusOK,
Request: expectedRequestJson,
Response: gqlResponse,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expand Down Expand Up @@ -103,7 +107,11 @@ var _ = Describe("Config", func() {
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse)
appendPostHandler(testServer, token, MockRequestResponse{
Status: http.StatusOK,
Request: expectedRequestJson,
Response: gqlResponse,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expand Down Expand Up @@ -149,7 +157,11 @@ var _ = Describe("Config", func() {
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse)
appendPostHandler(testServer, token, MockRequestResponse{
Status: http.StatusOK,
Request: expectedRequestJson,
Response: gqlResponse,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expand Down Expand Up @@ -180,7 +192,11 @@ var _ = Describe("Config", func() {
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse)
appendPostHandler(testServer, token, MockRequestResponse{
Status: http.StatusOK,
Request: expectedRequestJson,
Response: gqlResponse,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expand Down
37 changes: 37 additions & 0 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"strings"

"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/client"
Expand All @@ -17,6 +18,8 @@ import (

var orbVersion string
var orbID string
var organizationName string
var organizationVcs string

func newOrbCommand() *cobra.Command {

Expand Down Expand Up @@ -49,6 +52,17 @@ func newOrbCommand() *cobra.Command {
orbPublishCommand.PersistentFlags().StringVarP(&orbVersion, "orb-version", "o", "", "version of orb to publish")
orbPublishCommand.PersistentFlags().StringVarP(&orbID, "orb-id", "i", "", "id of orb to publish")

orbCreateNamespace := &cobra.Command{
Use: "create",
Short: "create an orb namespace",
RunE: createOrbNamespace,
Args: cobra.ExactArgs(1),
}

namespaceCommand := &cobra.Command{
Use: "ns",
}

orbCommand := &cobra.Command{
Use: "orb",
Short: "Operate on orbs",
Expand All @@ -62,6 +76,11 @@ func newOrbCommand() *cobra.Command {

orbCommand.AddCommand(orbPublishCommand)

orbCreateNamespace.PersistentFlags().StringVar(&organizationName, "org-name", "", "organization name")
orbCreateNamespace.PersistentFlags().StringVar(&organizationVcs, "vcs", "github", "organization vcs, e.g. 'github', 'bitbucket'")
namespaceCommand.AddCommand(orbCreateNamespace)
orbCommand.AddCommand(namespaceCommand)

return orbCommand
}

Expand Down Expand Up @@ -241,3 +260,21 @@ func publishOrb(cmd *cobra.Command, args []string) error {
Logger.Info("Orb published")
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
}
Loading

0 comments on commit 41b2030

Please sign in to comment.