From bc3a5ffefc79c1a349559287173db9118e58eb53 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Sun, 6 Jul 2025 15:37:26 +0200 Subject: [PATCH 1/2] wip: start adding get attestations command --- cmd/kosli/get.go | 1 + cmd/kosli/getAttestations.go | 180 +++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 cmd/kosli/getAttestations.go diff --git a/cmd/kosli/get.go b/cmd/kosli/get.go index 8ce43bafc..267b0872e 100644 --- a/cmd/kosli/get.go +++ b/cmd/kosli/get.go @@ -27,6 +27,7 @@ func newGetCmd(out io.Writer) *cobra.Command { newGetPolicyCmd(out), newGetAttestationTypeCmd(out), newGetAttestationCmd(out), + newGetAttestationsCmd(out), ) return cmd } diff --git a/cmd/kosli/getAttestations.go b/cmd/kosli/getAttestations.go new file mode 100644 index 000000000..60e29204d --- /dev/null +++ b/cmd/kosli/getAttestations.go @@ -0,0 +1,180 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/kosli-dev/cli/internal/gitview" + "github.com/kosli-dev/cli/internal/output" + "github.com/kosli-dev/cli/internal/requests" + "github.com/spf13/cobra" +) + +const getAttestationsShortDesc = `Get attestations. ` + +const getAttestationsLongDesc = getAttestationShortDesc + `` + +const getAttestationsExample = ` +# 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 getAttestationsOptions struct { + output string + flowName string + attestationType string + attestationName string + commitRange string + repositoryRoot string +} + +func newGetAttestationsCmd(out io.Writer) *cobra.Command { + o := new(getAttestationsOptions) + cmd := &cobra.Command{ + Use: "attestations", + Hidden: true, + Short: getAttestationsShortDesc, + Long: getAttestationsLongDesc, + Example: getAttestationsExample, + Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, args []string) error { + err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) + if err != nil { + return ErrorBeforePrintingUsage(cmd, err.Error()) + } + 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.flowName, "flow", "f", "", flowNameFlag) + cmd.Flags().StringVar(&o.attestationType, "type", "", "attestationTypeFlag") + cmd.Flags().StringVar(&o.attestationName, "name", "", "attestationNameFlag") + cmd.Flags().StringVar(&o.commitRange, "commit-range", "", "commitRangeFlag") + cmd.Flags().StringVar(&o.repositoryRoot, "repository-root", ".", "repositoryRootFlag") + return cmd +} + +func (o *getAttestationsOptions) run(out io.Writer, args []string) error { + baseURL := fmt.Sprintf("%s/api/v2/attestations/%s/list_attestations_for_criteria", global.Host, global.Org) + parsedURL, err := url.Parse(baseURL) + if err != nil { + return err + } + queryParams := parsedURL.Query() + + if o.flowName != "" { + queryParams.Add("flow_name", o.flowName) + } + if o.attestationType != "" { + queryParams.Add("attestation_type", o.attestationType) + } + if o.attestationName != "" { + queryParams.Add("attestation_name", o.attestationName) + } + if o.commitRange != "" { + commitRange := strings.Split(o.commitRange, "..") + if len(commitRange) != 2 { + return fmt.Errorf("invalid commit range: %s", o.commitRange) + } + baseRef := commitRange[0] + targetRef := commitRange[1] + + gitView, err := gitview.New(o.repositoryRoot) + if err != nil { + return err + } + commits, err := gitView.CommitsBetween(baseRef, targetRef, logger) + if err != nil { + return err + } + for _, commit := range commits { + queryParams.Add("commit_list", commit.Sha1) + } + } + + parsedURL.RawQuery = queryParams.Encode() + finalURL := parsedURL.String() + + reqParams := &requests.RequestParams{ + Method: http.MethodGet, + URL: finalURL, + 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": printFilteredAttestationsAsTable, + "json": output.PrintJson, + }) +} + +func printFilteredAttestationsAsTable(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 +} From aafc3956da77b8b99cdf06862db61b47e8e64759 Mon Sep 17 00:00:00 2001 From: Faye Date: Mon, 7 Jul 2025 16:55:03 +0200 Subject: [PATCH 2/2] Start making output table for get attestations command --- cmd/kosli/getAttestations.go | 107 +++++++++++++++++------------------ 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/cmd/kosli/getAttestations.go b/cmd/kosli/getAttestations.go index 60e29204d..72effb279 100644 --- a/cmd/kosli/getAttestations.go +++ b/cmd/kosli/getAttestations.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io" "net/http" @@ -18,15 +19,24 @@ const getAttestationsShortDesc = `Get attestations. ` const getAttestationsLongDesc = getAttestationShortDesc + `` const getAttestationsExample = ` -# get an attestation from a trail (requires the --trail flag) -kosli get attestation attestationName \ +# get trail attestations of a given type and name for a range of commits in a flow (requires the --flow flag) +kosli get attestations \ --flow flowName \ - --trail trailName + --type attestationType \ + --name attestationName \ + --commit-range commitRange -# get an attestation from an artifact -kosli get attestation attestationName \ +# get artifact attestations of a given type and name for a range of commits in a flow (requires the --flow flag) +kosli get attestations \ --flow flowName \ - --fingerprint fingerprint + --type attestationType \ + --name slotName.attestationName \ + --commit-range commitRange + +# get all attestations of a given type from a flow +kosli get attestations \ + --flow flowName \ + --type attestationType ` type getAttestationsOptions struct { @@ -38,6 +48,14 @@ type getAttestationsOptions struct { repositoryRoot string } +type SingleAttestation 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"` +} + func newGetAttestationsCmd(out io.Writer) *cobra.Command { o := new(getAttestationsOptions) cmd := &cobra.Command{ @@ -128,53 +146,34 @@ func (o *getAttestationsOptions) run(out io.Writer, args []string) error { } func printFilteredAttestationsAsTable(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) - // } + var commitDict map[string][]SingleAttestation + err := json.Unmarshal([]byte(raw), &commitDict) + if err != nil { + return err + } + + if len(commitDict) == 0 { + logger.Info("No commits found.") + return nil + } + + header := []string{"COMMIT", "ATTESTATION NAME", "ATTESTATION TYPE", "COMPLIANT", "CREATED_AT"} + rows := []string{} + for commit, attestations := range commitDict { + for i, attestation := range attestations { + createdAt, err := formattedTimestamp(attestation.CreatedAt, true) + if err != nil { + return err + } + if i == 0 { + rows = append(rows, fmt.Sprintf("%s\t%s\t%s\t%t\t%s", commit, attestation.Name, attestation.Type, attestation.Compliance, createdAt)) + } else { + rows = append(rows, fmt.Sprintf("\t%s\t%s\t%t\t%s", attestation.Name, attestation.Type, attestation.Compliance, createdAt)) + } + } + rows = append(rows, "\t\t\t") + } + tabFormattedPrint(out, header, rows) + return nil }