Skip to content

Commit

Permalink
Merge pull request #548 from CircleCI-Public/use-org-slug-for-configs
Browse files Browse the repository at this point in the history
[CIRCLE-32051] CLI Commands now optionally accept --org-slug flags
  • Loading branch information
kelvinkfli authored Feb 3, 2021
2 parents e0dcdd7 + 3d15335 commit 350505f
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 25 deletions.
34 changes: 24 additions & 10 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,29 +513,43 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) {
}

// ConfigQuery calls the GQL API to validate and process config
func ConfigQuery(cl *graphql.Client, configPath string, pipelineValues pipeline.Values) (*ConfigResponse, error) {
func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, pipelineValues pipeline.Values) (*ConfigResponse, error) {
var response BuildConfigResponse
var query string

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

query := `
query ValidateConfig ($config: String!, $pipelineValues: [StringKeyVal!]) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`
if orgSlug != "" {
query = `query ValidateConfig ($config: String!, $pipelineValues: [StringKeyVal!], $orgSlug: String) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues, orgSlug: $orgSlug) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`
} else {
query = `query ValidateConfig ($config: String!, $pipelineValues: [StringKeyVal!]) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`
}

request := graphql.NewRequest(query)
request.Var("config", config)
if pipelineValues != nil {
request.Var("pipelineValues", pipeline.PrepareForGraphQL(pipelineValues))
}
if orgSlug != "" {
request.Var("orgSlug", orgSlug)
}
request.SetToken(cl.Token)

err = cl.Run(request, &response)
Expand Down
3 changes: 2 additions & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
)

func newLocalExecuteCommand(config *settings.Config) *cobra.Command {

buildCommand := &cobra.Command{
Use: "execute",
Short: "Run a job in a container on the local machine",
Expand All @@ -17,10 +16,12 @@ func newLocalExecuteCommand(config *settings.Config) *cobra.Command {
}

local.AddFlagsForDocumentation(buildCommand.Flags())
buildCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")

return buildCommand
}

// hidden command for backwards compatibility
func newBuildCommand(config *settings.Config) *cobra.Command {
cmd := newLocalExecuteCommand(config)
cmd.Hidden = true
Expand Down
23 changes: 15 additions & 8 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -61,8 +62,8 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
opts.args = args
opts.cl = graphql.NewClient(config.Host, config.Endpoint, config.Token, config.Debug)
},
RunE: func(_ *cobra.Command, _ []string) error {
return validateConfig(opts)
RunE: func(cmd *cobra.Command, _ []string) error {
return validateConfig(opts, cmd.Flags())
},
Args: cobra.MaximumNArgs(1),
Annotations: make(map[string]string),
Expand All @@ -72,6 +73,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
if err := validateCommand.PersistentFlags().MarkHidden("config"); err != nil {
panic(err)
}
validateCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")

processCommand := &cobra.Command{
Use: "process <path>",
Expand All @@ -80,13 +82,14 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
opts.args = args
opts.cl = graphql.NewClient(config.Host, config.Endpoint, config.Token, config.Debug)
},
RunE: func(_ *cobra.Command, _ []string) error {
return processConfig(opts)
RunE: func(cmd *cobra.Command, _ []string) error {
return processConfig(opts, cmd.Flags())
},
Args: cobra.ExactArgs(1),
Annotations: make(map[string]string),
}
processCommand.Annotations["<path>"] = configAnnotations["<path>"]
processCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org")

migrateCommand := &cobra.Command{
Use: "migrate",
Expand All @@ -113,7 +116,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
}

// The <path> arg is actually optional, in order to support compatibility with the --path flag.
func validateConfig(opts configOptions) error {
func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
path := local.DefaultConfigPath
// First, set the path to configPath set by --path flag for compatibility
if configPath != "" {
Expand All @@ -125,7 +128,9 @@ func validateConfig(opts configOptions) error {
path = opts.args[0]
}

_, err := api.ConfigQuery(opts.cl, path, pipeline.FabricatedValues())
orgSlug, _ := flags.GetString("org-slug")

_, err := api.ConfigQuery(opts.cl, path, orgSlug, pipeline.FabricatedValues())

if err != nil {
return err
Expand All @@ -140,8 +145,10 @@ func validateConfig(opts configOptions) error {
return nil
}

func processConfig(opts configOptions) error {
response, err := api.ConfigQuery(opts.cl, opts.args[0], pipeline.FabricatedValues())
func processConfig(opts configOptions, flags *pflag.FlagSet) error {
orgSlug, _ := flags.GetString("org-slug")

response, err := api.ConfigQuery(opts.cl, opts.args[0], orgSlug, pipeline.FabricatedValues())

if err != nil {
return err
Expand Down
163 changes: 162 additions & 1 deletion cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ package cmd_test

import (
"fmt"
"io"
"net/http"
"os/exec"
"path/filepath"
"time"

"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/clitest"
"github.com/CircleCI-Public/circleci-cli/pipeline"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
"gotest.tools/v3/golden"
)

var _ = Describe("Config", func() {

Describe("pack", func() {
var (
command *exec.Cmd
results []byte
tempSettings *clitest.TempSettings
token string = "testtoken"
)

BeforeEach(func() {
Expand Down Expand Up @@ -139,5 +145,160 @@ var _ = Describe("Config", func() {
Eventually(session).Should(clitest.ShouldFail())
})
})

Describe("validating configs", func() {
config := "version: 2.1"
var expReq string

BeforeEach(func() {
command = exec.Command(pathCLI,
"config", "validate",
"--skip-update-check",
"--token", token,
"--host", tempSettings.TestServer.URL(),
"-",
)

stdin, err := command.StdinPipe()
Expect(err).ToNot(HaveOccurred())
_, err = io.WriteString(stdin, config)
Expect(err).ToNot(HaveOccurred())
stdin.Close()

query := `query ValidateConfig ($config: String!, $pipelineValues: [StringKeyVal!]) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`

r := graphql.NewRequest(query)
r.Variables["config"] = config
r.Variables["pipelineValues"] = pipeline.PrepareForGraphQL(pipeline.FabricatedValues())

req, err := r.Encode()
Expect(err).ShouldNot(HaveOccurred())
expReq = req.String()
})

It("returns an error when validating a config", func() {
expResp := `{
"buildConfig": {
"errors": [
{"message": "error1"}
]
}
}`

tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status: http.StatusOK,
Request: expReq,
Response: expResp,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Err, time.Second*3).Should(gbytes.Say("Error: error1"))
Eventually(session).Should(clitest.ShouldFail())
})

It("returns successfully when validating a config", func() {
expResp := `{
"buildConfig": {}
}`

tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status: http.StatusOK,
Request: expReq,
Response: expResp,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Out, time.Second*3).Should(gbytes.Say("Config input is valid."))
Eventually(session).Should(gexec.Exit(0))
})
})

Describe("validating configs with private orbs", func() {
config := "version: 2.1"
orgSlug := "circleci"
var expReq string

BeforeEach(func() {
command = exec.Command(pathCLI,
"config", "validate",
"--skip-update-check",
"--token", token,
"--host", tempSettings.TestServer.URL(),
"--org-slug", orgSlug,
"-",
)

stdin, err := command.StdinPipe()
Expect(err).ToNot(HaveOccurred())
_, err = io.WriteString(stdin, config)
Expect(err).ToNot(HaveOccurred())
stdin.Close()

query := `query ValidateConfig ($config: String!, $pipelineValues: [StringKeyVal!], $orgSlug: String) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues, orgSlug: $orgSlug) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`

r := graphql.NewRequest(query)
r.Variables["config"] = config
r.Variables["pipelineValues"] = pipeline.PrepareForGraphQL(pipeline.FabricatedValues())
r.Variables["orgSlug"] = orgSlug

req, err := r.Encode()
Expect(err).ShouldNot(HaveOccurred())
expReq = req.String()
})

It("returns an error when validating a config with a private orb", func() {
expResp := `{
"buildConfig": {
"errors": [
{"message": "permission denied"}
]
}
}`

tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status: http.StatusOK,
Request: expReq,
Response: expResp,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Err, time.Second*3).Should(gbytes.Say("Error: permission denied"))
Eventually(session).Should(clitest.ShouldFail())
})

It("returns successfully when validating a config with private orbs", func() {
expResp := `{
"buildConfig": {}
}`

tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{
Status: http.StatusOK,
Request: expReq,
Response: expResp,
})

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).ShouldNot(HaveOccurred())
Eventually(session.Out, time.Second*3).Should(gbytes.Say("Config input is valid."))
Eventually(session).Should(gexec.Exit(0))
})
})
})
})
11 changes: 6 additions & 5 deletions local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ func UpdateBuildAgent() error {
}

func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {

processedArgs, configPath := buildAgentArguments(flags)
orgSlug, _ := flags.GetString("org-slug")
cl := graphql.NewClient(cfg.Host, cfg.Endpoint, cfg.Token, cfg.Debug)
configResponse, err := api.ConfigQuery(cl, configPath, pipeline.FabricatedValues())
configResponse, err := api.ConfigQuery(cl, configPath, orgSlug, pipeline.FabricatedValues())

if err != nil {
return err
Expand Down Expand Up @@ -102,7 +102,7 @@ func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {
}

// The `local execute` command proxies execution to the picard docker container,
// and ultimately to `build-agent`. We want to pass all arguments passed to the
// and ultimately to `build-agent`. We want to pass most arguments passed to the
// `local execute` command on to build-agent
// These options are here to retain a mock of the flags used by `build-agent`.
// They don't reflect the entire structure or available flags, only those which
Expand All @@ -123,7 +123,8 @@ func AddFlagsForDocumentation(flags *pflag.FlagSet) {

// Given the full set of flags that were passed to this command, return the path
// to the config file, and the list of supplied args _except_ for the `--config`
// or `-c` argument.
// or `-c` argument, and except for --debug and --org-slug which are consumed by
// this program.
// The `build-agent` can only deal with config version 2.0. In order to feed
// version 2.0 config to it, we need to process the supplied config file using the
// GraphQL API, and feed the result of that into `build-agent`. The first step of
Expand All @@ -135,7 +136,7 @@ func buildAgentArguments(flags *pflag.FlagSet) ([]string, string) {

// build a list of all supplied flags, that we will pass on to build-agent
flags.Visit(func(flag *pflag.Flag) {
if flag.Name != "config" && flag.Name != "debug" {
if flag.Name != "org-slug" && flag.Name != "config" && flag.Name != "debug" {
result = append(result, unparseFlag(flags, flag)...)
}
})
Expand Down

0 comments on commit 350505f

Please sign in to comment.