Skip to content

Commit

Permalink
Merge pull request #792 from CircleCI-Public/PIPE-2315/new-branch-to-…
Browse files Browse the repository at this point in the history
…iteratively-test

Migrates away from API-Service and calls orb-service directly for config compilation and validation
  • Loading branch information
Elliot Forbes authored Oct 5, 2022
2 parents 97049b8 + ad724f0 commit a77d125
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 546 deletions.
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ orbs:
executors:
go:
docker:
- image: cimg/go:1.18
- image: cimg/go:1.19
environment:
CGO_ENABLED: 0
mac:
Expand Down Expand Up @@ -105,8 +105,8 @@ jobs:
- checkout
- run: |
brew update
brew install go@1.18
echo 'export PATH="/usr/local/opt/go@1.18/bin:$PATH"' >> ~/.bash_profile
brew install go@1.19
echo 'export PATH="/usr/local/opt/go@1.19/bin:$PATH"' >> ~/.bash_profile
- gomod
- run: make test
build:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM cimg/go:1.18.3
FROM cimg/go:1.19.1

ENV CIRCLECI_CLI_SKIP_UPDATE_CHECK true

Expand Down
120 changes: 0 additions & 120 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"strings"

"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/pipeline"
"github.com/CircleCI-Public/circleci-cli/references"
"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/Masterminds/semver"
Expand Down Expand Up @@ -513,125 +512,6 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) {
return &response, nil
}

// 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.
var fieldAddendums string
if orgSlug != "" {
fieldAddendums += ", orgSlug: $orgSlug"
}
if len(params) > 0 {
fieldAddendums += ", pipelineParametersJson: $pipelineParametersJson"
}
query = fmt.Sprintf(
`query ValidateConfig ($config: String!, $pipelineParametersJson: String, $pipelineValues: [StringKeyVal!], $orgSlug: String) {
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 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
}

return &response.BuildConfig.ConfigResponse, nil
}

// OrbQuery validated and processes an orb.
func OrbQuery(cl *graphql.Client, configPath string) (*ConfigResponse, error) {
var response OrbConfigResponse
Expand Down
38 changes: 35 additions & 3 deletions api/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

type Client struct {
baseURL *url.URL
apiURL *url.URL
circleToken string
client *http.Client
}
Expand All @@ -29,13 +30,14 @@ func New(host string, config *settings.Config) *Client {
endpoint += "/"
}

u, _ := url.Parse(host)

baseURL, _ := url.Parse(host)
apiURL, _ := url.Parse(config.ConfigAPIHost)
client := config.HTTPClient
client.Timeout = 10 * time.Second

return &Client{
baseURL: u.ResolveReference(&url.URL{Path: endpoint}),
apiURL: apiURL.ResolveReference(&url.URL{Path: endpoint}),
baseURL: baseURL.ResolveReference(&url.URL{Path: endpoint}),
circleToken: config.Token,
client: client,
}
Expand Down Expand Up @@ -70,6 +72,36 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req
return req, nil
}

// NewAPIRequest - similar to NewRequest except it uses the apiURL as the base URL.
func (c *Client) NewAPIRequest(method string, u *url.URL, payload interface{}) (req *http.Request, err error) {
var r io.Reader
if payload != nil {
buf := &bytes.Buffer{}
r = buf
err = json.NewEncoder(buf).Encode(payload)
if err != nil {
return nil, err
}
}

req, err = http.NewRequest(method, c.apiURL.ResolveReference(u).String(), r)
if err != nil {
return nil, err
}

req.Header.Set("Circle-Token", c.circleToken)
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", version.UserAgent())
commandStr := header.GetCommandStr()
if commandStr != "" {
req.Header.Set("Circleci-Cli-Command", commandStr)
}
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
return req, nil
}

func (c *Client) DoRequest(req *http.Request, resp interface{}) (statusCode int, err error) {
httpResp, err := c.client.Do(req)
if err != nil {
Expand Down
69 changes: 56 additions & 13 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package cmd
import (
"fmt"
"io/ioutil"
"net/url"
"strings"

"github.com/CircleCI-Public/circleci-cli/api"
"github.com/CircleCI-Public/circleci-cli/api/graphql"
"github.com/CircleCI-Public/circleci-cli/api/rest"
"github.com/CircleCI-Public/circleci-cli/config"
"github.com/CircleCI-Public/circleci-cli/filetree"
"github.com/CircleCI-Public/circleci-cli/local"
"github.com/CircleCI-Public/circleci-cli/pipeline"
Expand All @@ -20,7 +21,7 @@ import (

type configOptions struct {
cfg *settings.Config
cl *graphql.Client
rest *rest.Client
args []string
}

Expand Down Expand Up @@ -63,7 +64,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
Short: "Check that the config file is well formed.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
opts.rest = rest.New(config.Host, config)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return validateConfig(opts, cmd.Flags())
Expand All @@ -85,7 +86,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command {
Short: "Validate config and display expanded configuration.",
PreRun: func(cmd *cobra.Command, args []string) {
opts.args = args
opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
opts.rest = rest.New(config.Host, config)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return processConfig(opts, cmd.Flags())
Expand Down Expand Up @@ -125,7 +126,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, flags *pflag.FlagSet) error {
var err error
var response *api.ConfigResponse
var response *config.ConfigResponse
path := local.DefaultConfigPath
// First, set the path to configPath set by --path flag for compatibility
if configPath != "" {
Expand All @@ -138,15 +139,22 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
}

//if no orgId provided use org slug
orgID, _ := flags.GetString("org-id")
var orgID string
orgID, _ = flags.GetString("org-id")
if strings.TrimSpace(orgID) != "" {
response, err = api.ConfigQuery(opts.cl, path, orgID, nil, pipeline.LocalPipelineValues())
orgID, _ = flags.GetString("org-id")
response, err = config.ConfigQuery(opts.rest, 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())
orgs, err := GetOrgCollaborations(opts.rest)
if err != nil {
fmt.Println(err.Error())
}
orgID = GetOrgIdFromSlug(orgSlug, orgs)
response, err = config.ConfigQuery(opts.rest, path, orgID, nil, pipeline.LocalPipelineValues())
if err != nil {
return err
}
Expand All @@ -156,7 +164,7 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error {
// link here to blog post when available
// returns an error if a deprecated image is used
if !ignoreDeprecatedImages {
err := deprecatedImageCheck(response)
err := config.DeprecatedImageCheck(response)
if err != nil {
return err
}
Expand All @@ -173,7 +181,7 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error {

func processConfig(opts configOptions, flags *pflag.FlagSet) error {
paramsYaml, _ := flags.GetString("pipeline-parameters")
var response *api.ConfigResponse
var response *config.ConfigResponse
var params pipeline.Parameters
var err error

Expand All @@ -194,13 +202,18 @@ func processConfig(opts configOptions, flags *pflag.FlagSet) error {
//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())
response, err = config.ConfigQuery(opts.rest, 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())
orgs, err := GetOrgCollaborations(opts.rest)
if err != nil {
fmt.Println(err.Error())
}
orgID = GetOrgIdFromSlug(orgSlug, orgs)
response, err = config.ConfigQuery(opts.rest, opts.args[0], orgID, params, pipeline.LocalPipelineValues())
if err != nil {
return err
}
Expand All @@ -227,3 +240,33 @@ func packConfig(opts configOptions) error {
func migrateConfig(opts configOptions) error {
return proxy.Exec([]string{"config", "migrate"}, opts.args)
}

type CollaborationResult struct {
VcsTye string `json:"vcs_type"`
OrgSlug string `json:"slug"`
OrgName string `json:"name"`
OrgId string `json:"id"`
AvatarUrl string `json:"avatar_url"`
}

// GetOrgCollaborations - fetches all the collaborations for a given user.
func GetOrgCollaborations(client *rest.Client) ([]CollaborationResult, error) {
req, err := client.NewRequest("GET", &url.URL{Path: "me/collaborations"}, nil)
if err != nil {
return nil, err
}

var resp []CollaborationResult
_, err = client.DoRequest(req, &resp)
return resp, err
}

// GetOrgIdFromSlug - converts a slug into an orgID.
func GetOrgIdFromSlug(slug string, collaborations []CollaborationResult) string {
for _, v := range collaborations {
if v.OrgSlug == slug {
return v.OrgId
}
}
return ""
}
Loading

0 comments on commit a77d125

Please sign in to comment.