diff --git a/pkg/cmd/attestation/download/download.go b/pkg/cmd/attestation/download/download.go index c3dc8426f..2adc0bb50 100644 --- a/pkg/cmd/attestation/download/download.go +++ b/pkg/cmd/attestation/download/download.go @@ -136,13 +136,11 @@ func runDownload(opts *Options) error { return fmt.Errorf("failed to fetch attestations: %w", err) } - filePath := createJSONLinesFilePath(artifact.DigestWithAlg(), opts.OutputPath) - fmt.Fprintf(opts.Logger.IO.Out, "Writing attestations to file %s.\nAny previous content will be overwritten\n\n", filePath) - - metadataFilePath, err := createMetadataFile(attestations, filePath) + metadataFilePath, err := opts.Store.createMetadataFile(artifact.DigestWithAlg(), attestations) if err != nil { return fmt.Errorf("failed to write attestation: %w", err) } + fmt.Fprintf(opts.Logger.IO.Out, "Wrote attestations to file %s.\nAny previous content has been overwritten\n\n", metadataFilePath) fmt.Fprint(opts.Logger.IO.Out, opts.Logger.ColorScheme.Greenf( diff --git a/pkg/cmd/attestation/download/download_test.go b/pkg/cmd/attestation/download/download_test.go index 5455eb3dd..a3d5dcbc4 100644 --- a/pkg/cmd/attestation/download/download_test.go +++ b/pkg/cmd/attestation/download/download_test.go @@ -33,7 +33,10 @@ func TestNewDownloadCmd(t *testing.T) { return client, nil }, } - tempDir := t.TempDir() + + store := &MetadataStore{ + outputPath: t.TempDir(), + } testcases := []struct { name string @@ -50,7 +53,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha384", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 30, }, wantsErr: true, @@ -64,7 +67,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 30, }, wantsErr: false, @@ -78,7 +81,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 30, }, wantsErr: true, @@ -92,7 +95,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Repo: "sigstore/sigstore-js", Limit: 30, }, @@ -107,7 +110,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 30, }, wantsErr: false, @@ -121,7 +124,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 101, }, wantsErr: false, @@ -135,7 +138,7 @@ func TestNewDownloadCmd(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha256", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 0, }, wantsErr: true, @@ -173,6 +176,9 @@ func TestNewDownloadCmd(t *testing.T) { func TestRunDownload(t *testing.T) { tempDir := t.TempDir() + store := &MetadataStore{ + outputPath: tempDir, + } baseOpts := Options{ ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz", @@ -180,7 +186,7 @@ func TestRunDownload(t *testing.T) { OCIClient: oci.MockClient{}, DigestAlgorithm: "sha512", Owner: "sigstore", - OutputPath: tempDir, + Store: store, Limit: 30, Logger: logging.NewTestLogger(), } diff --git a/pkg/cmd/attestation/download/metadata.go b/pkg/cmd/attestation/download/metadata.go new file mode 100644 index 000000000..d271e360c --- /dev/null +++ b/pkg/cmd/attestation/download/metadata.go @@ -0,0 +1,54 @@ +package download + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/cli/cli/v2/pkg/cmd/attestation/api" +) + +type MetadataStore struct { + outputPath string +} + +func (s *MetadataStore) createJSONLinesFilePath(artifact string) string { + path := fmt.Sprintf("%s.jsonl", artifact) + if s.outputPath != "" { + return fmt.Sprintf("%s/%s", s.outputPath, path) + } + return path +} + +func (s *MetadataStore) createMetadataFile(artifactDigest string, attestationsResp []*api.Attestation) (string, error) { + metadataFilePath := s.createJSONLinesFilePath(artifactDigest) + + f, err := os.Create(metadataFilePath) + if err != nil { + return "", fmt.Errorf("failed to create trusted metadata file: %w", err) + } + + for _, resp := range attestationsResp { + bundle := resp.Bundle + attBytes, err := json.Marshal(bundle) + if err != nil { + return "", fmt.Errorf("failed to marshall attestation to JSON: %w", err) + } + + withNewline := fmt.Sprintf("%s\n", attBytes) + _, err = f.Write([]byte(withNewline)) + if err != nil { + if err = f.Close(); err != nil { + return "", fmt.Errorf("failed to close file while handling write error: %w", err) + } + + return "", fmt.Errorf("failed to write trusted metadata: %w", err) + } + } + + if err = f.Close(); err != nil { + return "", fmt.Errorf("failed to close file after writing metadata: %w", err) + } + + return metadataFilePath, nil +} diff --git a/pkg/cmd/attestation/download/options.go b/pkg/cmd/attestation/download/options.go index 215316a8a..15eca8f38 100644 --- a/pkg/cmd/attestation/download/options.go +++ b/pkg/cmd/attestation/download/options.go @@ -19,8 +19,8 @@ type Options struct { DigestAlgorithm string Logger *logging.Logger Limit int + Store *MetadataStore OCIClient oci.Client - OutputPath string Owner string Repo string Verbose bool