diff --git a/cmd/config.go b/cmd/config.go index f20b9add1..6c85de763 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -8,13 +8,14 @@ import ( "github.com/CircleCI-Public/circleci-config/labeling" "github.com/CircleCI-Public/circleci-config/labeling/codebase" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + "github.com/CircleCI-Public/circleci-cli/config" "github.com/CircleCI-Public/circleci-cli/filetree" "github.com/CircleCI-Public/circleci-cli/proxy" "github.com/CircleCI-Public/circleci-cli/settings" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" ) // Path to the config.yml file to operate on. @@ -96,13 +97,18 @@ func newConfigCommand(globalConfig *settings.Config) *cobra.Command { if len(args) == 1 { path = args[0] } - return compiler.ProcessConfig(config.ProcessConfigOpts{ + response, err := compiler.ProcessConfig(config.ProcessConfigOpts{ ConfigPath: path, OrgID: orgID, OrgSlug: orgSlug, PipelineParamsFilePath: pipelineParamsFilePath, VerboseOutput: verboseOutput, }) + if err != nil { + return err + } + fmt.Print(response.OutputYaml) + return nil }, Args: cobra.ExactArgs(1), Annotations: make(map[string]string), diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index f809a4571..d37f7e22c 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -23,12 +23,12 @@ import ( "github.com/CircleCI-Public/circleci-cli/api/policy" "github.com/CircleCI-Public/circleci-cli/cmd/validator" - + "github.com/CircleCI-Public/circleci-cli/config" "github.com/CircleCI-Public/circleci-cli/settings" ) // NewCommand creates the root policy command with all policy subcommands attached. -func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command { +func NewCommand(globalConfig *settings.Config, preRunE validator.Validator) *cobra.Command { cmd := &cobra.Command{ Use: "policy", PersistentPreRunE: preRunE, @@ -55,7 +55,7 @@ This group of commands allows the management of polices to be verified against b request.Policies = bundle - client := policy.NewClient(*policyBaseURL, config) + client := policy.NewClient(*policyBaseURL, globalConfig) if !noPrompt { request.DryRun = true @@ -116,7 +116,7 @@ This group of commands allows the management of polices to be verified against b return fmt.Errorf("failed to walk policy directory path: %w", err) } - diff, err := policy.NewClient(*policyBaseURL, config).CreatePolicyBundle(ownerID, context, policy.CreatePolicyBundleRequest{ + diff, err := policy.NewClient(*policyBaseURL, globalConfig).CreatePolicyBundle(ownerID, context, policy.CreatePolicyBundleRequest{ Policies: bundle, DryRun: true, }) @@ -147,7 +147,7 @@ This group of commands allows the management of polices to be verified against b if len(args) == 1 { policyName = args[0] } - policies, err := policy.NewClient(*policyBaseURL, config).FetchPolicyBundle(ownerID, context, policyName) + policies, err := policy.NewClient(*policyBaseURL, globalConfig).FetchPolicyBundle(ownerID, context, policyName) if err != nil { return fmt.Errorf("failed to fetch policy bundle: %v", err) } @@ -219,7 +219,7 @@ This group of commands allows the management of polices to be verified against b }() } - client := policy.NewClient(*policyBaseURL, config) + client := policy.NewClient(*policyBaseURL, globalConfig) output, err := func() (interface{}, error) { if decisionID != "" { @@ -259,14 +259,16 @@ This group of commands allows the management of polices to be verified against b decide := func() *cobra.Command { var ( - inputPath string - policyPath string - meta string - metaFile string - ownerID string - context string - strict bool - request policy.DecisionRequest + inputPath string + policyPath string + meta string + metaFile string + ownerID string + context string + strict bool + noCompile bool + pipelineParamsFilePath string + request policy.DecisionRequest ) cmd := &cobra.Command{ @@ -276,18 +278,33 @@ This group of commands allows the management of polices to be verified against b if len(args) == 1 { policyPath = args[0] } - if (policyPath == "" && ownerID == "") || (policyPath != "" && ownerID != "") { + if policyPath == "" && ownerID == "" { return fmt.Errorf("either [policy_file_or_dir_path] or --owner-id is required") } + if !noCompile && ownerID == "" { + return fmt.Errorf("--owner-id is required for compiling config (use --no-compile to evaluate policy against source config only)") + } + + metadata, err := readMetadata(meta, metaFile) + if err != nil { + return fmt.Errorf("failed to read metadata: %w", err) + } input, err := os.ReadFile(inputPath) if err != nil { return fmt.Errorf("failed to read input file: %w", err) } - metadata, err := readMetadata(meta, metaFile) - if err != nil { - return fmt.Errorf("failed to read metadata: %w", err) + if !noCompile { + compiler := config.New(globalConfig) + input, err = mergeCompiledConfig(compiler, config.ProcessConfigOpts{ + ConfigPath: inputPath, + OrgID: ownerID, + PipelineParamsFilePath: pipelineParamsFilePath, + }) + if err != nil { + return err + } } decision, err := func() (*cpa.Decision, error) { @@ -296,7 +313,7 @@ This group of commands allows the management of polices to be verified against b } request.Input = string(input) request.Metadata = metadata - return policy.NewClient(*policyBaseURL, config).MakeDecision(ownerID, context, request) + return policy.NewClient(*policyBaseURL, globalConfig).MakeDecision(ownerID, context, request) }() if err != nil { return fmt.Errorf("failed to make decision: %w", err) @@ -322,6 +339,8 @@ This group of commands allows the management of polices to be verified against b cmd.Flags().StringVar(&meta, "meta", "", "decision metadata (json string)") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") cmd.Flags().BoolVar(&strict, "strict", false, "return non-zero status code for decision resulting in HARD_FAIL") + cmd.Flags().BoolVar(&noCompile, "no-compile", false, "skip config compilation (evaluate policy against source config only)") + cmd.Flags().StringVar(&pipelineParamsFilePath, "pipeline-parameters", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)") if err := cmd.MarkFlagRequired("input"); err != nil { panic(err) @@ -331,15 +350,23 @@ This group of commands allows the management of polices to be verified against b }() eval := func() *cobra.Command { - var inputPath, meta, metaFile, query string + var ( + inputPath string + meta string + metaFile string + ownerID string + query string + noCompile bool + pipelineParamsFilePath string + ) cmd := &cobra.Command{ Short: "perform raw opa evaluation locally", Use: "eval ", RunE: func(cmd *cobra.Command, args []string) error { policyPath := args[0] - input, err := os.ReadFile(inputPath) - if err != nil { - return fmt.Errorf("failed to read input file: %w", err) + + if !noCompile && ownerID == "" { + return fmt.Errorf("--owner-id is required for compiling config (use --no-compile to evaluate policy against source config only)") } metadata, err := readMetadata(meta, metaFile) @@ -347,6 +374,23 @@ This group of commands allows the management of polices to be verified against b return fmt.Errorf("failed to read metadata: %w", err) } + input, err := os.ReadFile(inputPath) + if err != nil { + return fmt.Errorf("failed to read input file: %w", err) + } + + if !noCompile { + compiler := config.New(globalConfig) + input, err = mergeCompiledConfig(compiler, config.ProcessConfigOpts{ + ConfigPath: inputPath, + OrgID: ownerID, + PipelineParamsFilePath: pipelineParamsFilePath, + }) + if err != nil { + return err + } + } + decision, err := getPolicyEvaluationLocally(policyPath, input, metadata, query) if err != nil { return fmt.Errorf("failed to make decision: %w", err) @@ -362,10 +406,13 @@ This group of commands allows the management of polices to be verified against b Example: `circleci policy eval ./policies --input ./.circleci/config.yml`, } + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") cmd.Flags().StringVar(&meta, "meta", "", "decision metadata (json string)") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") cmd.Flags().StringVar(&query, "query", "data", "policy decision query") + cmd.Flags().BoolVar(&noCompile, "no-compile", false, "skip config compilation (evaluate policy against source config only)") + cmd.Flags().StringVar(&pipelineParamsFilePath, "pipeline-parameters", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)") if err := cmd.MarkFlagRequired("input"); err != nil { panic(err) @@ -386,7 +433,7 @@ This group of commands allows the management of polices to be verified against b Short: "get/set policy decision settings (To read settings: run command without any settings flags)", Use: "settings", RunE: func(cmd *cobra.Command, args []string) error { - client := policy.NewClient(*policyBaseURL, config) + client := policy.NewClient(*policyBaseURL, globalConfig) response, err := func() (interface{}, error) { if cmd.Flag("enabled").Changed { @@ -501,6 +548,25 @@ This group of commands allows the management of polices to be verified against b return cmd } +func mergeCompiledConfig(compiler *config.ConfigCompiler, processConfigOpts config.ProcessConfigOpts) ([]byte, error) { + var sourceConfigMap, compiledConfigMap map[string]any + var err error + + response, err := compiler.ProcessConfig(processConfigOpts) + if err != nil { + return nil, fmt.Errorf("failed to compile config: %w", err) + } + if err != yaml.Unmarshal([]byte(response.OutputYaml), &compiledConfigMap) { + return nil, fmt.Errorf("compiled config is not a valid yaml: %w", err) + } + err = yaml.Unmarshal([]byte(response.SourceYaml), &sourceConfigMap) + if err != nil { + return nil, fmt.Errorf("source config is not a valid yaml: %w", err) + } + sourceConfigMap["_compiled_"] = compiledConfigMap + return yaml.Marshal(sourceConfigMap) +} + func readMetadata(meta string, metaFile string) (map[string]interface{}, error) { var metadata map[string]interface{} if meta != "" && metaFile != "" { diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 0c833c241..c92543ff6 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -15,8 +15,11 @@ import ( "time" "github.com/spf13/cobra" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "github.com/CircleCI-Public/circleci-cli/config" "github.com/CircleCI-Public/circleci-cli/settings" ) @@ -25,7 +28,7 @@ var testdata embed.FS func testdataContent(t *testing.T, filePath string) string { data, err := testdata.ReadFile(path.Join(".", "testdata", filePath)) - assert.NilError(t, err) + assert.NoError(t, err) return string(data) } @@ -39,15 +42,15 @@ func TestPushPolicyWithPrompt(t *testing.T) { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), expectedURLs[requestCount]) - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{ + assert.Equal(t, "POST", r.Method) + assert.Equal(t, expectedURLs[requestCount], r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{ "policies": map[string]interface{}{ filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), }, - }) + }, body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte("{}")) requestCount++ @@ -73,21 +76,21 @@ func TestPushPolicyWithPrompt(t *testing.T) { done := make(chan struct{}) go func() { - assert.NilError(t, cmd.Execute()) + assert.NoError(t, cmd.Execute()) close(done) }() time.Sleep(50 * time.Millisecond) expectedMessage := "The following changes are going to be made: {}\n\nDo you wish to continue? (y/N) " - assert.Equal(t, buffer.String(), expectedMessage) + assert.Equal(t, expectedMessage, buffer.String()) _, err := pw.Write([]byte("y\n")) - assert.NilError(t, err) + assert.NoError(t, err) time.Sleep(50 * time.Millisecond) - assert.Equal(t, buffer.String()[len(expectedMessage):], "\nPolicy Bundle Pushed Successfully\n\ndiff: {}\n") + assert.Equal(t, "\nPolicy Bundle Pushed Successfully\n\ndiff: {}\n", buffer.String()[len(expectedMessage):]) <-done } @@ -126,12 +129,12 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { Args: []string{"push", "./testdata/test0/no-valid-policy-files", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{ + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-org/context/custom/policy-bundle", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{ "policies": map[string]interface{}{}, - }) + }, body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte("{}")) }, @@ -143,15 +146,15 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { Args: []string{"push", "./testdata/test0", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{ + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-org/context/custom/policy-bundle", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{ "policies": map[string]interface{}{ filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), }, - }) + }, body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte("{}")) @@ -170,7 +173,7 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, stderr := makeCMD() + cmd, stdout, stderr := makeCMD("") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL, "--no-prompt")) @@ -180,9 +183,9 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { return } - assert.NilError(t, err) - assert.Equal(t, stdout.String(), tc.ExpectedStdOut) - assert.Equal(t, stderr.String(), tc.ExpectedStdErr) + assert.NoError(t, err) + assert.Equal(t, tc.ExpectedStdOut, stdout.String()) + assert.Equal(t, tc.ExpectedStdErr, stderr.String()) }) } } @@ -221,12 +224,12 @@ func TestDiffPolicyBundle(t *testing.T) { Args: []string{"diff", "./testdata/test0/no-valid-policy-files", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{ + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{ "policies": map[string]interface{}{}, - }) + }, body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte("{}")) }, @@ -237,15 +240,15 @@ func TestDiffPolicyBundle(t *testing.T) { Args: []string{"diff", "./testdata/test0", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{ + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{ "policies": map[string]interface{}{ filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), }, - }) + }, body) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte("{}")) @@ -263,7 +266,7 @@ func TestDiffPolicyBundle(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, stderr := makeCMD() + cmd, stdout, stderr := makeCMD("") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -273,9 +276,9 @@ func TestDiffPolicyBundle(t *testing.T) { return } - assert.NilError(t, err) - assert.Equal(t, stdout.String(), tc.ExpectedStdOut) - assert.Equal(t, stderr.String(), tc.ExpectedStdErr) + assert.NoError(t, err) + assert.Equal(t, tc.ExpectedStdOut, stdout.String()) + assert.Equal(t, tc.ExpectedStdErr, stderr.String()) }) } } @@ -298,71 +301,74 @@ func TestFetchPolicyBundle(t *testing.T) { Args: []string{"fetch", "policyName", "--owner-id", "ownerID", "--context", "someContext"}, ExpectedErr: "failed to fetch policy bundle: unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/someContext/policy-bundle/policyName") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/someContext/policy-bundle/policyName", r.URL.String()) w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(`{"error": "Forbidden"}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, }, { Name: "successfully fetches single policy", Args: []string{"fetch", "my_policy", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/my_policy") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/my_policy", r.URL.String()) _, err := w.Write([]byte(`{ - "content": "package org\n\npolicy_name[\"my_policy\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "my_policy" - }`)) - assert.NilError(t, err) + "content": "package org\n\npolicy_name[\"my_policy\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "my_policy" + }`)) + assert.NoError(t, err) }, ExpectedOutput: `{ - "content": "package org\n\npolicy_name[\"my_policy\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "my_policy" -} + "content": "package org\n\npolicy_name[\"my_policy\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "my_policy" + } + `, }, { Name: "successfully fetches policy bundle", Args: []string{"fetch", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/", r.URL.String()) _, err := w.Write([]byte(`{ - "a": { - "content": "package org\n\npolicy_name[\"a\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "a" - }, - "b": { - "content": "package org\n\npolicy_name[\"b\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "b" - } -}`)) - assert.NilError(t, err) + "a": { + "content": "package org\n\npolicy_name[\"a\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "a" + }, + "b": { + "content": "package org\n\npolicy_name[\"b\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "b" + } + }`)) + + assert.NoError(t, err) }, ExpectedOutput: `{ - "a": { - "content": "package org\n\npolicy_name[\"a\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "a" - }, - "b": { - "content": "package org\n\npolicy_name[\"b\"] { true }", - "created_at": "2022-08-10T10:47:01.859756-04:00", - "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", - "name": "b" - } -} + "a": { + "content": "package org\n\npolicy_name[\"a\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "a" + }, + "b": { + "content": "package org\n\npolicy_name[\"b\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "b" + } + } + `, }, } @@ -376,19 +382,19 @@ func TestFetchPolicyBundle(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + cmd, stdout, _ := makeCMD("") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) err := cmd.Execute() if tc.ExpectedErr == "" { - assert.NilError(t, err) + assert.NoError(t, err) } else { assert.Error(t, err, tc.ExpectedErr) return } - assert.Equal(t, stdout.String(), tc.ExpectedOutput) + assert.JSONEq(t, stdout.String(), tc.ExpectedOutput) }) } } @@ -430,12 +436,12 @@ func TestGetDecisionLogs(t *testing.T) { Name: "no filter is set", Args: []string{"logs", "--owner-id", "ownerID"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision", r.URL.String()) _, err := w.Write([]byte("[]")) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: "[]\n", + ExpectedOutput: "[]", }, { Name: "all filters are set", @@ -444,23 +450,23 @@ func TestGetDecisionLogs(t *testing.T) { "--branch", "branchValue", "--project-id", "projectIDValue", }, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue&status=PASS") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue&status=PASS", r.URL.String()) _, err := w.Write([]byte("[]")) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: "[]\n", + ExpectedOutput: "[]", }, { Name: "gets error response", Args: []string{"logs", "--owner-id", "ownerID"}, ExpectedErr: "failed to get policy decision logs: unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision", r.URL.String()) w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(`{"error": "Forbidden"}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, }, { @@ -471,55 +477,56 @@ func TestGetDecisionLogs(t *testing.T) { return func(w http.ResponseWriter, r *http.Request) { defer func() { count++ }() - assert.Equal(t, r.Method, "GET") + assert.Equal(t, "GET", r.Method) if count == 0 { - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision", r.URL.String()) _, err := w.Write([]byte(` - [ - { - "created_at": "2022-08-11T09:20:40.674594-04:00", - "decision": { - "enabled_rules": [ - "branch_is_main" - ], - "status": "PASS" - }, - "metadata": {}, - "policies": [ - "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", - "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" - ], - "time_taken_ms": 4 - } - ]`), + [ + { + "created_at": "2022-08-11T09:20:40.674594-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" + }, + "metadata": {}, + "policies": [ + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 4 + } + ]`), ) - assert.NilError(t, err) + assert.NoError(t, err) } else if count == 1 { - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision?offset=1") + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision?offset=1", r.URL.String()) _, err := w.Write([]byte("[]")) - assert.NilError(t, err) + assert.NoError(t, err) } else { t.Fatal("did not expect more than two requests but received a third") } } }(), ExpectedOutput: `[ - { - "created_at": "2022-08-11T09:20:40.674594-04:00", - "decision": { - "enabled_rules": [ - "branch_is_main" - ], - "status": "PASS" - }, - "metadata": {}, - "policies": [ - "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", - "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" - ], - "time_taken_ms": 4 - } + { + "created_at": "2022-08-11T09:20:40.674594-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" + }, + "metadata": {}, + "policies": [ + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 4 + } + ] `, }, @@ -528,26 +535,26 @@ func TestGetDecisionLogs(t *testing.T) { Args: []string{"logs", "--owner-id", "ownerID", "decisionID"}, ServerHandler: func() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision/decisionID") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision/decisionID", r.URL.String()) _, err := w.Write([]byte("{}")) - assert.NilError(t, err) + assert.NoError(t, err) } }(), - ExpectedOutput: "{}\n", + ExpectedOutput: "{}", }, { Name: "successfully gets policy-bundle for given decision ID", Args: []string{"logs", "--owner-id", "ownerID", "decisionID", "--policy-bundle"}, ServerHandler: func() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision/decisionID/policy-bundle") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/config/decision/decisionID/policy-bundle", r.URL.String()) _, err := w.Write([]byte("{}")) - assert.NilError(t, err) + assert.NoError(t, err) } }(), - ExpectedOutput: "{}\n", + ExpectedOutput: "{}", }, } @@ -560,29 +567,30 @@ func TestGetDecisionLogs(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + cmd, stdout, _ := makeCMD("") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) err := cmd.Execute() if tc.ExpectedErr == "" { - assert.NilError(t, err) + assert.NoError(t, err) } else { assert.Error(t, err, tc.ExpectedErr) return } - assert.Equal(t, stdout.String(), tc.ExpectedOutput) + assert.JSONEq(t, stdout.String(), tc.ExpectedOutput) }) } } func TestMakeDecisionCommand(t *testing.T) { testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string + Name string + Args []string + ServerHandler http.HandlerFunc + CompilerServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string }{ { Name: "requires flags", @@ -590,54 +598,124 @@ func TestMakeDecisionCommand(t *testing.T) { ExpectedErr: `required flag(s) "input" not set`, }, { - Name: "sends expected request", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, + Name: "sends expected request, config compilation is disabled", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) + + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) + + _, _ = io.WriteString(w, `{"status":"PASS"}`) + }, + ExpectedOutput: `{"status":"PASS"}`, + }, + { + Name: "sends expected request, config compilation is enabled (source config has _compiled_ top level key)", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test4/config.yml"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&payload)) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + assert.Equal(t, map[string]interface{}{ + "input": `_compiled_: + test: config +test: config +`, + }, payload) _, _ = io.WriteString(w, `{"status":"PASS"}`) }, - ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + CompilerServerHandler: func(w http.ResponseWriter, r *http.Request) { + var req config.CompileConfigRequest + 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). + var yamlResp map[string]any + err = yaml.Unmarshal([]byte(req.ConfigYaml), &yamlResp) + require.NoError(t, err) + delete(yamlResp, "_compiled_") + compiledConfig, err := yaml.Marshal(yamlResp) + require.NoError(t, err) + + response := config.ConfigResponse{Valid: true, SourceYaml: req.ConfigYaml, OutputYaml: string(compiledConfig)} + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonResponse) + require.NoError(t, err) + }, + ExpectedOutput: `{"status":"PASS"}`, }, { - Name: "passes when decision status = HARD_FAIL AND --strict is OFF", + Name: "sends expected request, config compilation is enabled", Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&payload)) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + assert.Equal(t, map[string]interface{}{ + "input": `_compiled_: + test: config +test: config +`, + }, payload) + + _, _ = io.WriteString(w, `{"status":"PASS"}`) + }, + CompilerServerHandler: func(w http.ResponseWriter, r *http.Request) { + var req config.CompileConfigRequest + err := json.NewDecoder(r.Body).Decode(&req) + require.NoError(t, err) + + response := config.ConfigResponse{Valid: true, SourceYaml: req.ConfigYaml, OutputYaml: req.ConfigYaml} + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonResponse) + require.NoError(t, err) + }, + ExpectedOutput: `{"status":"PASS"}`, + }, + { + Name: "passes when decision status = HARD_FAIL AND --strict is OFF", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--no-compile"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) + + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) _, _ = io.WriteString(w, `{"status":"HARD_FAIL"}`) }, - ExpectedOutput: "{\n \"status\": \"HARD_FAIL\"\n}\n", + ExpectedOutput: `{"status":"HARD_FAIL"}`, }, { Name: "fails when decision status = HARD_FAIL AND --strict is ON", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--strict"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--strict", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") - - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) _, _ = io.WriteString(w, `{"status":"HARD_FAIL"}`) }, @@ -645,35 +723,29 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "passes when decision status = ERROR AND --strict is OFF", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") - - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) _, _ = io.WriteString(w, `{"status":"ERROR", "reason": "some reason"}`) }, - ExpectedOutput: "{\n \"status\": \"ERROR\",\n \"reason\": \"some reason\"\n}\n", + ExpectedOutput: `{"status":"ERROR", "reason": "some reason"}`, }, { Name: "fails when decision status = ERROR AND --strict is ON", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--strict"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--strict", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) - - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) _, _ = io.WriteString(w, `{"status":"ERROR", "reason": "some reason"}`) }, @@ -681,69 +753,52 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "sends expected request with context", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision") - - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/custom/decision", r.URL.Path) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) _, _ = io.WriteString(w, `{"status":"PASS"}`) }, - ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + ExpectedOutput: `{"status":"PASS"}`, }, { Name: "sends expected request with meta", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`, "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision") - - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/custom/decision", r.URL.Path) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - "metadata": map[string]interface{}{ - "project_id": "test-project-id", - "vcs": map[string]any{"branch": "main"}, - }, - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n", "metadata": {"project_id": "test-project-id", "vcs":{"branch": "main"}}}`) _, _ = io.WriteString(w, `{"status":"PASS"}`) }, - ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + ExpectedOutput: `{"status":"PASS"}`, }, { Name: "sends expected request with metafile", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--metafile", "./testdata/test1/meta.yml"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--metafile", "./testdata/test1/meta.yml", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision") - - var payload map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/custom/decision", r.URL.Path) - assert.DeepEqual(t, payload, map[string]interface{}{ - "input": "test: config\n", - "metadata": map[string]interface{}{ - "project_id": "test-project-id", - "vcs": map[string]any{"branch": "main"}, - }, - }) + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.JSONEq(t, string(payload), `{"input": "test: config\n", "metadata": {"project_id": "test-project-id", "vcs":{"branch": "main"}}}`) _, _ = io.WriteString(w, `{"status":"PASS"}`) }, - ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + ExpectedOutput: `{"status":"PASS"}`, }, { Name: "fails on unexpected status code", - Args: []string{"decide", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner"}, + Args: []string{"decide", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner", "--no-compile"}, ServerHandler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) _, _ = io.WriteString(w, `{"error":"oopsie!"}`) @@ -753,103 +808,106 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "fails if neither local-policy nor owner-id is provided", - Args: []string{"decide", "--input", "./testdata/test1/test.yml"}, - ExpectedErr: "either [policy_file_or_dir_path] or --owner-id is required", - }, - { - Name: "fails if both local-policy and owner-id are provided", - Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner"}, + Args: []string{"decide", "--input", "./testdata/test1/test.yml", "--no-compile"}, ExpectedErr: "either [policy_file_or_dir_path] or --owner-id is required", }, { Name: "fails for input file not found", - Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml", "--no-compile"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"decide", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + Args: []string{"decide", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml", "--no-compile"}, ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "fails if both meta and metafile are provided", - Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile"}, + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile", "--no-compile"}, ExpectedErr: "failed to read metadata: use either --meta or --metafile flag, but not both", }, { - Name: "successfully performs decision for policy FILE provided locally", - Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test0/config.yml"}, - ExpectedOutput: `{ - "status": "PASS", - "enabled_rules": [ - "branch_is_main" - ] -} -`, + Name: "fails if config compilation is enabled, but owner-id isn't provided", + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml"}, + ExpectedErr: "--owner-id is required for compiling config (use --no-compile to evaluate policy against source config only)", + }, + { + Name: "successfully performs decision for policy FILE provided locally", + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test0/config.yml", "--no-compile"}, + ExpectedOutput: `{"status": "PASS", "enabled_rules": ["branch_is_main"]}`, + }, + { + Name: "successfully performs decision for policy FILE provided locally, when config compilation is enabled", + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test0/config.yml", "--owner-id", "test-owner"}, + CompilerServerHandler: func(w http.ResponseWriter, r *http.Request) { + var req config.CompileConfigRequest + err := json.NewDecoder(r.Body).Decode(&req) + require.NoError(t, err) + + response := config.ConfigResponse{Valid: true, SourceYaml: req.ConfigYaml, OutputYaml: req.ConfigYaml} + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonResponse) + require.NoError(t, err) + }, + ExpectedOutput: `{"status": "PASS", "enabled_rules": ["branch_is_main"]}`, }, { Name: "successfully performs decision for policy FILE provided locally, passes when decision = HARD_FAIL and strict = OFF", - Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml"}, + Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml", "--no-compile"}, ExpectedOutput: `{ - "status": "HARD_FAIL", - "enabled_rules": [ - "always_hard_fails" - ], - "hard_failures": [ - { - "rule": "always_hard_fails", - "reason": "0 is not equals 1" - } - ] -} + "status": "HARD_FAIL", + "enabled_rules": [ + "always_hard_fails" + ], + "hard_failures": [ + { + "rule": "always_hard_fails", + "reason": "0 is not equals 1" + } + ] + } + `, }, { Name: "successfully performs decision for policy FILE provided locally, fails when decision = HARD_FAIL and strict = ON", - Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml", "--strict"}, + Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml", "--strict", "--no-compile"}, ExpectedErr: "policy decision status: HARD_FAIL", }, { Name: "successfully performs decision for policy FILE provided locally, passes when decision = ERROR and strict = OFF", - Args: []string{"decide", "./testdata/test3/runtime_error_policy.rego", "--input", "./testdata/test0/config.yml"}, + Args: []string{"decide", "./testdata/test3/runtime_error_policy.rego", "--input", "./testdata/test0/config.yml", "--no-compile"}, ExpectedOutput: `{ - "status": "ERROR", - "reason": "./testdata/test3/runtime_error_policy.rego:8: eval_conflict_error: complete rules must not produce multiple outputs" -} + "status": "ERROR", + "reason": "./testdata/test3/runtime_error_policy.rego:8: eval_conflict_error: complete rules must not produce multiple outputs" + } + `, }, { Name: "successfully performs decision for policy FILE provided locally, fails when decision = ERROR and strict = ON", - Args: []string{"decide", "./testdata/test3/runtime_error_policy.rego", "--input", "./testdata/test0/config.yml", "--strict"}, + Args: []string{"decide", "./testdata/test3/runtime_error_policy.rego", "--input", "./testdata/test0/config.yml", "--strict", "--no-compile"}, ExpectedErr: "policy decision status: ERROR", }, { Name: "successfully performs decision with meta for policy FILE provided locally", Args: []string{ "decide", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--meta", - `{"project_id": "test-project-id","vcs": {"branch": "main"}}`, "--input", "./testdata/test0/config.yml", + `{"project_id": "test-project-id","vcs": {"branch": "main"}}`, "--input", "./testdata/test0/config.yml", "--no-compile", }, - ExpectedOutput: `{ - "status": "PASS", - "enabled_rules": [ - "enabled" - ] -} -`, + ExpectedOutput: `{"status": "PASS", "enabled_rules": ["enabled"]}`, }, { Name: "successfully performs decision with metafile for policy FILE provided locally", Args: []string{ "decide", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", - "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", + "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", "--no-compile", }, - ExpectedOutput: `{ - "status": "PASS", - "enabled_rules": [ - "enabled" - ] -} -`, + ExpectedOutput: `{"status": "PASS", "enabled_rules": ["enabled"]}`, }, } @@ -862,53 +920,61 @@ func TestMakeDecisionCommand(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + if tc.CompilerServerHandler == nil { + tc.CompilerServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + compilerServer := httptest.NewServer(tc.CompilerServerHandler) + defer compilerServer.Close() + + cmd, stdout, _ := makeCMD(compilerServer.URL) cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) err := cmd.Execute() if tc.ExpectedErr == "" { - assert.NilError(t, err) + assert.NoError(t, err) } else { assert.ErrorContains(t, err, tc.ExpectedErr) return } - assert.Equal(t, stdout.String(), tc.ExpectedOutput) + assert.JSONEq(t, stdout.String(), tc.ExpectedOutput) }) } } func TestRawOPAEvaluationCommand(t *testing.T) { testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string + Name string + Args []string + ServerHandler http.HandlerFunc + CompilerServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string }{ { Name: "fails if local-policy is not provided", - Args: []string{"eval", "--input", "./testdata/test1/test.yml"}, + Args: []string{"eval", "--input", "./testdata/test1/test.yml", "--no-compile"}, ExpectedErr: `accepts 1 arg(s), received 0`, }, { Name: "fails if input is not provided", - Args: []string{"eval", "./testdata/test0/policy.rego"}, + Args: []string{"eval", "./testdata/test0/policy.rego", "--no-compile"}, ExpectedErr: `required flag(s) "input" not set`, }, { Name: "fails for input file not found", - Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml", "--no-compile"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"eval", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + Args: []string{"eval", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml", "--no-compile"}, ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "fails if both meta and metafile are provided", - Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile"}, + Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--meta", "{}", "--metafile", "somefile", "--no-compile"}, ExpectedErr: "failed to read metadata: use either --meta or --metafile flag, but not both", }, { @@ -916,24 +982,25 @@ func TestRawOPAEvaluationCommand(t *testing.T) { Args: []string{ "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`, - "--input", "./testdata/test0/config.yml", + "--input", "./testdata/test0/config.yml", "--no-compile", }, ExpectedOutput: `{ - "meta": { - "vcs": { - "branch": "main" - }, - "project_id": "test-project-id" - }, - "org": { - "enable_rule": [ - "enabled" - ], - "policy_name": [ - "meta_policy_test" - ] - } -} + "meta": { + "vcs": { + "branch": "main" + }, + "project_id": "test-project-id" + }, + "org": { + "enable_rule": [ + "enabled" + ], + "policy_name": [ + "meta_policy_test" + ] + } + } + `, }, { @@ -941,24 +1008,25 @@ func TestRawOPAEvaluationCommand(t *testing.T) { Args: []string{ "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", "./testdata/test1/meta.yml", - "--input", "./testdata/test0/config.yml", + "--input", "./testdata/test0/config.yml", "--no-compile", }, ExpectedOutput: `{ - "meta": { - "vcs": { - "branch": "main" - }, - "project_id": "test-project-id" - }, - "org": { - "enable_rule": [ - "enabled" - ], - "policy_name": [ - "meta_policy_test" - ] - } -} + "meta": { + "vcs": { + "branch": "main" + }, + "project_id": "test-project-id" + }, + "org": { + "enable_rule": [ + "enabled" + ], + "policy_name": [ + "meta_policy_test" + ] + } + } + `, }, { @@ -967,12 +1035,9 @@ func TestRawOPAEvaluationCommand(t *testing.T) { "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--meta", `{"project_id": "test-project-id","vcs": {"branch": "main"}}`, "--input", "./testdata/test0/config.yml", - "--query", "data.org.enable_rule", + "--query", "data.org.enable_rule", "--no-compile", }, - ExpectedOutput: `[ - "enabled" -] -`, + ExpectedOutput: `["enabled"]`, }, { Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metafile and query", @@ -980,12 +1045,60 @@ func TestRawOPAEvaluationCommand(t *testing.T) { "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", - "--query", "data.org.enable_rule", + "--query", "data.org.enable_rule", "--no-compile", }, - ExpectedOutput: `[ - "enabled" -] + ExpectedOutput: `["enabled"]`, + }, + { + Name: "sends expected request, config compilation is disabled", + Args: []string{"eval", "./testdata/test0/policy.rego", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--no-compile"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) + + payload, err := io.ReadAll(r.Body) + require.NoError(t, err) + + assert.JSONEq(t, string(payload), `{"input": "test: config\n"}`) + + _, _ = io.WriteString(w, `{"status":"PASS"}`) + }, + ExpectedOutput: `{"meta": null, "org": {"enable_rule": ["branch_is_main"], "policy_name": ["test"]}}`, + }, + { + Name: "sends expected request, config compilation is enabled", + Args: []string{"eval", "./testdata/test0/policy.rego", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "POST", r.Method) + assert.Equal(t, "/api/v1/owner/test-owner/context/config/decision", r.URL.Path) + + var payload map[string]interface{} + assert.NoError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.Equal(t, map[string]interface{}{ + "input": `_compiled_: + test: config +test: config `, + }, payload) + + _, _ = io.WriteString(w, `{"status":"PASS"}`) + }, + CompilerServerHandler: func(w http.ResponseWriter, r *http.Request) { + var req config.CompileConfigRequest + err := json.NewDecoder(r.Body).Decode(&req) + require.NoError(t, err) + + response := config.ConfigResponse{Valid: true, SourceYaml: req.ConfigYaml, OutputYaml: req.ConfigYaml} + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonResponse) + require.NoError(t, err) + }, + ExpectedOutput: `{"meta": null, "org": {"enable_rule": ["branch_is_main"], "policy_name": ["test"]}}`, }, } @@ -998,7 +1111,14 @@ func TestRawOPAEvaluationCommand(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + if tc.CompilerServerHandler == nil { + tc.CompilerServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + compilerServer := httptest.NewServer(tc.CompilerServerHandler) + defer compilerServer.Close() + + cmd, stdout, _ := makeCMD(compilerServer.URL) args := append(tc.Args, "--policy-base-url", svr.URL) @@ -1006,17 +1126,17 @@ func TestRawOPAEvaluationCommand(t *testing.T) { err := cmd.Execute() if tc.ExpectedErr == "" { - assert.NilError(t, err) + assert.NoError(t, err) } else { assert.ErrorContains(t, err, tc.ExpectedErr) return } var actual, expected any - assert.NilError(t, json.Unmarshal(stdout.Bytes(), &actual)) - assert.NilError(t, json.Unmarshal([]byte(tc.ExpectedOutput), &expected)) + assert.NoError(t, json.Unmarshal(stdout.Bytes(), &actual)) + assert.NoError(t, json.Unmarshal([]byte(tc.ExpectedOutput), &expected)) - assert.DeepEqual(t, actual, expected) + assert.Equal(t, expected, actual) }) } } @@ -1039,81 +1159,69 @@ func TestGetSetSettings(t *testing.T) { Args: []string{"settings", "--owner-id", "ownerID", "--context", "someContext"}, ExpectedErr: "failed to run settings : unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/someContext/decision/settings") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/ownerID/context/someContext/decision/settings", r.URL.String()) w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(`{"error": "Forbidden"}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, }, { Name: "successfully fetches settings", Args: []string{"settings", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings") + assert.Equal(t, "GET", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings", r.URL.String()) w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(`{"enabled": true}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: `{ - "enabled": true -} -`, + ExpectedOutput: `{"enabled": true}`, }, { Name: "successfully sets settings (--enabled)", Args: []string{"settings", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f", "--enabled"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{"enabled": true}) + assert.Equal(t, "PATCH", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{"enabled": true}, body) w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(`{"enabled": true}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: `{ - "enabled": true -} -`, + ExpectedOutput: `{"enabled": true}`, }, { Name: "successfully sets settings (--enabled=true)", Args: []string{"settings", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f", "--enabled=true"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{"enabled": true}) + assert.Equal(t, "PATCH", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{"enabled": true}, body) w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(`{"enabled": true}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: `{ - "enabled": true -} -`, + ExpectedOutput: `{"enabled": true}`, }, { Name: "successfully sets settings (--enabled=false)", Args: []string{"settings", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f", "--enabled=false"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings") - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.DeepEqual(t, body, map[string]interface{}{"enabled": false}) + assert.Equal(t, "PATCH", r.Method) + assert.Equal(t, "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/decision/settings", r.URL.String()) + assert.NoError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, map[string]interface{}{"enabled": false}, body) w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(`{"enabled": false}`)) - assert.NilError(t, err) + assert.NoError(t, err) }, - ExpectedOutput: `{ - "enabled": false -} -`, + ExpectedOutput: `{"enabled": false}`, }, } @@ -1126,19 +1234,19 @@ func TestGetSetSettings(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + cmd, stdout, _ := makeCMD("") cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) err := cmd.Execute() if tc.ExpectedErr == "" { - assert.NilError(t, err) + assert.NoError(t, err) } else { assert.Error(t, err, tc.ExpectedErr) return } - assert.Equal(t, stdout.String(), tc.ExpectedOutput) + assert.JSONEq(t, stdout.String(), tc.ExpectedOutput) }) } } @@ -1158,18 +1266,18 @@ func TestTestRunner(t *testing.T) { { Name: "default options", Expected: func(t *testing.T, s string) { - assert.Check(t, strings.Contains(s, "testdata/test_policies")) - assert.Check(t, strings.Contains(s, "2/2 tests passed")) - assert.Check(t, !strings.Contains(s, "test_feature"), "should not have verbose output") + assert.Contains(t, s, "testdata/test_policies") + assert.Contains(t, s, "2/2 tests passed") + assert.NotContains(t, s, "test_feature", "should not have verbose output") }, }, { Name: "verbose", Verbose: true, Expected: func(t *testing.T, s string) { - assert.Check(t, strings.Contains(s, "test_feature")) - assert.Check(t, strings.Contains(s, "test_main")) - assert.Check(t, strings.Contains(s, "2/2 tests passed")) + assert.Contains(t, s, "test_feature") + assert.Contains(t, s, "test_main") + assert.Contains(t, s, "2/2 tests passed") }, }, { @@ -1177,40 +1285,40 @@ func TestTestRunner(t *testing.T) { Verbose: true, Run: "test_main", Expected: func(t *testing.T, s string) { - assert.Check(t, strings.Contains(s, "test_main")) - assert.Check(t, !strings.Contains(s, "test_feature")) - assert.Check(t, strings.Contains(s, "1/1 tests passed")) + assert.Contains(t, s, "test_main") + assert.NotContains(t, s, "test_feature") + assert.Contains(t, s, "1/1 tests passed") }, }, { Name: "debug", Debug: true, Expected: func(t *testing.T, s string) { - assert.Check(t, strings.Contains(s, "---- Debug Test Context ----")) + assert.Contains(t, s, "---- Debug Test Context ----") }, }, { Name: "json", Json: true, Expected: func(t *testing.T, s string) { - assert.Check(t, strings.HasPrefix(s, jsonDeprecationMessage)) - assert.Check(t, s[len(jsonDeprecationMessage)] == '[') - assert.Check(t, s[len(s)-2] == ']') + assert.True(t, strings.HasPrefix(s, jsonDeprecationMessage)) + assert.True(t, s[len(jsonDeprecationMessage)] == '[') + assert.True(t, s[len(s)-2] == ']') }, }, { Name: "format:json", Format: "json", Expected: func(t *testing.T, s string) { - assert.Check(t, s[0] == '[') - assert.Check(t, s[len(s)-2] == ']') + assert.True(t, s[0] == '[') + assert.True(t, s[len(s)-2] == ']') }, }, { Name: "format:junit", Format: "junit", Expected: func(t *testing.T, s string) { - assert.Check(t, strings.Contains(s, " 0 { // The 'src' value can be a filepath, or a yaml string. If the file cannot be read successfully, @@ -79,7 +77,7 @@ func (c *ConfigCompiler) ProcessConfig(opts ProcessConfigOpts) error { err = yaml.Unmarshal(raw, ¶ms) if err != nil { - return fmt.Errorf("invalid 'pipeline-parameters' provided: %s", err.Error()) + return nil, fmt.Errorf("invalid 'pipeline-parameters' provided: %s", err.Error()) } } @@ -92,7 +90,7 @@ func (c *ConfigCompiler) ProcessConfig(opts ProcessConfigOpts) error { orgID, err := c.getOrgID(opts.OrgID, opts.OrgSlug) if err != nil { - return fmt.Errorf("failed to get the appropriate org-id: %s", err.Error()) + return nil, fmt.Errorf("failed to get the appropriate org-id: %s", err.Error()) } response, err = c.ConfigQuery( @@ -102,16 +100,15 @@ func (c *ConfigCompiler) ProcessConfig(opts ProcessConfigOpts) error { values, ) if err != nil { - return err + return nil, err } if !response.Valid { fmt.Println(response.Errors) - return errors.New("config is invalid") + return nil, errors.New("config is invalid") } - fmt.Print(response.OutputYaml) - return nil + return response, nil } type ValidateConfigOpts struct { diff --git a/go.mod b/go.mod index 380836956..e61e99699 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,6 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/alessio/shellescape v1.4.1 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/charmbracelet/bubbles v0.11.0 // indirect github.com/charmbracelet/bubbletea v0.21.0 // indirect diff --git a/go.sum b/go.sum index efca168b1..650b11dd5 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,7 @@ github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITg github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -54,18 +55,24 @@ github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY= github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= @@ -88,7 +95,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -100,16 +112,20 @@ github.com/erikgeiser/promptkit v0.7.0/go.mod h1:Jj9bhN+N8RbMjB1jthkr9A4ydmczZ1W github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -118,7 +134,9 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -148,8 +166,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -183,6 +203,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY= github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -201,15 +222,20 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -226,8 +252,10 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= @@ -267,7 +295,11 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a h1:YNh/SV+Z0p7kQDUE9Ux+46ruTucvQP43XB06DfZa8Es= @@ -276,6 +308,7 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= @@ -283,6 +316,7 @@ github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= @@ -326,6 +360,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -631,6 +666,7 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=