Skip to content

Commit

Permalink
Merge pull request #27 from CircleCI-Public/11123-publish-orb
Browse files Browse the repository at this point in the history
Circle-11123 publish orb
  • Loading branch information
marcomorain authored Jul 20, 2018
2 parents 143611a + 642314a commit 6d28439
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 18 deletions.
71 changes: 67 additions & 4 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import (
"github.com/spf13/viper"
)

// GQLResponseErrors is a slice of errors returned by the GraphQL server. Each
// error message is a key-value pair with the structure "Message: string"
type GQLResponseErrors struct {
Errors []struct {
Message string
}
}

// ConfigResponse is a structure that matches the result of the GQL
// query, so that we can use mapstructure to convert from
// nested maps to a strongly typed struct.
Expand All @@ -19,13 +27,23 @@ type ConfigResponse struct {
SourceYaml string
OutputYaml string

Errors []struct {
Message string
GQLResponseErrors
}

// The PublishOrbResponse type matches the data shape of the GQL response for
// publishing an orb.
type PublishOrbResponse struct {
Orb struct {
CreatedAt string
Version string
}

GQLResponseErrors
}

// ToError returns an error created from any error messages, or nil.
func (response ConfigResponse) ToError() error {
// ToError returns all GraphQL errors for a single response concatenated, or
// nil.
func (response GQLResponseErrors) ToError() error {
messages := []string{}

for i := range response.Errors {
Expand Down Expand Up @@ -101,3 +119,48 @@ 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) {
var response struct {
PublishOrb struct {
PublishOrbResponse
}
}

config, err := loadYaml(configPath)
if err != nil {
return nil, err
}

query := `
mutation($config: String!, $orbId: UUID!, $version: String!) {
publishOrb(
orbId: $orbId,
orbYaml: $config,
version: $version
) {
orb {
version
createdAt
}
errors { message }
}
}
`

request := client.NewAuthorizedRequest(viper.GetString("token"), query)
request.Var("config", config)
request.Var("orbId", orbID)
request.Var("version", orbVersion)

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

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

if err != nil {
err = errors.Wrap(err, "Unable to publish orb")
}
return &response.PublishOrb.PublishOrbResponse, err
}
30 changes: 30 additions & 0 deletions cmd/orb.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
)

var orbPath string
var orbVersion string
var orbID string

func newOrbCommand() *cobra.Command {

Expand All @@ -37,6 +39,15 @@ func newOrbCommand() *cobra.Command {
RunE: expandOrb,
}

orbPublishCommand := &cobra.Command{
Use: "publish",
Short: "publish a version of an orb",
RunE: publishOrb,
}
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")

orbCommand := &cobra.Command{
Use: "orb",
Short: "Operate on orbs",
Expand All @@ -50,6 +61,8 @@ func newOrbCommand() *cobra.Command {
orbExpandCommand.PersistentFlags().StringVarP(&orbPath, "path", "p", "orb.yml", "path to orb file")
orbCommand.AddCommand(orbExpandCommand)

orbCommand.AddCommand(orbPublishCommand)

return orbCommand
}

Expand Down Expand Up @@ -199,3 +212,20 @@ func expandOrb(cmd *cobra.Command, args []string) error {
Logger.Info(response.OutputYaml)
return nil
}

func publishOrb(cmd *cobra.Command, args []string) error {
ctx := context.Background()

response, err := api.OrbPublish(ctx, Logger, orbPath, orbVersion, orbID)

if err != nil {
return err
}

if len(response.Errors) > 0 {
return response.ToError()
}

Logger.Info("Orb published")
return nil
}
105 changes: 91 additions & 14 deletions cmd/orb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"github.com/onsi/gomega/ghttp"
)

var _ = Describe("Orb", func() {
Describe("with an api and orb.yml", func() {
var _ = Describe("Orb integration tests", func() {
Describe("CLI behavior with a stubbed api and an orb.yml provided", func() {
var (
testServer *ghttp.Server
orb tmpFile
token string = "testtoken"
command *exec.Cmd
)

BeforeEach(func() {
Expand All @@ -33,13 +35,7 @@ var _ = Describe("Orb", func() {
})

Describe("when validating orb", func() {
var (
token string
command *exec.Cmd
)

BeforeEach(func() {
token = "testtoken"
command = exec.Command(pathCLI,
"orb", "validate",
"-t", token,
Expand Down Expand Up @@ -112,13 +108,7 @@ var _ = Describe("Orb", func() {
})

Describe("when expanding orb", func() {
var (
token string
command *exec.Cmd
)

BeforeEach(func() {
token = "testtoken"
command = exec.Command(pathCLI,
"orb", "expand",
"-t", token,
Expand Down Expand Up @@ -191,5 +181,92 @@ var _ = Describe("Orb", func() {

})
})

Describe("when publishing an orb version", func() {
BeforeEach(func() {
command = exec.Command(pathCLI,
"orb", "publish",
"-t", token,
"-e", testServer.URL(),
"-p", orb.Path,
"--orb-version", "0.0.1",
"--orb-id", "bb604b45-b6b0-4b81-ad80-796f15eddf87",
)
})

It("works", func() {

// TODO: factor out common test setup into a top-level JustBeforeEach. Rely
// on BeforeEach in each block to specify server mocking.
By("setting up a mock server")
// write to test file
err := orb.write(`some orb`)
// assert write to test file successful
Expect(err).ToNot(HaveOccurred())

gqlResponse := `{
"publishOrb": {
"errors": [],
"orb": {
"createdAt": "2018-07-16T18:03:18.961Z",
"version": "0.0.1"
}
}
}`

expectedRequestJson := `{
"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\tcreatedAt\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t",
"variables": {
"config": "some orb",
"orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87",
"version": "0.0.1"
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, gqlResponse)

By("running the command")
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)

Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Out).Should(gbytes.Say("Orb published"))
Eventually(session).Should(gexec.Exit(0))
})

It("prints all errors returned by the GraphQL API", func() {
By("setting up a mock server")
err := orb.write(`some orb`)
Expect(err).ToNot(HaveOccurred())

gqlResponse := `{
"publishOrb": {
"errors": [
{"message": "error1"},
{"message": "error2"}
],
"orb": null
}
}`

expectedRequestJson := `{
"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\tcreatedAt\n\t\t\t\t}\n\t\t\t\terrors { message }\n\t\t\t}\n\t\t}\n\t",
"variables": {
"config": "some orb",
"orbId": "bb604b45-b6b0-4b81-ad80-796f15eddf87",
"version": "0.0.1"
}
}`

appendPostHandler(testServer, token, http.StatusOK, expectedRequestJson, 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))

})
})
})
})

0 comments on commit 6d28439

Please sign in to comment.