Skip to content

Commit

Permalink
Merge pull request #878 from CircleCI-Public/revert-873-revert-865-PI…
Browse files Browse the repository at this point in the history
…PE-2315/remove-api-service-from-config-compilation

PIPE-2315 - Removes API-Service from config compilation
  • Loading branch information
elliotforbes authored Mar 22, 2023
2 parents 1e8b8c7 + c4e7392 commit c3e90a1
Show file tree
Hide file tree
Showing 13 changed files with 610 additions and 466 deletions.
119 changes: 0 additions & 119 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,124 +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.SetToken(cl.Token)
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)
}

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.SetToken(cl.Token)
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)
}

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
12 changes: 9 additions & 3 deletions api/rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ func NewFromConfig(host string, config *settings.Config) *Client {
endpoint += "/"
}

u, _ := url.Parse(host)
baseURL, _ := url.Parse(host)

client := config.HTTPClient
client.Timeout = 10 * time.Second

return New(
u.ResolveReference(&url.URL{Path: endpoint}),
baseURL.ResolveReference(&url.URL{Path: endpoint}),
config.Token,
client,
)
Expand All @@ -65,6 +65,11 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req
return nil, err
}

c.enrichRequestHeaders(req, payload)
return req, nil
}

func (c *Client) enrichRequestHeaders(req *http.Request, payload interface{}) {
req.Header.Set("Circle-Token", c.circleToken)
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", version.UserAgent())
Expand All @@ -75,12 +80,12 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req
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 {
fmt.Printf("failed to make http request: %s\n", err.Error())
return 0, err
}
defer httpResp.Body.Close()
Expand All @@ -91,6 +96,7 @@ func (c *Client) DoRequest(req *http.Request, resp interface{}) (statusCode int,
}{}
err = json.NewDecoder(httpResp.Body).Decode(&httpError)
if err != nil {
fmt.Printf("failed to decode body: %s", err.Error())
return httpResp.StatusCode, err
}
return httpResp.StatusCode, &HTTPError{Code: httpResp.StatusCode, Message: httpError.Message}
Expand Down
115 changes: 88 additions & 27 deletions api/rest/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
"sync"
"testing"

"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
"github.com/stretchr/testify/assert"

"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/CircleCI-Public/circleci-cli/version"
Expand All @@ -30,29 +29,29 @@ func TestClient_DoRequest(t *testing.T) {
A: "aaa",
B: 123,
})
assert.NilError(t, err)
assert.Nil(t, err)

resp := make(map[string]interface{})
statusCode, err := c.DoRequest(r, &resp)
assert.NilError(t, err)
assert.Nil(t, err)
assert.Equal(t, statusCode, http.StatusCreated)
assert.Check(t, cmp.DeepEqual(resp, map[string]interface{}{
assert.Equal(t, resp, map[string]interface{}{
"key": "value",
}))
})
})

t.Run("Check request", func(t *testing.T) {
assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/my/endpoint"}))
assert.Check(t, cmp.Equal(fix.Method(), "PUT"))
assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{
assert.Equal(t, fix.URL(), url.URL{Path: "/api/v2/my/endpoint"})
assert.Equal(t, fix.Method(), "PUT")
assert.Equal(t, fix.Header(), http.Header{
"Accept-Encoding": {"gzip"},
"Accept": {"application/json"},
"Circle-Token": {"fake-token"},
"Content-Length": {"20"},
"Content-Type": {"application/json"},
"User-Agent": {version.UserAgent()},
}))
assert.Check(t, cmp.Equal(fix.Body(), `{"A":"aaa","B":123}`+"\n"))
})
assert.Equal(t, fix.Body(), `{"A":"aaa","B":123}`+"\n")
})
})

Expand All @@ -63,25 +62,25 @@ func TestClient_DoRequest(t *testing.T) {

t.Run("Check result", func(t *testing.T) {
r, err := c.NewRequest(http.MethodGet, &url.URL{Path: "my/error/endpoint"}, nil)
assert.NilError(t, err)
assert.Nil(t, err)

resp := make(map[string]interface{})
statusCode, err := c.DoRequest(r, &resp)
assert.Error(t, err, "the error message")
assert.Equal(t, statusCode, http.StatusBadRequest)
assert.Check(t, cmp.DeepEqual(resp, map[string]interface{}{}))
assert.Equal(t, resp, map[string]interface{}{})
})

t.Run("Check request", func(t *testing.T) {
assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/my/error/endpoint"}))
assert.Check(t, cmp.Equal(fix.Method(), http.MethodGet))
assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{
assert.Equal(t, fix.URL(), url.URL{Path: "/api/v2/my/error/endpoint"})
assert.Equal(t, fix.Method(), http.MethodGet)
assert.Equal(t, fix.Header(), http.Header{
"Accept-Encoding": {"gzip"},
"Accept": {"application/json"},
"Circle-Token": {"fake-token"},
"User-Agent": {version.UserAgent()},
}))
assert.Check(t, cmp.Equal(fix.Body(), ""))
})
assert.Equal(t, fix.Body(), "")
})
})

Expand All @@ -92,29 +91,91 @@ func TestClient_DoRequest(t *testing.T) {

t.Run("Check result", func(t *testing.T) {
r, err := c.NewRequest(http.MethodGet, &url.URL{Path: "path"}, nil)
assert.NilError(t, err)
assert.Nil(t, err)

resp := make(map[string]interface{})
statusCode, err := c.DoRequest(r, &resp)
assert.NilError(t, err)
assert.Nil(t, err)
assert.Equal(t, statusCode, http.StatusCreated)
assert.Check(t, cmp.DeepEqual(resp, map[string]interface{}{
assert.Equal(t, resp, map[string]interface{}{
"a": "abc",
"b": true,
}))
})
})

t.Run("Check request", func(t *testing.T) {
assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/path"}))
assert.Check(t, cmp.Equal(fix.Method(), http.MethodGet))
assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{
assert.Equal(t, fix.URL(), url.URL{Path: "/api/v2/path"})
assert.Equal(t, fix.Method(), http.MethodGet)
assert.Equal(t, fix.Header(), http.Header{
"Accept-Encoding": {"gzip"},
"Accept": {"application/json"},
"Circle-Token": {"fake-token"},
"User-Agent": {version.UserAgent()},
}))
assert.Check(t, cmp.Equal(fix.Body(), ""))
})
assert.Equal(t, fix.Body(), "")
})
})
}

func TestAPIRequest(t *testing.T) {
fix := &fixture{}
c, cleanup := fix.Run(http.StatusCreated, `{"key": "value"}`)
defer cleanup()

t.Run("test new api request sets the default headers", func(t *testing.T) {
req, err := c.NewRequest("GET", &url.URL{}, struct{}{})
assert.Nil(t, err)
assert.Equal(t, req.Header.Get("User-Agent"), "circleci-cli/0.0.0-dev+dirty-local-tree (source)")
assert.Equal(t, req.Header.Get("Circle-Token"), c.circleToken)
assert.Equal(t, req.Header.Get("Accept"), "application/json")
})

type testPayload struct {
Message string
}

t.Run("test new api request sets the default headers", func(t *testing.T) {
req, err := c.NewRequest("GET", &url.URL{}, testPayload{Message: "hello"})
assert.Nil(t, err)
assert.Equal(t, req.Header.Get("Circleci-Cli-Command"), "")
assert.Equal(t, req.Header.Get("Content-Type"), "application/json")
})

t.Run("test new api request doesn't set content-type with empty payload", func(t *testing.T) {
req, err := c.NewRequest("GET", &url.URL{}, nil)
assert.Nil(t, err)
assert.Equal(t, req.Header.Get("Circleci-Cli-Command"), "")
assert.Equal(t, req.Header.Get("Content-Type"), "")
})

type Options struct {
OwnerID string `json:"owner_id,omitempty"`
PipelineParameters map[string]interface{} `json:"pipeline_parameters,omitempty"`
PipelineValues map[string]string `json:"pipeline_values,omitempty"`
}

type CompileConfigRequest struct {
ConfigYaml string `json:"config_yaml"`
Options Options `json:"options"`
}

t.Run("config compile and validate payloads have expected shape", func(t *testing.T) {
req, err := c.NewRequest("GET", &url.URL{}, CompileConfigRequest{
ConfigYaml: "test-config",
Options: Options{
OwnerID: "1234",
PipelineValues: map[string]string{
"key": "val",
},
},
})
assert.Nil(t, err)
assert.Equal(t, req.Header.Get("Circleci-Cli-Command"), "")
assert.Equal(t, req.Header.Get("Content-Type"), "application/json")

reqBody, _ := io.ReadAll(req.Body)
assert.Contains(t, string(reqBody), `"config_yaml":"test-config"`)
assert.Contains(t, string(reqBody), `"owner_id":"1234"`)
})
}

Expand Down
Loading

0 comments on commit c3e90a1

Please sign in to comment.