From 1f847031b7580c0ce4068442cc7d230090b9193d Mon Sep 17 00:00:00 2001 From: davidmdm Date: Tue, 25 Jul 2023 16:48:11 -0400 Subject: [PATCH 1/4] wip --- cmd/policy/policy.go | 49 +++++++++++++++++++++++++++++++++++++++++++- go.mod | 5 ++++- go.sum | 2 -- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index d37f7e22c..abfb5bd4e 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/fs" + "net/url" "os" "path/filepath" "regexp" @@ -22,6 +23,7 @@ import ( "gopkg.in/yaml.v3" "github.com/CircleCI-Public/circleci-cli/api/policy" + "github.com/CircleCI-Public/circleci-cli/api/rest" "github.com/CircleCI-Public/circleci-cli/cmd/validator" "github.com/CircleCI-Public/circleci-cli/config" "github.com/CircleCI-Public/circleci-cli/settings" @@ -473,6 +475,7 @@ This group of commands allows the management of polices to be verified against b debug bool useJSON bool format string + ownerID string ) cmd := &cobra.Command{ @@ -487,9 +490,51 @@ This group of commands allows the management of polices to be verified against b } } + host := globalConfig.Host + if host == "https://circleci.com" { + host = "https://api.circleci.com" + } + + client := rest.NewFromConfig(host, globalConfig) + runnerOpts := tester.RunnerOptions{ Path: args[0], Include: include, + Compile: func(data []byte, pipelineValues map[string]any) ([]byte, error) { + parameters, _ := pipelineValues["parameters"].(map[string]any) + delete(pipelineValues, "parameters") + + req, err := client.NewRequest( + "POST", + &url.URL{Path: "compile-config-with-defaults"}, + config.CompileConfigRequest{ + ConfigYaml: string(data), + Options: config.Options{ + OwnerID: ownerID, + PipelineValues: pipelineValues, + PipelineParameters: parameters, + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("an error occurred creating the request: %w", err) + } + + var resp config.ConfigResponse + if _, err := client.DoRequest(req, &resp); err != nil { + return nil, fmt.Errorf("failed to get compilation response: %w", err) + } + + if len(resp.Errors) > 0 { + messages := make([]error, len(resp.Errors)) + for i := range resp.Errors { + messages[i] = errors.New(resp.Errors[i].Message) + } + return nil, errors.Join(messages...) + } + + return []byte(resp.OutputYaml), nil + }, } runner, err := tester.NewRunner(runnerOpts) @@ -531,8 +576,10 @@ This group of commands allows the management of polices to be verified against b cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print all tests instead of only failed tests") cmd.Flags().BoolVar(&debug, "debug", false, "print test debug context. Sets verbose to true") cmd.Flags().BoolVar(&useJSON, "json", false, "sprints json test results instead of standard output format") - _ = cmd.Flags().MarkDeprecated("json", "use --format=json to print json test results") cmd.Flags().StringVar(&format, "format", "", "select desired format between json or junit") + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + + _ = cmd.Flags().MarkDeprecated("json", "use --format=json to print json test results") return cmd }() diff --git a/go.mod b/go.mod index 23c2f1d2c..e3b84837b 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,9 @@ require ( ) // fix vulnerability: CVE-2020-15114 in etcd v3.3.10+incompatible -replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.24+incompatible +replace ( + github.com/CircleCI-Public/circle-policy-agent => ../circle-policy-agent + github.com/coreos/etcd => github.com/coreos/etcd v3.3.24+incompatible +) go 1.20 diff --git a/go.sum b/go.sum index 4dfdd5aa1..6e1141d21 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= -github.com/CircleCI-Public/circle-policy-agent v0.0.663 h1:v2DbMYrzcoO6x5KN8y7hxByXMdUjntm8eM5DGnXhKeg= -github.com/CircleCI-Public/circle-policy-agent v0.0.663/go.mod h1:72U4Q4OtvAGRGGo/GqlCCO0tARg1cSG9xwxWyz3ktQI= github.com/CircleCI-Public/circleci-config v0.0.0-20230609135034-182164ce950a h1:RqA4H9p77FsqV++HNNDBq8dJftYuJ+r+KdD9HAX28t4= github.com/CircleCI-Public/circleci-config v0.0.0-20230609135034-182164ce950a/go.mod h1:XZaQPj2ylXZaz5vW31dRdkUY/Ey8MdpbgrUHbHyzICY= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= From 11091c197e90a77eda78702b485c7d86fd0c45d9 Mon Sep 17 00:00:00 2001 From: davidmdm Date: Wed, 26 Jul 2023 17:39:18 -0400 Subject: [PATCH 2/4] update cpa --- cmd/policy/policy.go | 7 +------ cmd/policy/testdata/policy/test-expected-usage.txt | 9 +++++---- config/config.go | 5 ++--- config/config_test.go | 6 ++---- go.mod | 7 ++----- go.sum | 2 ++ 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index abfb5bd4e..101113c27 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -490,12 +490,7 @@ This group of commands allows the management of polices to be verified against b } } - host := globalConfig.Host - if host == "https://circleci.com" { - host = "https://api.circleci.com" - } - - client := rest.NewFromConfig(host, globalConfig) + client := rest.NewFromConfig(config.GetCompileHost(globalConfig.Host), globalConfig) runnerOpts := tester.RunnerOptions{ Path: args[0], diff --git a/cmd/policy/testdata/policy/test-expected-usage.txt b/cmd/policy/testdata/policy/test-expected-usage.txt index 3981864ae..89a4faed5 100644 --- a/cmd/policy/testdata/policy/test-expected-usage.txt +++ b/cmd/policy/testdata/policy/test-expected-usage.txt @@ -5,10 +5,11 @@ Examples: circleci policy test ./policies/... Flags: - --debug print test debug context. Sets verbose to true - --format string select desired format between json or junit - --run string select which tests to run based on regular expression - -v, --verbose print all tests instead of only failed tests + --debug print test debug context. Sets verbose to true + --format string select desired format between json or junit + --owner-id string the id of the policy's owner + --run string select which tests to run based on regular expression + -v, --verbose print all tests instead of only failed tests Global Flags: --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/config/config.go b/config/config.go index 1affc5672..caad447e5 100644 --- a/config/config.go +++ b/config/config.go @@ -31,9 +31,8 @@ type ConfigCompiler struct { } func New(cfg *settings.Config) *ConfigCompiler { - hostValue := getCompileHost(cfg.Host) + hostValue := GetCompileHost(cfg.Host) collaboratorsClient, err := collaborators.NewCollaboratorsRestClient(*cfg) - if err != nil { panic(err) } @@ -49,7 +48,7 @@ func New(cfg *settings.Config) *ConfigCompiler { return configCompiler } -func getCompileHost(cfgHost string) string { +func GetCompileHost(cfgHost string) string { if cfgHost != defaultHost { return cfgHost } else { diff --git a/config/config_test.go b/config/config_test.go index 036d042d1..84af6c2cc 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -33,12 +33,12 @@ func TestCompiler(t *testing.T) { t.Run("tests that we correctly get the config api host when the host is not the default one", func(t *testing.T) { // if the host isn't equal to `https://circleci.com` then this is likely a server instance and // wont have the api.X.com subdomain so we should instead just respect the host for config commands - host := getCompileHost("test") + host := GetCompileHost("test") assert.Equal(t, host, "test") // If the host passed in is the same as the defaultHost 'https://circleci.com' - then we know this is cloud // and as such should use the `api.circleci.com` subdomain - host = getCompileHost("https://circleci.com") + host = GetCompileHost("https://circleci.com") assert.Equal(t, host, "https://api.circleci.com") }) }) @@ -119,9 +119,7 @@ func TestCompiler(t *testing.T) { assert.Equal(t, "output", resp.OutputYaml) assert.Equal(t, "source", resp.SourceYaml) }) - }) - } func TestLoadYaml(t *testing.T) { diff --git a/go.mod b/go.mod index e3b84837b..c1ac245cc 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/CircleCI-Public/circleci-cli require ( github.com/AlecAivazis/survey/v2 v2.1.1 - github.com/CircleCI-Public/circle-policy-agent v0.0.663 + github.com/CircleCI-Public/circle-policy-agent v0.0.683 github.com/Masterminds/semver v1.4.2 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/blang/semver v3.5.1+incompatible @@ -119,9 +119,6 @@ require ( ) // fix vulnerability: CVE-2020-15114 in etcd v3.3.10+incompatible -replace ( - github.com/CircleCI-Public/circle-policy-agent => ../circle-policy-agent - github.com/coreos/etcd => github.com/coreos/etcd v3.3.24+incompatible -) +replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.24+incompatible go 1.20 diff --git a/go.sum b/go.sum index 6e1141d21..b5bcc5a28 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= +github.com/CircleCI-Public/circle-policy-agent v0.0.683 h1:EzZaLy9mUGl4dwDNWceBHeDb3X0KAAjV4eFOk3C7lts= +github.com/CircleCI-Public/circle-policy-agent v0.0.683/go.mod h1:72U4Q4OtvAGRGGo/GqlCCO0tARg1cSG9xwxWyz3ktQI= github.com/CircleCI-Public/circleci-config v0.0.0-20230609135034-182164ce950a h1:RqA4H9p77FsqV++HNNDBq8dJftYuJ+r+KdD9HAX28t4= github.com/CircleCI-Public/circleci-config v0.0.0-20230609135034-182164ce950a/go.mod h1:XZaQPj2ylXZaz5vW31dRdkUY/Ey8MdpbgrUHbHyzICY= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= From 71a1bdf5756728865ed07b65ae266c589b05cf54 Mon Sep 17 00:00:00 2001 From: davidmdm Date: Thu, 27 Jul 2023 10:11:42 -0400 Subject: [PATCH 3/4] add policy test --- api/policy/policy.go | 13 +++-- cmd/policy/policy.go | 10 ++-- cmd/policy/policy_test.go | 49 ++++++++++++++----- .../testdata/compile_policies/compile.rego | 13 +++++ .../compile_policies/compile_test.yaml | 25 ++++++++++ config/config.go | 2 +- 6 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 cmd/policy/testdata/compile_policies/compile.rego create mode 100644 cmd/policy/testdata/compile_policies/compile_test.yaml diff --git a/api/policy/policy.go b/api/policy/policy.go index 19babab9a..dbdc43f0f 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -326,7 +326,14 @@ func (c Client) MakeDecision(ownerID string, context string, req DecisionRequest // NewClient returns a new policy client that will use the provided settings.Config to automatically inject appropriate // Circle-Token authentication and other relevant CLI headers. func NewClient(baseURL string, config *settings.Config) *Client { - transport := config.HTTPClient.Transport + client := config.HTTPClient + if client == nil { + client = http.DefaultClient + } + + client = func(c http.Client) *http.Client { return &c }(*client) + + transport := client.Transport if transport == nil { transport = http.DefaultTransport } @@ -334,7 +341,7 @@ func NewClient(baseURL string, config *settings.Config) *Client { // Throttling the client so that it cannot make more than 10 concurrent requests at time sem := make(chan struct{}, 10) - config.HTTPClient.Transport = transportFunc(func(r *http.Request) (*http.Response, error) { + client.Transport = transportFunc(func(r *http.Request) (*http.Response, error) { // Acquiring semaphore to respect throttling sem <- struct{}{} @@ -355,7 +362,7 @@ func NewClient(baseURL string, config *settings.Config) *Client { return &Client{ serverUrl: strings.TrimSuffix(baseURL, "/"), - client: config.HTTPClient, + client: client, } } diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 101113c27..693bfb0c3 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -479,8 +479,9 @@ This group of commands allows the management of polices to be verified against b ) cmd := &cobra.Command{ - Use: "test [path]", - Short: "runs policy tests", + Use: "test [path]", + Short: "runs policy tests", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) (err error) { var include *regexp.Regexp if run != "" { @@ -490,8 +491,6 @@ This group of commands allows the management of polices to be verified against b } } - client := rest.NewFromConfig(config.GetCompileHost(globalConfig.Host), globalConfig) - runnerOpts := tester.RunnerOptions{ Path: args[0], Include: include, @@ -499,6 +498,9 @@ This group of commands allows the management of polices to be verified against b parameters, _ := pipelineValues["parameters"].(map[string]any) delete(pipelineValues, "parameters") + host := config.GetCompileHost(globalConfig.Host) + client := rest.NewFromConfig(host, globalConfig) + req, err := client.NewRequest( "POST", &url.URL{Path: "compile-config-with-defaults"}, diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index c92543ff6..39b414649 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -173,7 +173,7 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, stderr := makeCMD("") + cmd, stdout, stderr := makeCMD("", "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL, "--no-prompt")) @@ -266,7 +266,7 @@ func TestDiffPolicyBundle(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, stderr := makeCMD("") + cmd, stdout, stderr := makeCMD("", "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -382,7 +382,7 @@ func TestFetchPolicyBundle(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD("") + cmd, stdout, _ := makeCMD("", "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -567,7 +567,7 @@ func TestGetDecisionLogs(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD("") + cmd, stdout, _ := makeCMD("", "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -637,7 +637,7 @@ test: config err := json.NewDecoder(r.Body).Decode(&req) require.NoError(t, err) - //dummy compilation here (remove the _compiled_ key in compiled config, as compiled config can't have that at top-level key). + // dummy compilation here (remove the _compiled_ key in compiled config, as compiled config can't have that at top-level key). var yamlResp map[string]any err = yaml.Unmarshal([]byte(req.ConfigYaml), &yamlResp) require.NoError(t, err) @@ -927,7 +927,7 @@ test: config compilerServer := httptest.NewServer(tc.CompilerServerHandler) defer compilerServer.Close() - cmd, stdout, _ := makeCMD(compilerServer.URL) + cmd, stdout, _ := makeCMD(compilerServer.URL, "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -1118,7 +1118,7 @@ test: config compilerServer := httptest.NewServer(tc.CompilerServerHandler) defer compilerServer.Close() - cmd, stdout, _ := makeCMD(compilerServer.URL) + cmd, stdout, _ := makeCMD(compilerServer.URL, "testtoken") args := append(tc.Args, "--policy-base-url", svr.URL) @@ -1234,7 +1234,7 @@ func TestGetSetSettings(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD("") + cmd, stdout, _ := makeCMD("", "testtoken") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -1256,6 +1256,7 @@ const jsonDeprecationMessage = "Flag --json has been deprecated, use --format=js func TestTestRunner(t *testing.T) { cases := []struct { Name string + Path string Verbose bool Debug bool Run string @@ -1330,13 +1331,31 @@ func TestTestRunner(t *testing.T) { assert.Contains(t, s, "> + steps: + - run: it + workflows: + main: + jobs: + - test + decision: + status: PASS + enabled_rules: [enforce_small_jobs] diff --git a/config/config.go b/config/config.go index caad447e5..ba55404de 100644 --- a/config/config.go +++ b/config/config.go @@ -49,7 +49,7 @@ func New(cfg *settings.Config) *ConfigCompiler { } func GetCompileHost(cfgHost string) string { - if cfgHost != defaultHost { + if cfgHost != defaultHost && cfgHost != "" { return cfgHost } else { return defaultAPIHost From 6f0e0db5e393211a40f24bd5277ddd93130ce7bd Mon Sep 17 00:00:00 2001 From: davidmdm Date: Thu, 27 Jul 2023 11:06:26 -0400 Subject: [PATCH 4/4] added comment --- api/policy/policy.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/policy/policy.go b/api/policy/policy.go index dbdc43f0f..6a9cc4a8e 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -331,6 +331,8 @@ func NewClient(baseURL string, config *settings.Config) *Client { client = http.DefaultClient } + // Make sure to create a copy of the client so that any modifications we make to the transport + // doesn't affect the http.DefaultClient client = func(c http.Client) *http.Client { return &c }(*client) transport := client.Transport @@ -349,13 +351,13 @@ func NewClient(baseURL string, config *settings.Config) *Client { time.AfterFunc(time.Second, func() { <-sem }) if config.Token != "" { - r.Header.Add("circle-token", config.Token) + r.Header.Set("circle-token", config.Token) } - r.Header.Add("Accept", "application/json") - r.Header.Add("Content-Type", "application/json") - r.Header.Add("User-Agent", version.UserAgent()) + r.Header.Set("Accept", "application/json") + r.Header.Set("Content-Type", "application/json") + r.Header.Set("User-Agent", version.UserAgent()) if commandStr := header.GetCommandStr(); commandStr != "" { - r.Header.Add("Circleci-Cli-Command", commandStr) + r.Header.Set("Circleci-Cli-Command", commandStr) } return transport.RoundTrip(r) })