diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0757b1a12ed..9b22701a7d3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -51,7 +51,7 @@ jobs: - name: Build executable run: make - - name: Run attestation command integration Tests + - name: Run attestation command set integration tests + shell: bash run: | - ./test/integration/attestation-cmd/download-and-verify-package-attestation.sh - ./test/integration/attestation-cmd/verify-sigstore-bundle-versions.sh + ./test/integration/attestation-cmd/run-all-tests.sh "${{ matrix.os }}" diff --git a/pkg/cmd/attestation/download/download.go b/pkg/cmd/attestation/download/download.go index 143912308d3..7547e9e68ab 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". + + Colons are special characters on Windows and cannot be used in + file names. To accommodate, a dash will be used to separate the algorithm + from the digest in the attestations file name. 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/download_test.go b/pkg/cmd/attestation/download/download_test.go index 0762a29da6f..6c2986065c3 100644 --- a/pkg/cmd/attestation/download/download_test.go +++ b/pkg/cmd/attestation/download/download_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net/http" + "runtime" "strings" "testing" @@ -22,6 +23,17 @@ import ( var artifactPath = test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") +func expectedFilePath(tempDir string, digestWithAlg string) string { + var filename string + if runtime.GOOS == "windows" { + filename = fmt.Sprintf("%s.jsonl", strings.ReplaceAll(digestWithAlg, ":", "-")) + } else { + filename = fmt.Sprintf("%s.jsonl", digestWithAlg) + } + + return test.NormalizeRelativePath(fmt.Sprintf("%s/%s", tempDir, filename)) +} + func TestNewDownloadCmd(t *testing.T) { testIO, _, _, _ := iostreams.Test() f := &cmdutil.Factory{ @@ -201,9 +213,10 @@ func TestRunDownload(t *testing.T) { artifact, err := artifact.NewDigestedArtifact(baseOpts.OCIClient, baseOpts.ArtifactPath, baseOpts.DigestAlgorithm) require.NoError(t, err) - require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + expectedFilePath := expectedFilePath(tempDir, artifact.DigestWithAlg()) + require.FileExists(t, expectedFilePath) - actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + actualLineCount, err := countLines(expectedFilePath) require.NoError(t, err) expectedLineCount := 2 @@ -221,9 +234,10 @@ func TestRunDownload(t *testing.T) { artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) require.NoError(t, err) - require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + expectedFilePath := expectedFilePath(tempDir, artifact.DigestWithAlg()) + require.FileExists(t, expectedFilePath) - actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + actualLineCount, err := countLines(expectedFilePath) require.NoError(t, err) expectedLineCount := 2 @@ -240,9 +254,10 @@ func TestRunDownload(t *testing.T) { artifact, err := artifact.NewDigestedArtifact(opts.OCIClient, opts.ArtifactPath, opts.DigestAlgorithm) require.NoError(t, err) - require.FileExists(t, fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + expectedFilePath := expectedFilePath(tempDir, artifact.DigestWithAlg()) + require.FileExists(t, expectedFilePath) - actualLineCount, err := countLines(fmt.Sprintf("%s/%s.jsonl", tempDir, artifact.DigestWithAlg())) + actualLineCount, err := countLines(expectedFilePath) require.NoError(t, err) expectedLineCount := 2 diff --git a/pkg/cmd/attestation/download/metadata.go b/pkg/cmd/attestation/download/metadata.go index 4096be001c1..4bc353a96fd 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 8ee3b2a4569..2596e23773f 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,12 @@ 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()) + var expectedFileName string + if runtime.GOOS == "windows" { + expectedFileName = fmt.Sprintf("%s-%s.jsonl", artifact.Algorithm(), artifact.Digest()) + } else { + expectedFileName = fmt.Sprintf("%s.jsonl", artifact.DigestWithAlg()) + } testCases := []struct { name string @@ -41,22 +47,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, }, } diff --git a/pkg/cmd/attestation/test/data/gh_2.60.1_windows_arm64.zip b/pkg/cmd/attestation/test/data/gh_2.60.1_windows_arm64.zip new file mode 100644 index 00000000000..e49624a622f Binary files /dev/null and b/pkg/cmd/attestation/test/data/gh_2.60.1_windows_arm64.zip differ diff --git a/test/integration/attestation-cmd/download/download.sh b/test/integration/attestation-cmd/download/download.sh new file mode 100755 index 00000000000..0824c0e12a4 --- /dev/null +++ b/test/integration/attestation-cmd/download/download.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +os=$1 + +# Get the root directory of the repository +rootDir="$(git rev-parse --show-toplevel)" + +ghBuildPath="$rootDir/bin/gh" + +artifactPath="$rootDir/pkg/cmd/attestation/test/data/gh_2.60.1_windows_arm64.zip" + +# Download attestations for the package +if ! $ghBuildPath attestation download "$artifactPath" --owner=cli; then + # cleanup test data + echo "Failed to download attestations" + exit 1 +fi + +digest="5ddb1d4d013a44c2e5df027867c0d4161383eb7c16e569a86384af52bfe09a65" +attestation_filename="sha256:$digest.jsonl" +if [ "$os" == "windows-latest" ]; then + echo "Running the test on Windows." + echo "Build the expected filename accordingly" + attestation_filename="sha256-$digest.jsonl" +fi + +if [ ! -f "$attestation_filename" ]; then + echo "Expected attestation file $attestation_filename not found" + exit 1 +fi + +if [ ! -s "$attestation_filename" ]; then + echo "Attestation file $attestation_filename is empty" + rm "$attestation_filename" + exit 1 +fi + +cat "$attestation_filename" + +# Clean up the downloaded attestation file +rm "$attestation_filename" diff --git a/test/integration/attestation-cmd/run-all-tests.sh b/test/integration/attestation-cmd/run-all-tests.sh new file mode 100755 index 00000000000..45fba964ebb --- /dev/null +++ b/test/integration/attestation-cmd/run-all-tests.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +os=$1 + +# Get the root directory of the repository +rootDir="$(git rev-parse --show-toplevel)" + +verify_test_dir="$rootDir/test/integration/attestation-cmd/verify" +echo "Running all \"gh attestation verify\" tests" +for script in "$verify_test_dir"/*.sh; do + if [ -f "$script" ]; then + echo "Running $script..." + bash "$script" + fi +done + +download_test_dir="$rootDir/test/integration/attestation-cmd/download" +echo "Running all \"gh attestation download\" tests" +for script in "$download_test_dir"/*.sh; do + if [ -f "$script" ]; then + echo "Running $script..." + bash "$script" "$os" + fi +done diff --git a/test/integration/attestation-cmd/download-and-verify-package-attestation.sh b/test/integration/attestation-cmd/verify/download-and-verify-package-attestation.sh similarity index 100% rename from test/integration/attestation-cmd/download-and-verify-package-attestation.sh rename to test/integration/attestation-cmd/verify/download-and-verify-package-attestation.sh diff --git a/test/integration/attestation-cmd/verify-oci-bundle.sh b/test/integration/attestation-cmd/verify/verify-oci-bundle.sh similarity index 100% rename from test/integration/attestation-cmd/verify-oci-bundle.sh rename to test/integration/attestation-cmd/verify/verify-oci-bundle.sh diff --git a/test/integration/attestation-cmd/verify-sigstore-bundle-versions.sh b/test/integration/attestation-cmd/verify/verify-sigstore-bundle-versions.sh similarity index 100% rename from test/integration/attestation-cmd/verify-sigstore-bundle-versions.sh rename to test/integration/attestation-cmd/verify/verify-sigstore-bundle-versions.sh