Skip to content

Commit

Permalink
Ext 283 remove legacy org slug (#717)
Browse files Browse the repository at this point in the history
* Org UUID support

* Made two methods

* Test for OrgId

* Comments
  • Loading branch information
corinnesollows authored Jun 6, 2022
1 parent c3ad28b commit e96ff59
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 21 deletions.
68 changes: 63 additions & 5 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,16 +513,14 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) {
return &response, nil
}

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

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

// GraphQL isn't forwards-compatible, so we are unusually selective here about
// passing only non-empty fields on to the API, to minimize user impact if the
// backend is out of date.
Expand All @@ -546,6 +544,7 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p

request := graphql.NewRequest(query)
request.Var("config", config)

if values != nil {
request.Var("pipelineValues", pipeline.PrepareForGraphQL(values))
}
Expand All @@ -556,17 +555,76 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p
}
request.Var("pipelineParametersJson", string(pipelineParameters))
}

if orgSlug != "" {
request.Var("orgSlug", orgSlug)
}

request.SetToken(cl.Token)

err = cl.Run(request, &response)

if err != nil {
return nil, errors.Wrap(err, "Unable to validate config")
}
if len(response.BuildConfig.ConfigResponse.Errors) > 0 {
return nil, &response.BuildConfig.ConfigResponse.Errors
}

return &response.BuildConfig.ConfigResponse, nil
}

// ConfigQuery calls the GQL API to validate and process config with the org id
func ConfigQuery(cl *graphql.Client, configPath string, orgId string, params pipeline.Parameters, values pipeline.Values) (*ConfigResponse, error) {
var response BuildConfigResponse
var query string
config, err := loadYaml(configPath)
if err != nil {
return nil, err
}
// GraphQL isn't forwards-compatible, so we are unusually selective here about
// passing only non-empty fields on to the API, to minimize user impact if the
// backend is out of date.
var fieldAddendums string
if orgId != "" {
fieldAddendums += ", orgId: $orgId"
}
if len(params) > 0 {
fieldAddendums += ", pipelineParametersJson: $pipelineParametersJson"
}
query = fmt.Sprintf(
`query ValidateConfig ($config: String!, $pipelineParametersJson: String, $pipelineValues: [StringKeyVal!], $orgId: UUID!) {
buildConfig(configYaml: $config, pipelineValues: $pipelineValues%s) {
valid,
errors { message },
sourceYaml,
outputYaml
}
}`,
fieldAddendums)

request := graphql.NewRequest(query)
request.Var("config", config)

if values != nil {
request.Var("pipelineValues", pipeline.PrepareForGraphQL(values))
}
if params != nil {
pipelineParameters, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("unable to serialize pipeline values: %s", err.Error())
}
request.Var("pipelineParametersJson", string(pipelineParameters))
}

if orgId != "" {
request.Var("orgId", orgId)
}
request.SetToken(cl.Token)

err = cl.Run(request, &response)
if err != nil {
return nil, errors.Wrap(err, "Unable to validate config")
}
if len(response.BuildConfig.ConfigResponse.Errors) > 0 {
return nil, &response.BuildConfig.ConfigResponse.Errors
}
Expand Down
1 change: 1 addition & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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")
buildCommand.Flags().String("org-id", "", "organization id, used when a config depends on private orbs belonging to that org")

return buildCommand
}
Expand Down
43 changes: 33 additions & 10 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"io/ioutil"
"strings"

"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
Expand Down Expand Up @@ -77,6 +78,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
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")
validateCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org")

processCommand := &cobra.Command{
Use: "process <path>",
Expand All @@ -93,6 +95,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
}
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")
processCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org")
processCommand.Flags().StringP("pipeline-parameters", "", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)")

migrateCommand := &cobra.Command{
Expand Down Expand Up @@ -121,6 +124,8 @@ 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, flags *pflag.FlagSet) error {
var err error
var response *api.ConfigResponse
path := local.DefaultConfigPath
// First, set the path to configPath set by --path flag for compatibility
if configPath != "" {
Expand All @@ -132,11 +137,19 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
path = opts.args[0]
}

orgSlug, _ := flags.GetString("org-slug")

response, err := api.ConfigQuery(opts.cl, path, orgSlug, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = api.ConfigQuery(opts.cl, path, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
response, err = api.ConfigQueryLegacy(opts.cl, path, orgSlug, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}

// check if a deprecated Linux VM image is being used
Expand All @@ -159,10 +172,10 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
}

func processConfig(opts configOptions, flags *pflag.FlagSet) error {
orgSlug, _ := flags.GetString("org-slug")
paramsYaml, _ := flags.GetString("pipeline-parameters")

var response *api.ConfigResponse
var params pipeline.Parameters
var err error

if len(paramsYaml) > 0 {
// The 'src' value can be a filepath, or a yaml string. If the file cannot be read sucessfully,
Expand All @@ -178,9 +191,19 @@ func processConfig(opts configOptions, flags *pflag.FlagSet) error {
}
}

response, err := api.ConfigQuery(opts.cl, opts.args[0], orgSlug, params, pipeline.LocalPipelineValues())
if err != nil {
return err
//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = api.ConfigQuery(opts.cl, opts.args[0], orgID, params, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
response, err = api.ConfigQueryLegacy(opts.cl, opts.args[0], orgSlug, params, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}

fmt.Print(response.OutputYaml)
Expand Down
62 changes: 62 additions & 0 deletions cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,68 @@ var _ = Describe("Config", func() {
})

Describe("validating configs with private orbs", func() {
config := "version: 2.1"
orgId := "bb604b45-b6b0-4b81-ad80-796f15eddf87"
var expReq string

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

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

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

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

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())
})
})

Describe("validating configs with private orbs Legacy", func() {
config := "version: 2.1"
orgSlug := "circleci"
var expReq string
Expand Down
25 changes: 19 additions & 6 deletions local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"regexp"
"strings"
"syscall"

"github.com/CircleCI-Public/circleci-cli/api"
Expand All @@ -22,13 +23,25 @@ var picardRepo = "circleci/picard"
const DefaultConfigPath = ".circleci/config.yml"

func Execute(flags *pflag.FlagSet, cfg *settings.Config) error {
processedArgs, configPath := buildAgentArguments(flags)
orgSlug, _ := flags.GetString("org-slug")
var err error
var configResponse *api.ConfigResponse
cl := graphql.NewClient(cfg.HTTPClient, cfg.Host, cfg.Endpoint, cfg.Token, cfg.Debug)
configResponse, err := api.ConfigQuery(cl, configPath, orgSlug, nil, pipeline.LocalPipelineValues())

if err != nil {
return err
processedArgs, configPath := buildAgentArguments(flags)

//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
configResponse, err = api.ConfigQuery(cl, configPath, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
} else {
orgSlug, _ := flags.GetString("org-slug")
configResponse, err = api.ConfigQueryLegacy(cl, configPath, orgSlug, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
}

if !configResponse.Valid {
Expand Down Expand Up @@ -118,7 +131,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 != "org-slug" && flag.Name != "config" && flag.Name != "debug" {
if flag.Name != "org-slug" && flag.Name != "config" && flag.Name != "debug" && flag.Name != "org-id" {
result = append(result, unparseFlag(flags, flag)...)
}
})
Expand Down

0 comments on commit e96ff59

Please sign in to comment.