Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circle-11123 publish orb #27

Merged
merged 10 commits into from
Jul 20, 2018
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 @@ -13,6 +13,8 @@ import (
)

var orbPath string
var orbVersion string
var orbID string

func newOrbCommand() *cobra.Command {

Expand All @@ -34,6 +36,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 @@ -47,6 +58,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 @@ -150,3 +163,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))

})
})
})
})