-
Notifications
You must be signed in to change notification settings - Fork 6
3178 get attestation #493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
3178 get attestation #493
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
|
|
||
| "github.com/kosli-dev/cli/internal/output" | ||
| "github.com/kosli-dev/cli/internal/requests" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| const getAttestationShortDesc = `Get attestation by name from a specified trail or artifact. ` | ||
|
|
||
| const getAttestationLongDesc = getAttestationShortDesc + ` | ||
| You can get an attestation from a trail or artifact using its name. The attestation name should be given | ||
| WITHOUT dot-notation. | ||
sami-alajrami marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| To get an attestation from a trail, specify the trail name using the --trail flag. | ||
| To get an attestation from an artifact, specify the artifact fingerprint using the --fingerprint flag. | ||
sami-alajrami marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| In both cases the flow must also be specified using the --flow flag. | ||
|
|
||
| If there are multiple attestations with the same name on the trail or artifact, a list of all will be returned. | ||
| ` | ||
|
|
||
| const getAttestationExample = ` | ||
| # get an attestation from a trail (requires the --trail flag) | ||
| kosli get attestation attestationName \ | ||
| --flow flowName \ | ||
| --trail trailName | ||
|
|
||
| # get an attestation from an artifact | ||
| kosli get attestation attestationName \ | ||
| --flow flowName \ | ||
| --fingerprint fingerprint | ||
| ` | ||
|
|
||
| type getAttestationOptions struct { | ||
| output string | ||
| flow string | ||
| trail string | ||
| fingerprint string | ||
| } | ||
|
|
||
| type Attestation struct { | ||
| Name string `json:"attestation_name"` | ||
| Type string `json:"attestation_type"` | ||
| Compliance bool `json:"is_compliant"` | ||
| ArtifactFingerprint string `json:"artifact_fingerprint,omitempty"` | ||
| CreatedAt float64 `json:"created_at"` | ||
| GitCommitInfo *GitCommitInfo `json:"git_commit_info,omitempty"` | ||
| HtmlUrl string `json:"html_url"` | ||
| } | ||
|
|
||
| type GitCommitInfo struct { | ||
| Sha1 string `json:"sha1"` | ||
| Author string `json:"author"` | ||
| Message string `json:"message"` | ||
| Branch string `json:"branch"` | ||
| Url string `json:"url,omitempty"` | ||
| Timestamp float64 `json:"timestamp"` | ||
| } | ||
|
|
||
| func newGetAttestationCmd(out io.Writer) *cobra.Command { | ||
| o := new(getAttestationOptions) | ||
| cmd := &cobra.Command{ | ||
| Use: "attestation ATTESTATION-NAME", | ||
| Short: getAttestationShortDesc, | ||
| Long: getAttestationLongDesc, | ||
| Example: getAttestationExample, | ||
| Args: cobra.ExactArgs(1), | ||
| PreRunE: func(cmd *cobra.Command, args []string) error { | ||
| err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) | ||
| if err != nil { | ||
| return ErrorBeforePrintingUsage(cmd, err.Error()) | ||
| } | ||
| err = MuXRequiredFlags(cmd, []string{"trail", "fingerprint"}, true) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| }, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return o.run(out, args) | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().StringVarP(&o.output, "output", "o", "table", outputFlag) | ||
| cmd.Flags().StringVarP(&o.flow, "flow", "f", "", flowNameFlag) | ||
| cmd.Flags().StringVarP(&o.trail, "trail", "t", "", getAttestationTrailFlag) | ||
| cmd.Flags().StringVarP(&o.fingerprint, "fingerprint", "F", "", getAttestationFingerprintFlag) | ||
|
|
||
| err := RequireFlags(cmd, []string{"flow"}) | ||
| if err != nil { | ||
| logger.Error("failed to configure required flags: %v", err) | ||
| } | ||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| func (o *getAttestationOptions) run(out io.Writer, args []string) error { | ||
| var url string | ||
| baseUrl := fmt.Sprintf("%s/api/v2/attestations/%s/%s", global.Host, global.Org, o.flow) | ||
| if o.trail != "" { | ||
| url = fmt.Sprintf("%s/trail/%s", baseUrl, o.trail) | ||
| } | ||
|
|
||
| if o.fingerprint != "" { | ||
| url = fmt.Sprintf("%s/artifact/%s", baseUrl, o.fingerprint) | ||
| } | ||
|
|
||
| url = fmt.Sprintf("%s/%s", url, args[0]) | ||
|
|
||
| reqParams := &requests.RequestParams{ | ||
| Method: http.MethodGet, | ||
| URL: url, | ||
| Token: global.ApiToken, | ||
| } | ||
|
|
||
| response, err := kosliClient.Do(reqParams) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return output.FormattedPrint(response.Body, o.output, out, 0, | ||
| map[string]output.FormatOutputFunc{ | ||
| "table": printAttestationsAsTable, | ||
| "json": output.PrintJson, | ||
| }) | ||
| } | ||
|
|
||
| func printAttestationsAsTable(raw string, out io.Writer, pageNumber int) error { | ||
| var attestations []Attestation | ||
| err := json.Unmarshal([]byte(raw), &attestations) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if len(attestations) == 0 { | ||
| logger.Info("No attestations found.") | ||
| return nil | ||
| } | ||
|
|
||
| separator := "" | ||
| for _, attestation := range attestations { | ||
| rows := []string{} | ||
| rows = append(rows, fmt.Sprintf("Name:\t%s", attestation.Name)) | ||
| rows = append(rows, fmt.Sprintf("Type:\t%s", attestation.Type)) | ||
| rows = append(rows, fmt.Sprintf("Compliance:\t%t", attestation.Compliance)) | ||
|
|
||
| createdAt, err := formattedTimestamp(attestation.CreatedAt, false) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| rows = append(rows, fmt.Sprintf("Created at:\t%s", createdAt)) | ||
|
|
||
| if attestation.ArtifactFingerprint != "" { | ||
| rows = append(rows, fmt.Sprintf("Artifact fingerprint:\t%s", attestation.ArtifactFingerprint)) | ||
| } | ||
| if attestation.GitCommitInfo != nil { | ||
| rows = append(rows, "Git Commit Info:") | ||
| rows = append(rows, fmt.Sprintf(" Sha1:\t%s", attestation.GitCommitInfo.Sha1)) | ||
| rows = append(rows, fmt.Sprintf(" Author:\t%s", attestation.GitCommitInfo.Author)) | ||
| rows = append(rows, fmt.Sprintf(" Branch:\t%s", attestation.GitCommitInfo.Branch)) | ||
| rows = append(rows, fmt.Sprintf(" Commit URL:\t%s", attestation.GitCommitInfo.Url)) | ||
| timestamp, err := formattedTimestamp(attestation.GitCommitInfo.Timestamp, false) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| rows = append(rows, fmt.Sprintf(" Timestamp:\t%s", timestamp)) | ||
| } | ||
|
|
||
| if attestation.HtmlUrl != "" { | ||
| rows = append(rows, fmt.Sprintf("Attestation URL:\t%s", attestation.HtmlUrl)) | ||
| } | ||
|
|
||
| fmt.Print(separator) | ||
| separator = "\n" | ||
| tabFormattedPrint(out, []string{}, rows) | ||
| } | ||
| return nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| "github.com/stretchr/testify/suite" | ||
| ) | ||
|
|
||
| // Define the suite, and absorb the built-in basic suite | ||
| // functionality from testify - including a T() method which | ||
| // returns the current testing context | ||
| type GetAttestationCommandTestSuite struct { | ||
| suite.Suite | ||
| defaultKosliArguments string | ||
| flowName string | ||
| artifactName string | ||
| artifactPath string | ||
| fingerprint string | ||
| trailName string | ||
| } | ||
|
|
||
| func (suite *GetAttestationCommandTestSuite) SetupTest() { | ||
| suite.flowName = "get-attestation" | ||
| suite.artifactName = "arti" | ||
| suite.artifactPath = "testdata/folder1/hello.txt" | ||
| suite.trailName = "cli-build-1" | ||
| global = &GlobalOpts{ | ||
| ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", | ||
| Org: "docs-cmd-test-user-shared", | ||
| Host: "http://localhost:8001", | ||
| } | ||
| suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) | ||
|
|
||
| CreateFlowWithTemplate(suite.flowName, "testdata/valid_template.yml", suite.Suite.T()) | ||
| BeginTrail(suite.trailName, suite.flowName, "", suite.Suite.T()) | ||
| fingerprintOptions := &fingerprintOptions{ | ||
| artifactType: "file", | ||
| } | ||
| var err error | ||
| suite.fingerprint, err = GetSha256Digest(suite.artifactPath, fingerprintOptions, logger) | ||
| require.NoError(suite.Suite.T(), err) | ||
| CreateArtifactOnTrail(suite.flowName, suite.trailName, "cli", suite.fingerprint, suite.artifactName, suite.Suite.T()) | ||
| CreateGenericArtifactAttestation(suite.flowName, suite.trailName, suite.fingerprint, "first-artifact-attestation", suite.Suite.T()) | ||
| CreateGenericTrailAttestation(suite.flowName, suite.trailName, "first-trail-attestation", suite.Suite.T()) | ||
| CreateGenericArtifactAttestation(suite.flowName, suite.trailName, suite.fingerprint, "second-artifact-attestation", suite.Suite.T()) | ||
| CreateGenericTrailAttestation(suite.flowName, suite.trailName, "second-trail-attestation", suite.Suite.T()) | ||
| } | ||
|
|
||
| func (suite *GetAttestationCommandTestSuite) TestGetAttestationCmd() { | ||
| tests := []cmdTestCase{ | ||
| { | ||
| wantError: false, | ||
| name: "if no attestation found, say so", | ||
| cmd: fmt.Sprintf(`get attestation non-existent-attestation --flow %s --trail %s %s`, suite.flowName, suite.trailName, suite.defaultKosliArguments), | ||
| golden: "No attestations found.\n", | ||
| }, | ||
| { | ||
| wantError: false, | ||
| name: "if no attestation found return empty list in json format", | ||
| cmd: fmt.Sprintf(`get attestation non-existent-attestation --flow %s --trail %s %s --output json`, suite.flowName, suite.trailName, suite.defaultKosliArguments), | ||
| golden: "[]\n", | ||
| }, | ||
| { | ||
| wantError: true, | ||
| name: "providing more than one argument fails", | ||
| cmd: fmt.Sprintf(`get attestation first-attestation second-attestation --flow %s --trail %s %s`, suite.flowName, suite.trailName, suite.defaultKosliArguments), | ||
| golden: "Error: accepts 1 arg(s), received 2\n", | ||
| }, | ||
| { | ||
| wantError: true, | ||
| name: "missing --flow fails", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --trail %s %s`, suite.trailName, suite.defaultKosliArguments), | ||
| golden: "Error: required flag(s) \"flow\" not set\n", | ||
| }, | ||
| { | ||
| wantError: true, | ||
| name: "missing --api-token fails", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --flow %s --org orgX`, suite.flowName), | ||
| golden: "Error: --api-token is not set\nUsage: kosli get attestation ATTESTATION-NAME [flags]\n", | ||
| }, | ||
| { | ||
| name: "getting an existing trail attestation works", | ||
| cmd: fmt.Sprintf(`get attestation first-trail-attestation --flow %s --trail %s %s`, suite.flowName, suite.trailName, suite.defaultKosliArguments), | ||
| }, | ||
| { | ||
| name: "getting an existing trail attestation with --output json works", | ||
| cmd: fmt.Sprintf(`get attestation first-trail-attestation --flow %s --trail %s --output json %s`, suite.flowName, suite.trailName, suite.defaultKosliArguments), | ||
| }, | ||
| { | ||
| name: "getting an existing artifact attestation works", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --flow %s --fingerprint %s %s`, suite.flowName, suite.fingerprint, suite.defaultKosliArguments), | ||
| }, | ||
| { | ||
| name: "getting an existing artifact attestation with --output json works", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --flow %s --fingerprint %s --output json %s`, suite.flowName, suite.fingerprint, suite.defaultKosliArguments), | ||
| }, | ||
| { | ||
| wantError: true, | ||
| name: "missing both trail and fingerprint fails", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --flow %s %s`, suite.flowName, suite.defaultKosliArguments), | ||
| golden: "Error: at least one of --trail, --fingerprint is required\n", | ||
| }, | ||
| { | ||
| wantError: true, | ||
| name: "providing both trail and fingerprint fails", | ||
| cmd: fmt.Sprintf(`get attestation first-artifact-attestation --flow %s --trail %s --fingerprint %s %s`, suite.flowName, suite.trailName, suite.fingerprint, suite.defaultKosliArguments), | ||
| golden: "Error: only one of --trail, --fingerprint is allowed\n", | ||
| }, | ||
| } | ||
|
|
||
| runTestCmd(suite.Suite.T(), tests) | ||
| } | ||
|
|
||
| // In order for 'go test' to run this suite, we need to create | ||
| // a normal test function and pass our suite to suite.Run | ||
| func TestGetAttestationCommandTestSuite(t *testing.T) { | ||
| suite.Run(t, new(GetAttestationCommandTestSuite)) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.