diff --git a/pkg/cmd/attestation/download/download.go b/pkg/cmd/attestation/download/download.go index 143912308..5670186d0 100644 --- a/pkg/cmd/attestation/download/download.go +++ b/pkg/cmd/attestation/download/download.go @@ -47,6 +47,11 @@ func NewDownloadCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Comman Any associated bundle(s) will be written to a file in the current directory named after the artifact's digest. For example, if the digest is "sha256:1234", the file will be named "sha256:1234.jsonl". + + Because colons are special characters in Windows and cannot be used in + file names, the digest will be formatted with a dash separating the algorithm + from the digest. For example, if the digest is "sha256:1234", the file + will be named "sha256-1234.jsonl". `, "`"), Example: heredoc.Doc(` # Download attestations for a local artifact linked with an organization diff --git a/pkg/cmd/attestation/download/metadata.go b/pkg/cmd/attestation/download/metadata.go index 4096be001..4bc353a96 100644 --- a/pkg/cmd/attestation/download/metadata.go +++ b/pkg/cmd/attestation/download/metadata.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "os" + "runtime" + "strings" "github.com/cli/cli/v2/pkg/cmd/attestation/api" ) @@ -20,6 +22,12 @@ type LiveStore struct { } func (s *LiveStore) createJSONLinesFilePath(artifact string) string { + if runtime.GOOS == "windows" { + // Colons are special characters in Windows and cannot be used in file names. + // Replace them with dashes to avoid issues. + artifact = strings.ReplaceAll(artifact, ":", "-") + } + path := fmt.Sprintf("%s.jsonl", artifact) if s.outputPath != "" { return fmt.Sprintf("%s/%s", s.outputPath, path) diff --git a/pkg/cmd/attestation/download/metadata_test.go b/pkg/cmd/attestation/download/metadata_test.go index 8ee3b2a45..34f156364 100644 --- a/pkg/cmd/attestation/download/metadata_test.go +++ b/pkg/cmd/attestation/download/metadata_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "runtime" "testing" "github.com/cli/cli/v2/pkg/cmd/attestation/api" @@ -31,7 +32,15 @@ func TestCreateJSONLinesFilePath(t *testing.T) { artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") require.NoError(t, err) - outputFileName := fmt.Sprintf("%s.jsonl", artifact.DigestWithAlg()) + expectedPosixFileName := fmt.Sprintf("%s.jsonl", artifact.DigestWithAlg()) + expectedWindowsFileName := fmt.Sprintf("%s-%s.jsonl", artifact.Algorithm(), artifact.Digest()) + + var expectedFileName string + if runtime.GOOS == "windows" { + expectedFileName = expectedWindowsFileName + } else { + expectedFileName = expectedPosixFileName + } testCases := []struct { name string @@ -41,22 +50,22 @@ func TestCreateJSONLinesFilePath(t *testing.T) { { name: "with output path", outputPath: tempDir, - expected: path.Join(tempDir, outputFileName), + expected: path.Join(tempDir, expectedFileName), }, { name: "with nested output path", outputPath: path.Join(tempDir, "subdir"), - expected: path.Join(tempDir, "subdir", outputFileName), + expected: path.Join(tempDir, "subdir", expectedFileName), }, { name: "with output path with beginning slash", outputPath: path.Join("/", tempDir, "subdir"), - expected: path.Join("/", tempDir, "subdir", outputFileName), + expected: path.Join("/", tempDir, "subdir", expectedFileName), }, { name: "without output path", outputPath: "", - expected: outputFileName, + expected: expectedFileName, }, }