Skip to content

Commit 0e4a8d5

Browse files
Merge branch 'main' of github.com:kosli-dev/cli into install-script
2 parents 9e9c0c2 + eca8fdf commit 0e4a8d5

24 files changed

Lines changed: 297 additions & 141 deletions

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ logs_integration_test_server:
129129
follow_integration_test_server:
130130
@docker logs cli_kosli_server -f ${CONTAINER} 2>&1
131131

132+
enter_integration_test_server:
133+
@docker exec -it --workdir / cli_kosli_server bash
132134

133135
docker:
134136
@docker build -t kosli-cli .

cmd/kosli/archiveAttestationType_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ func (suite *ArchiveAttestationTypeCommandTestSuite) TestArchiveAttestationTypeC
3636
golden: "Custom attestation type archive-attestation-type was archived\n",
3737
},
3838
{
39-
wantError: true,
40-
name: "archiving non-existing custom attestation type fails",
41-
cmd: fmt.Sprintf(`archive attestation-type non-existing %s`, suite.defaultKosliArguments),
42-
golden: "Error: Custom attestation type 'non-existing' does not exist for org 'docs-cmd-test-user'. \n",
39+
wantError: true,
40+
name: "archiving non-existing custom attestation type fails",
41+
cmd: fmt.Sprintf(`archive attestation-type non-existing %s`, suite.defaultKosliArguments),
42+
goldenRegex: "^Error: Custom attestation type 'non-existing' does not exist for org 'docs-cmd-test-user'",
4343
},
4444
{
4545
wantError: true,

cmd/kosli/assertArtifact.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/url"
99

10+
"github.com/kosli-dev/cli/internal/output"
1011
"github.com/kosli-dev/cli/internal/requests"
1112
"github.com/spf13/cobra"
1213
)
@@ -45,6 +46,7 @@ type assertArtifactOptions struct {
4546
fingerprint string // This is calculated or provided by the user
4647
flowName string
4748
envName string
49+
output string
4850
}
4951

5052
func newAssertArtifactCmd(out io.Writer) *cobra.Command {
@@ -75,6 +77,8 @@ func newAssertArtifactCmd(out io.Writer) *cobra.Command {
7577
cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", fingerprintFlag)
7678
cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlag)
7779
cmd.Flags().StringVar(&o.envName, "environment", "", envNameFlag)
80+
cmd.Flags().StringVarP(&o.output, "output", "o", "table", outputFlag)
81+
7882
addFingerprintFlags(cmd, o.fingerprintOptions)
7983
addDryRunFlag(cmd)
8084

@@ -116,31 +120,92 @@ func (o *assertArtifactOptions) run(out io.Writer, args []string) error {
116120
return err
117121
}
118122

123+
return output.FormattedPrint(response.Body, o.output, out, 0,
124+
map[string]output.FormatOutputFunc{
125+
"table": printAssertAsTable,
126+
"json": output.PrintJson,
127+
})
128+
}
129+
130+
func printAssertAsTable(raw string, out io.Writer, page int) error {
119131
var evaluationResult map[string]interface{}
120-
err = json.Unmarshal([]byte(response.Body), &evaluationResult)
132+
err := json.Unmarshal([]byte(raw), &evaluationResult)
121133
if err != nil {
122134
return err
123135
}
124136

137+
flow, _ := evaluationResult["flow"].(string)
138+
trail, _ := evaluationResult["trail"].(string)
125139
scope := evaluationResult["scope"].(string)
140+
complianceStatus, _ := evaluationResult["compliance_status"].(map[string]interface{})
141+
attestationsStatuses, _ := complianceStatus["attestations_statuses"].([]interface{})
126142

127143
if evaluationResult["compliant"].(bool) {
128144
logger.Info("COMPLIANT")
129-
if scope == "flow" {
130-
logger.Info("See more details at %s", evaluationResult["html_url"].(string))
131-
}
132145
} else {
133-
if scope == "flow" {
134-
return fmt.Errorf("not compliant\nSee more details at %s", evaluationResult["html_url"].(string))
135-
} else {
136-
jsonData, err := json.MarshalIndent(evaluationResult["policy_evaluations"], "", " ")
137-
if err != nil {
138-
return fmt.Errorf("error marshalling evaluation result: %v", err)
146+
logger.Info("Error: NON-COMPLIANT")
147+
}
148+
logger.Info("Flow: %v\nTrail %v", flow, trail)
149+
logger.Info("%-32v %-30v %-15v %-10v", "Attestation-name", "type", "status", "compliant")
150+
151+
for _, item := range attestationsStatuses {
152+
attestation := item.(map[string]interface{})
153+
name := attestation["attestation_name"]
154+
attType := attestation["attestation_type"]
155+
status := attestation["status"]
156+
isCompliant, _ := attestation["is_compliant"].(bool)
157+
unexpected, _ := attestation["unexpected"].(bool)
158+
unexpectedStr := ""
159+
if unexpected {
160+
unexpectedStr = "unexpected"
161+
}
162+
163+
logger.Info(" %-32v %-30v %-15v %-10v %-10v", name, attType, status, isCompliant, unexpectedStr)
164+
}
165+
if scope == "environment" {
166+
logger.Info("%-32v %-30v", "Policy-name", "status")
167+
policyEvaluations := evaluationResult["policy_evaluations"].([]interface{})
168+
for _, item := range policyEvaluations {
169+
policyEvaluation := item.(map[string]interface{})
170+
policyName := policyEvaluation["policy_name"]
171+
policyStatus := policyEvaluation["status"]
172+
logger.Info(" %-32v %-30v", policyName, policyStatus)
173+
if policyStatus != "COMPLIANT" {
174+
ruleEvaluations := policyEvaluation["rule_evaluations"].([]interface{})
175+
var failures []string
176+
for _, item2 := range ruleEvaluations {
177+
ruleEvaluation := item2.(map[string]interface{})
178+
ignored := ruleEvaluation["ignored"].(bool)
179+
satisfied, _ := ruleEvaluation["satisfied"].(bool)
180+
if !ignored && !satisfied {
181+
rule := ruleEvaluation["rule"].(map[string]interface{})
182+
resolutions := ruleEvaluation["resolutions"].([]interface{})
183+
for _, item3 := range resolutions {
184+
resolution := item3.(map[string]interface{})
185+
resolutionType := resolution["type"].(string)
186+
ruleDefinition := rule["definition"].(map[string]interface{})
187+
attestationName := ruleDefinition["name"]
188+
attestationType := ruleDefinition["type"]
189+
switch resolutionType {
190+
case "legacy_flow":
191+
failures = append(failures, "artifact comes from a legacy flow and does not have the new attestations")
192+
case "missing_attestation":
193+
failures = append(failures, fmt.Sprintf("artifact is missing required '%v' (type: %v) attestation in trail", attestationName, attestationType))
194+
case "non_compliant_attestation":
195+
failures = append(failures, fmt.Sprintf("attestation '%v' is non-compliant in trail", attestationName))
196+
case "non_compliant_in_trail":
197+
failures = append(failures, "artifact is not compliant in trail")
198+
}
199+
}
200+
}
201+
}
202+
for _, fail := range failures {
203+
logger.Info(" %v", fail)
204+
}
139205
}
140-
return fmt.Errorf("not compliant for env [%s]: \n %v", o.envName,
141-
string(jsonData))
142206
}
143207
}
208+
logger.Info("\nSee more details at %s", evaluationResult["html_url"].(string))
144209

145210
return nil
146211
}

cmd/kosli/assertArtifact_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ type AssertArtifactCommandTestSuite struct {
1515
suite.Suite
1616
defaultKosliArguments string
1717
flowName string
18+
envName string
1819
artifactName string
1920
artifactPath string
2021
fingerprint string
2122
}
2223

2324
func (suite *AssertArtifactCommandTestSuite) SetupTest() {
2425
suite.flowName = "assert-artifact"
26+
suite.envName = "assert-artifact-environment"
2527
suite.artifactName = "arti"
2628
suite.artifactPath = "testdata/folder1/hello.txt"
2729
global = &GlobalOpts{
@@ -35,6 +37,7 @@ func (suite *AssertArtifactCommandTestSuite) SetupTest() {
3537
fingerprintOptions := &fingerprintOptions{
3638
artifactType: "file",
3739
}
40+
CreateEnv(global.Org, suite.envName, "server", suite.Suite.T())
3841
var err error
3942
suite.fingerprint, err = GetSha256Digest(suite.artifactPath, fingerprintOptions, logger)
4043
require.NoError(suite.Suite.T(), err)
@@ -58,12 +61,25 @@ func (suite *AssertArtifactCommandTestSuite) TestAssertArtifactCmd() {
5861
{
5962
name: "asserting an existing compliant artifact (using --fingerprint) results in OK and zero exit",
6063
cmd: fmt.Sprintf(`assert artifact --fingerprint %s --flow %s %s`, suite.fingerprint, suite.flowName, suite.defaultKosliArguments),
61-
goldenRegex: "COMPLIANT\nSee more details at http://localhost:8001/docs-cmd-test-user/flows/assert-artifact/artifacts/fcf33337634c2577a5d86fd7ecb0a25a7c1bb5d89c14fd236f546a5759252c02(?:\\?artifact_id=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8})?\n",
64+
goldenRegex: "(?s)^COMPLIANT\n.*Attestation-name.*See more details at http://localhost(:8001)?/docs-cmd-test-user/flows/assert-artifact/artifacts/fcf33337634c2577a5d86fd7ecb0a25a7c1bb5d89c14fd236f546a5759252c02(?:\\?artifact_id=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8})?\n",
65+
},
66+
{
67+
name: "asserting an existing compliant artifact (using --fingerprint) for an environment results in OK and zero exit",
68+
cmd: fmt.Sprintf(`assert artifact --fingerprint %s --flow %s --environment %s %s`, suite.fingerprint, suite.flowName, suite.envName, suite.defaultKosliArguments),
69+
goldenRegex: "(?s)^COMPLIANT\n.*Attestation-name.*Policy-name.*See more details at http://localhost(:8001)?/docs-cmd-test-user/flows/assert-artifact/artifacts/fcf33337634c2577a5d86fd7ecb0a25a7c1bb5d89c14fd236f546a5759252c02(?:\\?artifact_id=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8})?\n",
6270
},
6371
{
6472
name: "asserting an existing compliant artifact (using --artifact-type) results in OK and zero exit",
6573
cmd: fmt.Sprintf(`assert artifact %s --artifact-type file --flow %s %s`, suite.artifactPath, suite.flowName, suite.defaultKosliArguments),
66-
goldenRegex: "COMPLIANT\nSee more details at http://localhost:8001/docs-cmd-test-user/flows/assert-artifact/artifacts/fcf33337634c2577a5d86fd7ecb0a25a7c1bb5d89c14fd236f546a5759252c02?(?:\\?artifact_id=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8})?\n",
74+
goldenRegex: "(?s)^COMPLIANT\n.*See more details at http://localhost(:8001)?/docs-cmd-test-user/flows/assert-artifact/artifacts/fcf33337634c2577a5d86fd7ecb0a25a7c1bb5d89c14fd236f546a5759252c02?(?:\\?artifact_id=[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8})?\n",
75+
},
76+
{
77+
name: "json output of asserting an existing compliant artifact (using --artifact-type) results in OK and zero exit",
78+
cmd: fmt.Sprintf(`assert artifact %s --output json --artifact-type file --flow %s %s`, suite.artifactPath, suite.flowName, suite.defaultKosliArguments),
79+
goldenJson: []jsonCheck{
80+
{"compliant", true},
81+
{"scope", "flow"},
82+
},
6783
},
6884
{
6985
wantError: true,

cmd/kosli/attestArtifact_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ func (suite *AttestArtifactCommandTestSuite) TestAttestArtifactCmd() {
8282
golden: "Error: --external-fingerprints have labels that don't have a URL in --external-url\n",
8383
},
8484
{
85-
wantError: true,
86-
name: "fails (from server) when --external-fingerprint has invalid fingerprint",
87-
cmd: fmt.Sprintf("attest artifact testdata/file1 --fingerprint 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 --name cli --commit HEAD --build-url http://www.example.com --commit-url http://www.example.com --external-url file=https://http://www.example.com --external-fingerprint file=7509e5bda0 %s", suite.defaultKosliArguments),
88-
golden: "Error: Input payload validation failed: map[external_urls.file.fingerprint:'7509e5bda0' does not match '^[a-f0-9]{64}$']\n",
85+
wantError: true,
86+
name: "fails (from server) when --external-fingerprint has invalid fingerprint",
87+
cmd: fmt.Sprintf("attest artifact testdata/file1 --fingerprint 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 --name cli --commit HEAD --build-url http://www.example.com --commit-url http://www.example.com --external-url file=https://http://www.example.com --external-fingerprint file=7509e5bda0 %s", suite.defaultKosliArguments),
88+
goldenRegex: "Error: Input payload validation failed: .*7509e5bda0",
8989
},
9090
{
9191
name: "can attest with annotations against a trail",

cmd/kosli/attestSonar_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,19 +159,19 @@ func (suite *AttestSonarCommandTestSuite) TestAttestSonarCmd() {
159159
wantError: true,
160160
name: "if outdated task given (i.e. we try to get results for an older scan that SonarCloud has deleted), we get an error",
161161
cmd: fmt.Sprintf("attest sonar --name cli.foo --commit HEAD --origin-url http://www.example.com --sonar-working-dir testdata/sonar/sonarcloud/.scannerwork-old %s", suite.defaultKosliArguments),
162-
golden: "Error: analysis not found on https://sonarcloud.io. Snapshot may have been deleted by SonarQube\n",
162+
golden: "Error: No activity found for task 'AZERk4uWpzGpahwkB9ac' on https://sonarcloud.io. \nSonarQube may be experiencing problems, please check https://status.sonarqube.com/ and try again later. \nOtherwise if you are attesting an older scan, the snapshot may have been deleted by SonarQube.\n",
163163
},
164164
{
165165
wantError: true,
166166
name: "if incorrect revision given (or the scan for the given revision has been deleted by SonarCloud)",
167167
cmd: fmt.Sprintf("attest sonar --name cli.foo --commit HEAD --origin-url http://www.example.com --sonar-project-key cyber-dojo_differ --sonar-revision b4d1053f2aac18c9fb4b9a289a8289199c932e12 %s", suite.defaultKosliArguments),
168-
golden: "Error: analysis for revision b4d1053f2aac18c9fb4b9a289a8289199c932e12 of project cyber-dojo_differ not found. Check the revision is correct. Snapshot may also have been deleted by SonarQube\n",
168+
golden: "Error: analysis for revision b4d1053f2aac18c9fb4b9a289a8289199c932e12 of project cyber-dojo_differ not found. Check the revision is correct. \nThe scan may still be being processed by SonarQube, try again later.\n Otherwise if you are attesting an older scan, the snapshot may also have been deleted by SonarQube\n",
169169
},
170170
{
171171
wantError: true,
172172
name: "if incorrect project key given, we get an error message from Sonar",
173173
cmd: fmt.Sprintf("attest sonar --name cli.foo --commit HEAD --origin-url http://www.example.com --sonar-project-key cyber-dojo-differ --sonar-revision 38f3dc8b63abb632ac94a12b3f818b49f8047fa1 %s", suite.defaultKosliArguments),
174-
golden: "Error: sonar error: Component key 'cyber-dojo-differ' not found\n",
174+
golden: "Error: SonarQube error: Component key 'cyber-dojo-differ' not found\n",
175175
},
176176
}
177177

@@ -261,7 +261,7 @@ func (suite *AttestSonarQubeCommandTestSuite) TestAttestSonarQubeCmd() {
261261
wantError: true,
262262
name: "if incorrect project key given, we get an error message from Sonar",
263263
cmd: fmt.Sprintf("attest sonar --name cli.foo --commit HEAD --origin-url http://www.example.com --sonar-server-url http://localhost:9000 --sonar-project-key test99 --sonar-revision 38f3dc8b63abb632ac94a12b3f818b49f8047fa1 %s", suite.defaultKosliArguments),
264-
golden: "Error: sonar error: Component key 'test99' not found\n",
264+
golden: "Error: SonarQube error: Component key 'test99' not found\n",
265265
},
266266
{
267267
wantError: true,

cmd/kosli/beginTrail_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ func (suite *BeginTrailCommandTestSuite) TestBeginTrailCmd() {
3636
golden: "Error: accepts 1 arg(s), received 2\n",
3737
},
3838
{
39-
wantError: true,
40-
name: "fails when name is considered invalid by the server",
41-
cmd: fmt.Sprintf("begin trail foo?$bar --flow %s %s", suite.flowName, suite.defaultKosliArguments),
42-
golden: "Error: Input payload validation failed: map[name:'foo?$bar' does not match '^[a-zA-Z0-9][a-zA-Z0-9\\\\.\\\\-_~]*$']\n",
39+
wantError: true,
40+
name: "fails when name is considered invalid by the server",
41+
cmd: fmt.Sprintf("begin trail foo?$bar --flow %s %s", suite.flowName, suite.defaultKosliArguments),
42+
goldenRegex: "^Error.*foo\\?\\$bar",
4343
},
4444
{
4545
wantError: true,

cmd/kosli/createEnvironment_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@ func (suite *CreateEnvironmentCommandTestSuite) TestCreateEnvironmentCmd() {
6767
golden: "Error: only one of --exclude-scaling, --include-scaling is allowed\n",
6868
},
6969
{
70-
wantError: true,
71-
name: "fails if the type case does not match what the server accepts",
72-
cmd: "create env newEnv1 --type k8s" + suite.defaultKosliArguments,
73-
golden: "Error: Input payload validation failed: map[type:'k8s' is not one of ['K8S', 'ECS', 'S3', 'lambda', 'server', 'docker', 'azure-apps', 'logical']]\n",
70+
wantError: true,
71+
name: "fails if the type case does not match what the server accepts",
72+
cmd: "create env newEnv1 --type k8s" + suite.defaultKosliArguments,
73+
goldenRegex: "^Error: Input payload validation failed: .*k8s",
7474
},
7575
{
76-
wantError: true,
77-
name: "fails if the type is not recognized by the server",
78-
cmd: "create env newEnv1 --type unknown" + suite.defaultKosliArguments,
79-
golden: "Error: Input payload validation failed: map[type:'unknown' is not one of ['K8S', 'ECS', 'S3', 'lambda', 'server', 'docker', 'azure-apps', 'logical']]\n",
76+
wantError: true,
77+
name: "fails if the type is not recognized by the server",
78+
cmd: "create env newEnv1 --type unknown" + suite.defaultKosliArguments,
79+
goldenRegex: "^Error: Input payload validation failed: .*unknown",
8080
},
8181
{
8282
wantError: true,
@@ -97,10 +97,10 @@ func (suite *CreateEnvironmentCommandTestSuite) TestCreateEnvironmentCmd() {
9797
golden: "Error: accepts 1 arg(s), received 2\n",
9898
},
9999
{
100-
wantError: true,
101-
name: "fails when name is considered invalid by the server",
102-
cmd: "create env 'foo bar' --type K8S" + suite.defaultKosliArguments,
103-
golden: "Error: Input payload validation failed: map[name:'foo bar' does not match '^[a-zA-Z0-9][a-zA-Z0-9\\\\.\\\\-_]*$']\n",
100+
wantError: true,
101+
name: "fails when name is considered invalid by the server",
102+
cmd: "create env 'foo bar' --type K8S" + suite.defaultKosliArguments,
103+
goldenRegex: "^Error: Input payload validation failed: .*foo bar",
104104
},
105105
{
106106
wantError: false,

cmd/kosli/createFlow_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ func (suite *CreateFlowCommandTestSuite) TestCreateFlowCmd() {
3333
golden: "Error: accepts 1 arg(s), received 2\n",
3434
},
3535
{
36-
wantError: true,
37-
name: "fails when name is considered invalid by the server",
38-
cmd: "create flow 'foo bar'" + suite.defaultKosliArguments,
39-
golden: "Error: Input payload validation failed: map[name:'foo bar' does not match '^[a-zA-Z0-9][a-zA-Z0-9\\\\.\\\\-_~]*$']\n",
36+
wantError: true,
37+
name: "fails when name is considered invalid by the server",
38+
cmd: "create flow 'foo bar'" + suite.defaultKosliArguments,
39+
goldenRegex: "^Error: .*foo bar",
4040
},
4141
{
4242
name: "can create a flow (by default legacy template is used)",

cmd/kosli/getAttestationType_test.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ func (suite *GetAttestationTypeCommandTestSuite) SetupTest() {
3535
func (suite *GetAttestationTypeCommandTestSuite) TestGetAttestationTypeCmd() {
3636
tests := []cmdTestCase{
3737
{
38-
wantError: true,
39-
name: "getting a non existing attestation type fails",
40-
cmd: fmt.Sprintf(`get attestation-type foo %s`, suite.defaultKosliArguments),
41-
golden: fmt.Sprintf("Error: Custom attestation type 'foo' does not exist for org '%s'. \n", global.Org),
38+
wantError: true,
39+
name: "getting a non existing attestation type fails",
40+
cmd: fmt.Sprintf(`get attestation-type foo %s`, suite.defaultKosliArguments),
41+
goldenRegex: fmt.Sprintf("^Error: Custom attestation type 'foo' does not exist for org '%s'", global.Org),
4242
},
4343
{
4444
wantError: true,
@@ -74,11 +74,6 @@ func (suite *GetAttestationTypeCommandTestSuite) TestGetAttestationTypeCmd() {
7474
cmd: fmt.Sprintf(`get attestation-type %s@v1 %s`, suite.attestationTypeName, suite.defaultKosliArguments),
7575
goldenFile: "output/get/get-attestation-type-version.txt",
7676
},
77-
{
78-
name: "giving a non-integer version number returns the unversioned attestation type",
79-
cmd: fmt.Sprintf(`get attestation-type %s@vone %s`, suite.attestationTypeName, suite.defaultKosliArguments),
80-
goldenFile: "output/get/get-attestation-type.txt",
81-
},
8277
{
8378
name: "getting an existing attestation type with --output json works",
8479
cmd: fmt.Sprintf(`get attestation-type %s --output json %s`, suite.attestationTypeName, suite.defaultKosliArguments),

0 commit comments

Comments
 (0)