Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions cmd/kosli/assertArtifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/kosli-dev/cli/internal/requests"
"github.com/spf13/cobra"
)

const assertArtifactShortDesc = `Assert the compliance status of an artifact in Kosli. `
const assertArtifactShortDesc = `Assert the compliance status of an artifact in Kosli (in its flow or against an environment). `

const assertArtifactLongDesc = assertArtifactShortDesc + `
Exits with non-zero code if the artifact has a non-compliant status.`

const assertArtifactExample = `
# assert that an artifact meets all compliance requirements for an environment
kosli assert artifact \
--fingerprint 184c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0 \
--flow yourFlowName \
--against-env prod \
--api-token yourAPIToken \
--org yourOrgName

# fail if an artifact has a non-compliant status (using the artifact fingerprint)
kosli assert artifact \
--fingerprint 184c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0 \
Expand All @@ -35,6 +44,7 @@ type assertArtifactOptions struct {
fingerprintOptions *fingerprintOptions
fingerprint string // This is calculated or provided by the user
flowName string
envName string
}

func newAssertArtifactCmd(out io.Writer) *cobra.Command {
Expand Down Expand Up @@ -64,14 +74,10 @@ func newAssertArtifactCmd(out io.Writer) *cobra.Command {

cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", fingerprintFlag)
cmd.Flags().StringVarP(&o.flowName, "flow", "f", "", flowNameFlag)
cmd.Flags().StringVar(&o.envName, "environment", "", envNameFlag)
addFingerprintFlags(cmd, o.fingerprintOptions)
addDryRunFlag(cmd)

err := RequireFlags(cmd, []string{"flow"})
if err != nil {
logger.Error("failed to configure required flags: %v", err)
}

return cmd
}

Expand All @@ -84,31 +90,56 @@ func (o *assertArtifactOptions) run(out io.Writer, args []string) error {
}
}

url := fmt.Sprintf("%s/api/v2/artifacts/%s/%s/fingerprint/%s", global.Host, global.Org, o.flowName, o.fingerprint)
baseURL := fmt.Sprintf("%s/api/v2/asserts/%s/fingerprint/%s", global.Host, global.Org, o.fingerprint)
params := url.Values{}

if o.flowName != "" {
params.Add("flow_name", o.flowName)
}

if o.envName != "" {
params.Add("environment_name", o.envName)
}

fullURL := baseURL
if len(params) > 0 {
fullURL += "?" + params.Encode()
}

reqParams := &requests.RequestParams{
Method: http.MethodGet,
URL: url,
URL: fullURL,
Token: global.ApiToken,
}
response, err := kosliClient.Do(reqParams)
if err != nil {
return err
}

var artifactData map[string]interface{}
err = json.Unmarshal([]byte(response.Body), &artifactData)
var evaluationResult map[string]interface{}
err = json.Unmarshal([]byte(response.Body), &evaluationResult)
if err != nil {
return err
}

if artifactData["state"].(string) == "COMPLIANT" {
scope := evaluationResult["scope"].(string)

if evaluationResult["compliant"].(bool) {
logger.Info("COMPLIANT")
logger.Info("See more details at %s", artifactData["html_url"].(string))
if scope == "flow" {
logger.Info("See more details at %s", evaluationResult["html_url"].(string))
}
} else {
return fmt.Errorf("%s: %s\nSee more details at %s", artifactData["state"].(string),
artifactData["state_info"].(string),
artifactData["html_url"].(string))
if scope == "flow" {
return fmt.Errorf("not compliant\nSee more details at %s", evaluationResult["html_url"].(string))
} else {
jsonData, err := json.MarshalIndent(evaluationResult["policy_evaluations"], "", " ")
if err != nil {
return fmt.Errorf("error marshalling evaluation result: %v", err)
}
return fmt.Errorf("not compliant for env [%s]: \n %v", o.envName,
string(jsonData))
}
}

return nil
Expand Down
11 changes: 2 additions & 9 deletions cmd/kosli/assertArtifact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,11 @@ func (suite *AssertArtifactCommandTestSuite) TestAssertArtifactCmd() {
cmd: fmt.Sprintf(`assert artifact --flow %s %s`, suite.flowName, suite.defaultKosliArguments),
golden: "Error: docker image name or file/dir path is required when --fingerprint is not provided\nUsage: kosli assert artifact [IMAGE-NAME | FILE-PATH | DIR-PATH] [flags]\n",
},
// TODO: this test case does not pass as the validation does not check for it
// {
// wantError: true,
// name: "providing both --fingerprint and --artifact-type fails",
// cmd: fmt.Sprintf(`assert artifact --artifact-type file --fingerprint %s --flow %s %s`, suite.fingerprint, suite.flowName, suite.defaultKosliArguments),
// golden: "COMPLIANT\n",
// },
{
wantError: true,
name: "missing --flow fails",
name: "not providing --flow when the artifact has multiple instances fails with a server error",
cmd: fmt.Sprintf(`assert artifact --fingerprint %s %s`, suite.fingerprint, suite.defaultKosliArguments),
golden: "Error: required flag(s) \"flow\" not set\n",
golden: fmt.Sprintf("Error: Fingerprint '%s' matched multiple artifacts in org '%s'. Please narrow the search to one flow.\n", suite.fingerprint, global.Org),
},
}

Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file,
attestationTypeDescriptionFlag = "[optional] The attestation type description."
attestationTypeSchemaFlag = "[optional] Path to the attestation type schema in JSON Schema format."
attestationTypeJqFlag = "[optional] The attestation type evaluation JQ rules."
envNameFlag = "The Kosli environment name to assert the artifact against."
)

var global *GlobalOpts
Expand Down
Loading