From ca0f9847db0c582404f14105eff5de9e92f0af26 Mon Sep 17 00:00:00 2001 From: ejahnGithub Date: Thu, 22 May 2025 12:31:45 -0400 Subject: [PATCH] add json format --- .github/CODEOWNERS | 4 +++ pkg/cmd/release/attestation/options.go | 12 +++++++-- pkg/cmd/release/verify-asset/verify-asset.go | 28 ++++++++++++++------ pkg/cmd/release/verify/verify.go | 12 +++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 40683d917..5d39bf3af 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,10 @@ internal/codespaces/ @cli/codespaces # Limit Package Security team ownership to the attestation command package and related integration tests pkg/cmd/attestation/ @cli/package-security +pkg/cmd/release/attestation @cli/package-security +pkg/cmd/release/verify @cli/package-security +pkg/cmd/release/verify-asset @cli/package-security + test/integration/attestation-cmd @cli/package-security pkg/cmd/attestation/verification/embed/tuf-repo.github.com/ @cli/tuf-root-reviewers diff --git a/pkg/cmd/release/attestation/options.go b/pkg/cmd/release/attestation/options.go index aadbc2f47..f0957c04b 100644 --- a/pkg/cmd/release/attestation/options.go +++ b/pkg/cmd/release/attestation/options.go @@ -3,6 +3,7 @@ package attestation import ( "fmt" "net/http" + "path/filepath" "strings" "github.com/cli/cli/v2/internal/gh" @@ -47,8 +48,15 @@ type AttestOptions struct { Hostname string EC verification.EnforcementCriteria // Tenant is only set when tenancy is used - Tenant string - FilePath string + Tenant string + AssetFilePath string +} + +// Clean cleans the file path option values +func (opts *AttestOptions) Clean() { + if opts.AssetFilePath != "" { + opts.AssetFilePath = filepath.Clean(opts.AssetFilePath) + } } // AreFlagsValid checks that the provided flag combination is valid diff --git a/pkg/cmd/release/verify-asset/verify-asset.go b/pkg/cmd/release/verify-asset/verify-asset.go index 1a74afdbf..585643c2d 100644 --- a/pkg/cmd/release/verify-asset/verify-asset.go +++ b/pkg/cmd/release/verify-asset/verify-asset.go @@ -31,8 +31,8 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions) return cmdutil.FlagErrorf("You must specify a tag and a file path") } - opts.TagName = args[0] - opts.FilePath = args[1] + tagName := args[0] + assetFilePath := args[1] httpClient, err := f.HttpClient() if err != nil { @@ -46,8 +46,8 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions) hostname, _ := ghauth.DefaultHost() *opts = attestation.AttestOptions{ - TagName: opts.TagName, - FilePath: opts.FilePath, + TagName: tagName, + AssetFilePath: assetFilePath, Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(), APIClient: api.NewLiveClient(httpClient, hostname, logger), Limit: 10, @@ -93,15 +93,17 @@ func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions) return verifyAssetRun(opts) }, } + cmdutil.AddFormatFlags(cmd, &opts.Exporter) + return cmd } func verifyAssetRun(opts *attestation.AttestOptions) error { ctx := context.Background() - fileName := getFileName(opts.FilePath) + fileName := getFileName(opts.AssetFilePath) // calculate the digest of the file - fileDigest, err := artifact.NewDigestedArtifact(nil, opts.FilePath, "sha256") + fileDigest, err := artifact.NewDigestedArtifact(nil, opts.AssetFilePath, "sha256") if err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to calculate file digest")) return err @@ -141,7 +143,7 @@ func verifyAssetRun(opts *attestation.AttestOptions) error { } if len(filteredAttestations) == 0 { - opts.Logger.Printf(opts.Logger.ColorScheme.Red("Release %s does not contain %s (%s)\n"), opts.TagName, opts.FilePath, fileDigest.DigestWithAlg()) + opts.Logger.Printf(opts.Logger.ColorScheme.Red("Release %s does not contain %s (%s)\n"), opts.TagName, opts.AssetFilePath, fileDigest.DigestWithAlg()) return nil } @@ -152,10 +154,20 @@ func verifyAssetRun(opts *attestation.AttestOptions) error { if err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Red(errMsg)) - opts.Logger.Printf(opts.Logger.ColorScheme.Red("Release %s does not contain %s (%s)\n"), opts.TagName, opts.FilePath, fileDigest.DigestWithAlg()) + opts.Logger.Printf(opts.Logger.ColorScheme.Red("Release %s does not contain %s (%s)\n"), opts.TagName, opts.AssetFilePath, fileDigest.DigestWithAlg()) return err } + // If an exporter is provided with the --json flag, write the results to the terminal in JSON format + if opts.Exporter != nil { + // print the results to the terminal as an array of JSON objects + if err = opts.Exporter.Write(opts.Logger.IO, verified); err != nil { + opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to write JSON output")) + return err + } + return nil + } + opts.Logger.Printf("The following %s matched the policy criteria\n\n", text.Pluralize(len(verified), "attestation")) opts.Logger.Println(opts.Logger.ColorScheme.Green("✓ Verification succeeded!\n")) opts.Logger.Printf("Attestation found matching release %s (%s)\n", opts.TagName, releaseRefDigest.DigestWithAlg()) diff --git a/pkg/cmd/release/verify/verify.go b/pkg/cmd/release/verify/verify.go index 8c835d1e6..e8c6621e5 100644 --- a/pkg/cmd/release/verify/verify.go +++ b/pkg/cmd/release/verify/verify.go @@ -94,6 +94,8 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro return verifyRun(opts) }, } + cmdutil.AddFormatFlags(cmd, &opts.Exporter) + return cmd } @@ -137,6 +139,16 @@ func verifyRun(opts *attestation.AttestOptions) error { return err } + // If an exporter is provided with the --json flag, write the results to the terminal in JSON format + if opts.Exporter != nil { + // print the results to the terminal as an array of JSON objects + if err = opts.Exporter.Write(opts.Logger.IO, verified); err != nil { + opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to write JSON output")) + return err + } + return nil + } + opts.Logger.Printf("The following %s matched the policy criteria\n\n", text.Pluralize(len(verified), "attestation")) opts.Logger.Println(opts.Logger.ColorScheme.Green("✓ Verification succeeded!\n"))