From b39af75e83afd4a0b2690cb9cc7d24c47113f98c Mon Sep 17 00:00:00 2001 From: jason yang Date: Tue, 5 Nov 2024 06:03:14 +0000 Subject: [PATCH] cherry picking missing commits from sylabs sif repo Co-authored-by: Adam Hughes <9903835+tri-adam@users.noreply.github.com> Signed-off-by: jason yang --- .github/workflows/ci.yml | 5 +- .github/workflows/goreleaser.yml | 2 +- .golangci.yml | 5 +- go.mod | 20 +- go.sum | 48 +- internal/app/siftool/info_test.go | 2 - pkg/integrity/clearsign_test.go | 2 - pkg/integrity/digest_test.go | 3 - pkg/integrity/dsse.go | 186 +++--- pkg/integrity/dsse_test.go | 40 +- pkg/integrity/metadata_test.go | 3 - pkg/integrity/result.go | 9 +- pkg/integrity/select.go | 38 +- pkg/integrity/sign.go | 18 +- pkg/integrity/sign_test.go | 14 +- .../ED25519.golden | 2 +- .../Test_dsseEncoder_signMessage/Multi.golden | 2 +- .../RSA_SHA256.golden | 2 +- .../RSA_SHA384.golden | 2 +- .../RSA_SHA512.golden | 2 +- pkg/integrity/verify.go | 6 +- pkg/integrity/verify_test.go | 21 +- pkg/sif/add.go | 85 +++ pkg/sif/add_test.go | 159 +++++ pkg/sif/create.go | 434 ++------------ pkg/sif/create_test.go | 558 ++---------------- pkg/sif/delete.go | 167 ++++++ pkg/sif/delete_test.go | 356 +++++++++++ pkg/sif/descriptor.go | 4 +- pkg/sif/descriptor_input_test.go | 2 - pkg/sif/descriptor_test.go | 1 - pkg/sif/load_test.go | 4 - pkg/sif/select.go | 6 + pkg/sif/set.go | 224 +++++++ pkg/sif/set_test.go | 317 ++++++++++ pkg/sif/sif_test.go | 1 - .../TestDeleteObject/OneCompact.golden | Bin 0 -> 32180 bytes .../testdata/TestDeleteObject/OneZero.golden | Bin 0 -> 32180 bytes .../TestDeleteObject/OneZeroCompact.golden | Bin 0 -> 32180 bytes .../TestDeleteObject/TwoCompact.golden | Bin 0 -> 32178 bytes .../testdata/TestDeleteObject/TwoZero.golden | Bin 0 -> 32180 bytes .../TestDeleteObject/TwoZeroCompact.golden | Bin 0 -> 32178 bytes .../NoCompact.golden | Bin 32185 -> 32182 bytes .../TestDeleteObjectAndAddObject/Zero.golden | Bin 32185 -> 32182 bytes .../TestDeleteObjects/DataType.golden | Bin 0 -> 32180 bytes .../DataTypeCompact.golden} | Bin .../ErrObjectNotFound.golden} | Bin 32178 -> 32176 bytes .../TestDeleteObjects/NilSelectFunc.golden | Bin 0 -> 32180 bytes .../PrimaryPartitionCompact.golden | Bin 0 -> 32176 bytes .../Deterministic.golden | Bin 0 -> 32178 bytes .../TestFileImage_SetOCIBlobDigest/One.golden | Bin 0 -> 32178 bytes .../TestFileImage_SetOCIBlobDigest/Two.golden | Bin 0 -> 32180 bytes .../WithTime.golden | Bin 0 -> 32178 bytes 53 files changed, 1559 insertions(+), 1191 deletions(-) create mode 100644 pkg/sif/add.go create mode 100644 pkg/sif/add_test.go create mode 100644 pkg/sif/delete.go create mode 100644 pkg/sif/delete_test.go create mode 100644 pkg/sif/set.go create mode 100644 pkg/sif/set_test.go create mode 100644 pkg/sif/testdata/TestDeleteObject/OneCompact.golden create mode 100644 pkg/sif/testdata/TestDeleteObject/OneZero.golden create mode 100644 pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden create mode 100644 pkg/sif/testdata/TestDeleteObject/TwoCompact.golden create mode 100644 pkg/sif/testdata/TestDeleteObject/TwoZero.golden create mode 100644 pkg/sif/testdata/TestDeleteObject/TwoZeroCompact.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/DataType.golden rename pkg/sif/testdata/{TestDeleteObject/ZeroCompact.golden => TestDeleteObjects/DataTypeCompact.golden} (100%) rename pkg/sif/testdata/{TestDeleteObject/Zero.golden => TestDeleteObjects/ErrObjectNotFound.golden} (99%) create mode 100644 pkg/sif/testdata/TestDeleteObjects/NilSelectFunc.golden create mode 100644 pkg/sif/testdata/TestDeleteObjects/PrimaryPartitionCompact.golden create mode 100644 pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Deterministic.golden create mode 100644 pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/One.golden create mode 100644 pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Two.golden create mode 100644 pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/WithTime.golden diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4df5bd7..54ebe7c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,6 @@ name: ci on: + workflow_dispatch: {} pull_request: push: branches: @@ -22,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: '1.21.x' + go-version: '1.22.8' - name: Check go.mod and go.sum are tidy run: | @@ -41,7 +42,7 @@ jobs: - name: Install Lint uses: golangci/golangci-lint-action@v2 with: - version: v1.56 + version: v1.61 skip-pkg-cache: true skip-build-cache: true diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index b7193b65..4ec14a39 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.21.x + go-version: 1.22.8 - name: Install syft run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin diff --git a/.golangci.yml b/.golangci.yml index a1f8bd85..8b9a0068 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,21 +10,21 @@ linters: - bodyclose - containedctx - contextcheck + - copyloopvar - decorder - dogsled - dupl - dupword + - err113 - errcheck - errchkjson - errname - errorlint - - exportloopref - forcetypeassert - gochecknoinits - gocritic - godot - godox - - goerr113 - gofumpt - goimports - gomodguard @@ -35,6 +35,7 @@ linters: - grouper - ineffassign - interfacebloat + - intrange - ireturn - lll - maintidx diff --git a/go.mod b/go.mod index f04045f1..a7713f1c 100644 --- a/go.mod +++ b/go.mod @@ -1,33 +1,31 @@ module github.com/apptainer/sif/v2 -go 1.21 - -toolchain go1.21.9 +go 1.22.8 require ( github.com/ProtonMail/go-crypto v1.0.0 github.com/google/go-containerregistry v0.20.2 github.com/google/uuid v1.6.0 github.com/sebdah/goldie/v2 v2.5.5 - github.com/secure-systems-lab/go-securesystemslib v0.8.0 - github.com/sigstore/sigstore v1.8.4 + github.com/sigstore/sigstore v1.8.10 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 ) require ( github.com/cloudflare/circl v1.3.7 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.2.1 // indirect - github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect + github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/term v0.20.0 // indirect - google.golang.org/grpc v1.56.3 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 337cd21f..0762ad77 100644 --- a/go.sum +++ b/go.sum @@ -14,10 +14,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= @@ -34,8 +36,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e h1:RLTpX495BXToqxpM90Ws4hXEo4Wfh81jr9DX1n/4WOo= -github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e/go.mod h1:EAuqr9VFWxBi9nD5jc/EA2MT1RFty9288TF6zdtYoCU= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -59,8 +61,8 @@ github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sigstore/sigstore v1.8.4 h1:g4ICNpiENFnWxjmBzBDWUn62rNFeny/P77HUC8da32w= -github.com/sigstore/sigstore v1.8.4/go.mod h1:1jIKtkTFEeISen7en+ZPWdDHazqhxco/+v9CNjc7oNg= +github.com/sigstore/sigstore v1.8.10 h1:r4t+TYzJlG9JdFxMy+um9GZhZ2N1hBTyTex0AHEZxFs= +github.com/sigstore/sigstore v1.8.10/go.mod h1:BekjqxS5ZtHNJC4u3Q3Stvfx2eyisbW/lUZzmPU2u4A= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -73,16 +75,12 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/otel v1.15.0 h1:NIl24d4eiLJPM0vKn4HjLYM+UZf6gSfi9Z+NmCxkWbk= -go.opentelemetry.io/otel v1.15.0/go.mod h1:qfwLEbWhLPk5gyWrne4XnF0lC8wtywbuJbgfAE3zbek= -go.opentelemetry.io/otel/trace v1.15.0 h1:5Fwje4O2ooOxkfyqI/kJwxWotggDLix4BSAvpE1wlpo= -go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1J2W8GV1ev0Y4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -103,15 +101,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -123,18 +121,16 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/app/siftool/info_test.go b/internal/app/siftool/info_test.go index 3bc10939..6764b50c 100644 --- a/internal/app/siftool/info_test.go +++ b/internal/app/siftool/info_test.go @@ -66,8 +66,6 @@ func Test_readableSize(t *testing.T) { }, } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { if got, want := readableSize(tt.size), tt.want; got != want { t.Errorf("got %v, want %v", got, want) diff --git a/pkg/integrity/clearsign_test.go b/pkg/integrity/clearsign_test.go index 9530c730..86e8b865 100644 --- a/pkg/integrity/clearsign_test.go +++ b/pkg/integrity/clearsign_test.go @@ -55,7 +55,6 @@ func Test_clearsignEncoder_signMessage(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} @@ -174,7 +173,6 @@ func Test_clearsignDecoder_verifyMessage(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} diff --git a/pkg/integrity/digest_test.go b/pkg/integrity/digest_test.go index 930fe6c0..24d66061 100644 --- a/pkg/integrity/digest_test.go +++ b/pkg/integrity/digest_test.go @@ -94,7 +94,6 @@ func TestNewLegacyDigest(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { d, err := newLegacyDigest(tt.ht, []byte(tt.text)) if got, want := err, tt.wantError; !errors.Is(got, want) { @@ -160,7 +159,6 @@ func TestDigest_MarshalJSON(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { value, err := hex.DecodeString(tt.value) if err != nil { @@ -265,7 +263,6 @@ func TestDigest_UnmarshalJSON(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { var d digest diff --git a/pkg/integrity/dsse.go b/pkg/integrity/dsse.go index 68d9fd17..8a59fa92 100644 --- a/pkg/integrity/dsse.go +++ b/pkg/integrity/dsse.go @@ -13,27 +13,39 @@ import ( "bytes" "context" "crypto" + "encoding/base64" "encoding/json" "errors" "fmt" "io" - "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/sigstore/sigstore/pkg/signature/options" ) const metadataMediaType = "application/vnd.sylabs.sif-metadata+json" type dsseEncoder struct { - es *dsse.EnvelopeSigner - h crypto.Hash - payloadType string + ss []signature.Signer + opts []signature.SignOption } // newDSSEEncoder returns an encoder that signs messages in DSSE format according to opts, with key // material from ss. SHA256 is used as the hash algorithm, unless overridden by opts. -func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) (*dsseEncoder, error) { +func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) *dsseEncoder { + return &dsseEncoder{ + ss: ss, + opts: opts, + } +} + +// signMessage signs the message from r in DSSE format, and writes the result to w. On success, the +// hash function is returned. +func (en *dsseEncoder) signMessage(ctx context.Context, w io.Writer, r io.Reader) (crypto.Hash, error) { + opts := en.opts + opts = append(opts, options.WithContext(ctx)) + var so crypto.SignerOpts for _, opt := range opts { opt.ApplyCryptoSignerOpts(&so) @@ -45,57 +57,25 @@ func newDSSEEncoder(ss []signature.Signer, opts ...signature.SignOption) (*dsseE opts = append(opts, options.WithCryptoSignerOpts(so)) } - dss := make([]dsse.Signer, 0, len(ss)) - for _, s := range ss { - ds, err := newDSSESigner(s, opts...) - if err != nil { - return nil, err - } - - dss = append(dss, ds) - } - - es, err := dsse.NewEnvelopeSigner(dss...) - if err != nil { - return nil, err - } - - return &dsseEncoder{ - es: es, - h: so.HashFunc(), - payloadType: metadataMediaType, - }, nil -} - -// signMessage signs the message from r in DSSE format, and writes the result to w. On success, the -// hash function is returned. -func (en *dsseEncoder) signMessage(ctx context.Context, w io.Writer, r io.Reader) (crypto.Hash, error) { - body, err := io.ReadAll(r) - if err != nil { - return 0, err - } - - e, err := en.es.SignPayload(ctx, en.payloadType, body) + s := dsse.WrapMultiSigner(metadataMediaType, en.ss...) + b, err := s.SignMessage(r, opts...) if err != nil { return 0, err } - return en.h, json.NewEncoder(w).Encode(e) + _, err = w.Write(b) + return so.HashFunc(), err } type dsseDecoder struct { - vs []signature.Verifier - threshold int - payloadType string + vs []signature.Verifier } // newDSSEDecoder returns a decoder that verifies messages in DSSE format using key material from // vs. func newDSSEDecoder(vs ...signature.Verifier) *dsseDecoder { return &dsseDecoder{ - vs: vs, - threshold: 1, // Envelope considered verified if at least one verifier succeeds. - payloadType: metadataMediaType, + vs: vs, } } @@ -107,112 +87,78 @@ var ( // verifyMessage reads a message from r, verifies its signature(s), and returns the message // contents. On success, the accepted public keys are set in vr. func (de *dsseDecoder) verifyMessage(ctx context.Context, r io.Reader, h crypto.Hash, vr *VerifyResult) ([]byte, error) { //nolint:lll - vs := make([]dsse.Verifier, 0, len(de.vs)) + // Wrap the verifiers so we can accumulate the accepted public keys. + vs := make([]signature.Verifier, 0, len(de.vs)) for _, v := range de.vs { - dv, err := newDSSEVerifier(v, options.WithCryptoSignerOpts(h)) - if err != nil { - return nil, err - } - - vs = append(vs, dv) + vs = append(vs, wrappedVerifier{ + Verifier: v, + keys: &vr.keys, + }) } - v, err := dsse.NewMultiEnvelopeVerifier(de.threshold, vs...) + raw, err := io.ReadAll(r) if err != nil { return nil, err } - var e dsse.Envelope - if err := json.NewDecoder(r).Decode(&e); err != nil { - return nil, err - } + v := dsse.WrapMultiVerifier(metadataMediaType, 1, vs...) - vr.aks, err = v.Verify(ctx, &e) - if err != nil { + if err := v.VerifySignature(bytes.NewReader(raw), nil, options.WithContext(ctx), options.WithHash(h)); err != nil { return nil, fmt.Errorf("%w: %w", errDSSEVerifyEnvelopeFailed, err) } - if e.PayloadType != de.payloadType { - return nil, fmt.Errorf("%w: %v", errDSSEUnexpectedPayloadType, e.PayloadType) - } - - return e.DecodeB64Payload() -} - -type dsseSigner struct { - s signature.Signer - opts []signature.SignOption - pub crypto.PublicKey -} - -// newDSSESigner returns a dsse.Signer that uses s to sign according to opts. -func newDSSESigner(s signature.Signer, opts ...signature.SignOption) (*dsseSigner, error) { - pub, err := s.PublicKey() - if err != nil { + var e dsseEnvelope + if err := json.Unmarshal(raw, &e); err != nil { return nil, err } - return &dsseSigner{ - s: s, - opts: opts, - pub: pub, - }, nil -} - -// Sign signs the supplied data. -func (s *dsseSigner) Sign(ctx context.Context, data []byte) ([]byte, error) { - opts := s.opts - opts = append(opts, options.WithContext(ctx)) + if e.PayloadType != metadataMediaType { + return nil, fmt.Errorf("%w: %v", errDSSEUnexpectedPayloadType, e.PayloadType) + } - return s.s.SignMessage(bytes.NewReader(data), opts...) + return e.DecodedPayload() } -// KeyID returns the key ID associated with s. -func (s dsseSigner) KeyID() (string, error) { - return dsse.SHA256KeyID(s.pub) +type wrappedVerifier struct { + signature.Verifier + keys *[]crypto.PublicKey } -type dsseVerifier struct { - v signature.Verifier - opts []signature.VerifyOption - pub crypto.PublicKey -} +func (wv wrappedVerifier) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { + err := wv.Verifier.VerifySignature(signature, message, opts...) + if err == nil { + pub, err := wv.Verifier.PublicKey() + if err != nil { + return err + } -// newDSSEVerifier returns a dsse.Verifier that uses v to verify according to opts. -func newDSSEVerifier(v signature.Verifier, opts ...signature.VerifyOption) (*dsseVerifier, error) { - pub, err := v.PublicKey() - if err != nil { - return nil, err + *wv.keys = append(*wv.keys, pub) } - - return &dsseVerifier{ - v: v, - opts: opts, - pub: pub, - }, nil -} - -// Verify verifies that sig is a valid signature of data. -func (v *dsseVerifier) Verify(ctx context.Context, data, sig []byte) error { - opts := v.opts - opts = append(opts, options.WithContext(ctx)) - - return v.v.VerifySignature(bytes.NewReader(sig), bytes.NewReader(data), opts...) + return err } -// Public returns the public key associated with v. -func (v *dsseVerifier) Public() crypto.PublicKey { - return v.pub +// dsseEnvelope describes a DSSE envelope. +type dsseEnvelope struct { + PayloadType string `json:"payloadType"` + Payload string `json:"payload"` + Signatures []struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` + } `json:"signatures"` } -// KeyID returns the key ID associated with v. -func (v *dsseVerifier) KeyID() (string, error) { - return dsse.SHA256KeyID(v.pub) +// DecodedPayload returns the decoded payload from envelope e. +func (e *dsseEnvelope) DecodedPayload() ([]byte, error) { + b, err := base64.StdEncoding.DecodeString(e.Payload) + if err != nil { + return base64.URLEncoding.DecodeString(e.Payload) + } + return b, nil } // isDSSESignature returns true if r contains a signature in a DSSE envelope. func isDSSESignature(r io.Reader) bool { - var e dsse.Envelope + var e dsseEnvelope if err := json.NewDecoder(r).Decode(&e); err != nil { return false } diff --git a/pkg/integrity/dsse_test.go b/pkg/integrity/dsse_test.go index 57311269..e1784174 100644 --- a/pkg/integrity/dsse_test.go +++ b/pkg/integrity/dsse_test.go @@ -21,8 +21,8 @@ import ( "testing" "github.com/sebdah/goldie/v2" - "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -82,14 +82,10 @@ func Test_dsseEncoder_signMessage(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} - en, err := newDSSEEncoder(tt.signers, tt.signOpts...) - if err != nil { - t.Fatal(err) - } + en := newDSSEEncoder(tt.signers, tt.signOpts...) ht, err := en.signMessage(context.Background(), &b, strings.NewReader(testMessage)) if got, want := err, tt.wantErr; (got != nil) != want { @@ -110,28 +106,30 @@ func Test_dsseEncoder_signMessage(t *testing.T) { // corruptPayloadType corrupts the payload type of e and re-signs the envelope. The result is a // cryptographically valid envelope with an unexpected payload types. -func corruptPayloadType(t *testing.T, en *dsseEncoder, e *dsse.Envelope) { +func corruptPayloadType(t *testing.T, en *dsseEncoder, e *dsseEnvelope) { t.Helper() - body, err := e.DecodeB64Payload() + body, err := e.DecodedPayload() if err != nil { t.Fatal(err) } - bad, err := en.es.SignPayload(context.Background(), "bad", body) + bad, err := dsse.WrapMultiSigner("bad", en.ss...).SignMessage(bytes.NewReader(body)) if err != nil { t.Fatal(err) } - *e = *bad + if err := json.Unmarshal(bad, e); err != nil { + t.Fatal(err) + } } // corruptPayload corrupts the payload in e. The result is that the signature(s) in e do not match // the payload. -func corruptPayload(t *testing.T, _ *dsseEncoder, e *dsse.Envelope) { +func corruptPayload(t *testing.T, _ *dsseEncoder, e *dsseEnvelope) { t.Helper() - body, err := e.DecodeB64Payload() + body, err := e.DecodedPayload() if err != nil { t.Fatal(err) } @@ -141,7 +139,7 @@ func corruptPayload(t *testing.T, _ *dsseEncoder, e *dsse.Envelope) { // corruptSignatures corrupts the signature(s) in e. The result is that the signature(s) in e do // not match the payload. -func corruptSignatures(t *testing.T, _ *dsseEncoder, e *dsse.Envelope) { +func corruptSignatures(t *testing.T, _ *dsseEncoder, e *dsseEnvelope) { t.Helper() for i, sig := range e.Signatures { @@ -161,7 +159,7 @@ func Test_dsseDecoder_verifyMessage(t *testing.T) { name string signers []signature.Signer signOpts []signature.SignOption - corrupter func(*testing.T, *dsseEncoder, *dsse.Envelope) + corrupter func(*testing.T, *dsseEncoder, *dsseEnvelope) de *dsseDecoder wantErr error wantMessage string @@ -190,8 +188,7 @@ func Test_dsseDecoder_verifyMessage(t *testing.T) { de: newDSSEDecoder( getTestVerifier(t, "rsa-public.pem", crypto.SHA256), ), - wantErr: errDSSEVerifyEnvelopeFailed, - wantKeys: []crypto.PublicKey{}, + wantErr: errDSSEVerifyEnvelopeFailed, }, { name: "CorruptSignatures", @@ -202,8 +199,7 @@ func Test_dsseDecoder_verifyMessage(t *testing.T) { de: newDSSEDecoder( getTestVerifier(t, "rsa-public.pem", crypto.SHA256), ), - wantErr: errDSSEVerifyEnvelopeFailed, - wantKeys: []crypto.PublicKey{}, + wantErr: errDSSEVerifyEnvelopeFailed, }, { name: "Multi_SHA256", @@ -323,14 +319,10 @@ func Test_dsseDecoder_verifyMessage(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} - en, err := newDSSEEncoder(tt.signers, tt.signOpts...) - if err != nil { - t.Fatal(err) - } + en := newDSSEEncoder(tt.signers, tt.signOpts...) // Sign and encode message. h, err := en.signMessage(context.Background(), &b, strings.NewReader(testMessage)) @@ -340,7 +332,7 @@ func Test_dsseDecoder_verifyMessage(t *testing.T) { // Introduce corruption, if applicable. if tt.corrupter != nil { - var e dsse.Envelope + var e dsseEnvelope if err := json.Unmarshal(b.Bytes(), &e); err != nil { t.Fatal(err) } diff --git a/pkg/integrity/metadata_test.go b/pkg/integrity/metadata_test.go index 29c329fe..5f4e6492 100644 --- a/pkg/integrity/metadata_test.go +++ b/pkg/integrity/metadata_test.go @@ -50,7 +50,6 @@ func TestGetHeaderMetadata(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getHeaderMetadata(tt.header, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { @@ -106,7 +105,6 @@ func TestGetObjectMetadata(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getObjectMetadata(tt.relativeID, tt.descr, tt.data, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { @@ -159,7 +157,6 @@ func TestGetImageMetadata(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { md, err := getImageMetadata(f, tt.minID, tt.ods, tt.hash) if got, want := err, tt.wantErr; !errors.Is(got, want) { diff --git a/pkg/integrity/result.go b/pkg/integrity/result.go index e8debf12..8a5345a9 100644 --- a/pkg/integrity/result.go +++ b/pkg/integrity/result.go @@ -14,14 +14,13 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/apptainer/sif/v2/pkg/sif" - "github.com/secure-systems-lab/go-securesystemslib/dsse" ) // VerifyResult describes the results of an individual signature validation. type VerifyResult struct { sig sif.Descriptor verified []sif.Descriptor - aks []dsse.AcceptedKey + keys []crypto.PublicKey e *openpgp.Entity err error } @@ -38,11 +37,7 @@ func (r VerifyResult) Verified() []sif.Descriptor { // Keys returns the public key(s) used to verify the signature. func (r VerifyResult) Keys() []crypto.PublicKey { - keys := make([]crypto.PublicKey, 0, len(r.aks)) - for _, ak := range r.aks { - keys = append(keys, ak.Public) - } - return keys + return r.keys } // Entity returns the signing entity, or nil if the signing entity could not be determined. diff --git a/pkg/integrity/select.go b/pkg/integrity/select.go index c8cda40e..2cbfbfe3 100644 --- a/pkg/integrity/select.go +++ b/pkg/integrity/select.go @@ -11,8 +11,11 @@ package integrity import ( "bytes" + "cmp" "errors" "fmt" + "math" + "slices" "sort" "github.com/ProtonMail/go-crypto/openpgp/clearsign" @@ -24,21 +27,18 @@ var ( errNoGroupsFound = errors.New("no groups found") ) -// insertSorted inserts unique vals into the sorted slice s. -func insertSorted(s []uint32, vals ...uint32) []uint32 { - for _, val := range vals { - val := val - - i := sort.Search(len(s), func(i int) bool { return s[i] >= val }) - if i < len(s) && s[i] == val { - continue - } +// insertSorted inserts v into the sorted slice s. If s already contains v, the original slice is +// returned. +func insertSorted[S ~[]E, E cmp.Ordered](s S, v E) S { //nolint:ireturn + return insertSortedFunc(s, v, cmp.Compare[E]) +} - s = append(s, 0) - copy(s[i+1:], s[i:]) - s[i] = val +// insertSorted inserts v into the sorted slice s, using comparison function cmp. If s already +// contains v, the original slice is returned. +func insertSortedFunc[S ~[]E, E any](s S, v E, cmp func(E, E) int) S { //nolint:ireturn + if i, found := slices.BinarySearchFunc(s, v, cmp); !found { + return slices.Insert(s, i, v) } - return s } @@ -147,20 +147,16 @@ func getGroupSignatures(f *sif.FileImage, groupID uint32, legacy bool) ([]sif.De // in the object group with identifier groupID. If no such object group is found, errGroupNotFound // is returned. func getGroupMinObjectID(f *sif.FileImage, groupID uint32) (uint32, error) { - minID := ^uint32(0) + var minID uint32 = math.MaxUint32 f.WithDescriptors(func(od sif.Descriptor) bool { - if od.GroupID() != groupID { - return false - } - - if id := od.ID(); id < minID { - minID = id + if od.GroupID() == groupID { + minID = min(minID, od.ID()) } return false }) - if minID == ^uint32(0) { + if minID == math.MaxUint32 { return 0, errGroupNotFound } return minID, nil diff --git a/pkg/integrity/sign.go b/pkg/integrity/sign.go index 628a1baa..75b7c9f4 100644 --- a/pkg/integrity/sign.go +++ b/pkg/integrity/sign.go @@ -17,7 +17,7 @@ import ( "errors" "fmt" "io" - "sort" + "slices" "time" "github.com/ProtonMail/go-crypto/openpgp" @@ -142,13 +142,7 @@ func (gs *groupSigner) addObject(od sif.Descriptor) error { } // Insert into sorted descriptor list, if not already present. - i := sort.Search(len(gs.ods), func(i int) bool { return gs.ods[i].ID() >= od.ID() }) - if i < len(gs.ods) && gs.ods[i].ID() == od.ID() { - return nil - } - gs.ods = append(gs.ods, sif.Descriptor{}) - copy(gs.ods[i+1:], gs.ods[i:]) - gs.ods[i] = od + gs.ods = insertSortedFunc(gs.ods, od, func(a, b sif.Descriptor) int { return int(a.ID()) - int(b.ID()) }) return nil } @@ -288,7 +282,7 @@ func withGroupedObjects(f *sif.FileImage, ids []uint32, fn func(uint32, []uint32 groupObjectIDs[groupID] = append(groupObjectIDs[groupID], id) } - sort.Slice(groupIDs, func(i, j int) bool { return groupIDs[i] < groupIDs[j] }) + slices.Sort(groupIDs) for _, groupID := range groupIDs { if err := fn(groupID, groupObjectIDs[groupID]); err != nil { @@ -347,11 +341,7 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) { var en encoder switch { case so.ss != nil: - var err error - en, err = newDSSEEncoder(so.ss) - if err != nil { - return nil, fmt.Errorf("integrity: %w", err) - } + en = newDSSEEncoder(so.ss) case so.e != nil: timeFunc := time.Now if so.timeFunc != nil { diff --git a/pkg/integrity/sign_test.go b/pkg/integrity/sign_test.go index 9c8172dc..8260ef8d 100644 --- a/pkg/integrity/sign_test.go +++ b/pkg/integrity/sign_test.go @@ -16,7 +16,7 @@ import ( "errors" "os" "path/filepath" - "reflect" + "slices" "testing" "github.com/ProtonMail/go-crypto/openpgp" @@ -72,7 +72,6 @@ func TestOptSignGroupObjects(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { gs := groupSigner{f: twoGroupImage, id: tt.groupID} @@ -86,7 +85,7 @@ func TestOptSignGroupObjects(t *testing.T) { for _, od := range gs.ods { got = append(got, od.ID()) } - if want := tt.ids; !reflect.DeepEqual(got, want) { + if want := tt.ids; !slices.Equal(got, want) { t.Errorf("got objects %v, want %v", got, want) } } @@ -199,7 +198,6 @@ func TestNewGroupSigner(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { en := newClearsignEncoder(getTestEntity(t), fixedTime) @@ -225,7 +223,7 @@ func TestNewGroupSigner(t *testing.T) { for _, od := range s.ods { got = append(got, od.ID()) } - if want := tt.wantObjects; !reflect.DeepEqual(got, want) { + if want := tt.wantObjects; !slices.Equal(got, want) { t.Errorf("got objects %v, want %v", got, want) } @@ -343,7 +341,6 @@ func TestGroupSigner_Sign(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { di, err := tt.gs.sign(context.Background()) if (err != nil) != tt.wantErr { @@ -538,7 +535,6 @@ func TestNewSigner(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { s, err := NewSigner(tt.fi, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { @@ -565,7 +561,7 @@ func TestNewSigner(t *testing.T) { got = append(got, od.ID()) } - if !reflect.DeepEqual(got, want) { + if !slices.Equal(got, want) { t.Errorf("got objects %v, want %v", got, want) } } @@ -831,8 +827,6 @@ func TestSigner_Sign(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join(corpus, tt.inputFile)) if err != nil { diff --git a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/ED25519.golden b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/ED25519.golden index 2e545c67..b69b23d7 100644 --- a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/ED25519.golden +++ b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/ED25519.golden @@ -1 +1 @@ -{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:x6l8ZblpSSXGaPMCzySedWg88BwIFcz8jlPb6el0mFs","sig":"SNnYRFIhDwWjk0pxoreaNiLea6L2WAFUm4boxnv7jiBNGmvMnbCxdsHYsTRBLXvMJHwEfKGvHFJmi9VvMe4JCQ=="}]} +{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:x6l8ZblpSSXGaPMCzySedWg88BwIFcz8jlPb6el0mFs","sig":"SNnYRFIhDwWjk0pxoreaNiLea6L2WAFUm4boxnv7jiBNGmvMnbCxdsHYsTRBLXvMJHwEfKGvHFJmi9VvMe4JCQ=="}]} \ No newline at end of file diff --git a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/Multi.golden b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/Multi.golden index b4322d20..a7e10585 100644 --- a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/Multi.golden +++ b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/Multi.golden @@ -1 +1 @@ -{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="},{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="}]} +{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="},{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="}]} \ No newline at end of file diff --git a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA256.golden b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA256.golden index 0bc3e8d6..33c03ca9 100644 --- a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA256.golden +++ b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA256.golden @@ -1 +1 @@ -{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="}]} +{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"qtxC0N3TWRUOOF4nAFwf8izZMVhpGca/s0STBi2h/OU/lND9M4uPG70LMGJ+n2GhCOyKKLR5BpgtlUkBpwhsxiPDqyyXFE2/Rvu/MsNicNIal7A1E64X3iOrMmaXK7qHDY6TpwC0KlxTOsh2XHJSM/cItgebkiRn5ZaZl48/10IzMsq/nOr0k9fGdAdgeApnRAQzBuHzcSAMpz8k9ovbyecfwuNLxXk6PO3isetpFx2j1d11gNfmwE54lCQ9ZGC3hiTJVt9WLBP+xC5AGoiX9f5FQpRzQrg9xGjyfwZDF4PSE9UFfUAC4fGPdultxUPXp8afWocJwbDgZBOkUKgE2L16LtMYSPFMdmAy615Ah6AOyudDTY+6iUr8D7YFdXgkjuQOGxtk7Wh2AIwk1lTOF4nrpycNjOJawBW5AFxdjEJ0LvG/XEJgSC88RoAkQ0YdN7j5N8nNf4+bZJ+CmTXPWU0MdFVDgI59bJKUJU/lt1WM/ZEIzujCgtqYKwCc8LNl5Fruh+2nHmtsAS3bxxPv51Nbw5d8T316SBp0bhjY+R7OncQDaP2FQ+nwpUXuDX3Tr9pqMJxDgErbIATOdSaRQ3KB1iC5gzTwIikuwPIxAuB2Gb5wWGxhqqfx7iA38TpnP5x8YXsjGCseUxFjrKoj5uL1p6ayGXOPJy/D9FsQVtA="}]} \ No newline at end of file diff --git a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA384.golden b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA384.golden index 46c14754..f8e82afe 100644 --- a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA384.golden +++ b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA384.golden @@ -1 +1 @@ -{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"p30wIIYvuxg1vLuVMSBCb9HlLjCJuHcW4ORi1GHSFzFxqLMkUVcGZvLotyA+25tInLqwX8SfTXkuIM1LnvwrrjOubi2Om8Cd9gyZqYP5Dx+BsPbAEmDgBkmV/2am6voKmpXuzNlXYocyCezw+Px+oxJI3bmFMCTjkFf5q4ah5HWvDMgfpa9T7oj13iS1WtLE2W6S5yuaIu/5gGQAWrxKKIhjAGEyS9+6I9VJEP2d2hJPDyey7YatM2Z7BfukVml/IBKoYo4cz50Y5fLfA+DstjpxQzQ0t/LyuntvsMVnOEH0n1C59aEku7RTFDVoA7GKvhSnWmGr4lD/33ZHeUDSNDWGoYo2rKeWUby+Z4Jyf27AbK2TrPvB3bxjIt7EoB4yuG1bmHgv6Agf7U43o8SZxo5mWEU/HOxulNRyE/W4quoVnD3slAIAfhDoOPo1flqaWFApPj4toyCuieQq2AheJxyP//crJnI+iLy3eUVWPELbSFpD4Gg2NKJ3PjiTx2XZndy5QVguAbjQy5RmMiFZ9Hk5qmV71wGDaBsGPKIDesM8CAXu3YOkhAEa36HYg9whgPsWDWjPe0VNzoN4UqXcjsb986n2M7AHVv+XHURf9MepRPy/pjShR0xUAeRFrZz8sBlhqaIXZcPwLBNSgZ91B5y/+VOy5aeOFTtfDG4nows="}]} +{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"p30wIIYvuxg1vLuVMSBCb9HlLjCJuHcW4ORi1GHSFzFxqLMkUVcGZvLotyA+25tInLqwX8SfTXkuIM1LnvwrrjOubi2Om8Cd9gyZqYP5Dx+BsPbAEmDgBkmV/2am6voKmpXuzNlXYocyCezw+Px+oxJI3bmFMCTjkFf5q4ah5HWvDMgfpa9T7oj13iS1WtLE2W6S5yuaIu/5gGQAWrxKKIhjAGEyS9+6I9VJEP2d2hJPDyey7YatM2Z7BfukVml/IBKoYo4cz50Y5fLfA+DstjpxQzQ0t/LyuntvsMVnOEH0n1C59aEku7RTFDVoA7GKvhSnWmGr4lD/33ZHeUDSNDWGoYo2rKeWUby+Z4Jyf27AbK2TrPvB3bxjIt7EoB4yuG1bmHgv6Agf7U43o8SZxo5mWEU/HOxulNRyE/W4quoVnD3slAIAfhDoOPo1flqaWFApPj4toyCuieQq2AheJxyP//crJnI+iLy3eUVWPELbSFpD4Gg2NKJ3PjiTx2XZndy5QVguAbjQy5RmMiFZ9Hk5qmV71wGDaBsGPKIDesM8CAXu3YOkhAEa36HYg9whgPsWDWjPe0VNzoN4UqXcjsb986n2M7AHVv+XHURf9MepRPy/pjShR0xUAeRFrZz8sBlhqaIXZcPwLBNSgZ91B5y/+VOy5aeOFTtfDG4nows="}]} \ No newline at end of file diff --git a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA512.golden b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA512.golden index 5ade7939..f9f2e7f5 100644 --- a/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA512.golden +++ b/pkg/integrity/testdata/Test_dsseEncoder_signMessage/RSA_SHA512.golden @@ -1 +1 @@ -{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"QgK1Nf31jAGUaF8uUHiTCQdRaXXh4tUvX1r/n8TPv+jKYhwKTrwYYMHol0gzo1m/OmhTn8XC3vZ1cuVyvhhhBjIgux2gee+7Rm9Vzu5WwDeH0haU1tgUXcGqqLSc7Udh1zkT8+peGSTytF1ZCPuDxYJhKe6b5ZkT8vbd6nC5zHOAe9PRAp+VoJg3okvQvdWgphHcdpFM4eF6h4qTi9A5BlKCkL4MCdfKJWg0T/aLNQLI7+eKxqk7mWPcraTXqPUnyptQ4z+pO6nocNaTYx6Ouh4vkiMKzokesQxTwZz+/80Y/Ig1hvcljR7VPFrCfInhKU5o7ljjB8CQEN+MaFqtFVsAatO6ttC0Msj7iozaj+Cqm0PwrysIfQaRWGkMgnFkMEhgO4aebBLNLoAVBf92oY06lNsIPQofNi6id7s0R8ch6fuQ0N/4GjmDRCfaYYS5pQuA8TYIVVBCkjzMLgjInD1QQV/MxSDRNPLOrKcnF8j6ub0iknWaDyuBsIzjIe0D4QDdOJDxzhC3G4cqtPXfnvfJaGs7n0t05Y2sT4Z/5mGKw2yAJRd4g7Ur1HawcLdN42Bt200C6yXovmpV6MZglNLUttfQBffZQOSB7nK9jkBtHZca7YsUQIas9sIBbRX3HLzNfnz3MDhjuquLo4w633ZPoY+IPmcs/3x3QVX3tU8="}]} +{"payloadType":"application/vnd.sylabs.sif-metadata+json","payload":"eyJPbmUiOjEsIlR3byI6Mn0K","signatures":[{"keyid":"SHA256:BhCwr7qZulYcOMSl2Jt2DuYHxHNnN6th4NdMqR/PGa4","sig":"QgK1Nf31jAGUaF8uUHiTCQdRaXXh4tUvX1r/n8TPv+jKYhwKTrwYYMHol0gzo1m/OmhTn8XC3vZ1cuVyvhhhBjIgux2gee+7Rm9Vzu5WwDeH0haU1tgUXcGqqLSc7Udh1zkT8+peGSTytF1ZCPuDxYJhKe6b5ZkT8vbd6nC5zHOAe9PRAp+VoJg3okvQvdWgphHcdpFM4eF6h4qTi9A5BlKCkL4MCdfKJWg0T/aLNQLI7+eKxqk7mWPcraTXqPUnyptQ4z+pO6nocNaTYx6Ouh4vkiMKzokesQxTwZz+/80Y/Ig1hvcljR7VPFrCfInhKU5o7ljjB8CQEN+MaFqtFVsAatO6ttC0Msj7iozaj+Cqm0PwrysIfQaRWGkMgnFkMEhgO4aebBLNLoAVBf92oY06lNsIPQofNi6id7s0R8ch6fuQ0N/4GjmDRCfaYYS5pQuA8TYIVVBCkjzMLgjInD1QQV/MxSDRNPLOrKcnF8j6ub0iknWaDyuBsIzjIe0D4QDdOJDxzhC3G4cqtPXfnvfJaGs7n0t05Y2sT4Z/5mGKw2yAJRd4g7Ur1HawcLdN42Bt200C6yXovmpV6MZglNLUttfQBffZQOSB7nK9jkBtHZca7YsUQIas9sIBbRX3HLzNfnz3MDhjuquLo4w633ZPoY+IPmcs/3x3QVX3tU8="}]} \ No newline at end of file diff --git a/pkg/integrity/verify.go b/pkg/integrity/verify.go index eecafe9a..bf74753c 100644 --- a/pkg/integrity/verify.go +++ b/pkg/integrity/verify.go @@ -18,7 +18,7 @@ import ( "errors" "fmt" "io" - "sort" + "slices" "strings" "github.com/ProtonMail/go-crypto/openpgp" @@ -576,9 +576,7 @@ func (v *Verifier) fingerprints(any bool) ([][]byte, error) { } } - sort.Slice(fps, func(i, j int) bool { - return bytes.Compare(fps[i], fps[j]) < 0 - }) + slices.SortFunc(fps, bytes.Compare) return fps, nil } diff --git a/pkg/integrity/verify_test.go b/pkg/integrity/verify_test.go index 0564819e..b7cc592a 100644 --- a/pkg/integrity/verify_test.go +++ b/pkg/integrity/verify_test.go @@ -1,12 +1,13 @@ // Copyright (c) Contributors to the Apptainer project, established as -// Apptainer a Series of LF Projects LLC. -// For website terms of use, trademark policy, privacy policy and other -// project policies see https://lfprojects.org/policies +// +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// // Copyright (c) 2020-2024, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file // distributed with the sources of this project regarding your rights to use or distribute this // software. - package integrity import ( @@ -55,7 +56,6 @@ func TestGroupVerifier_signatures(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { v := &groupVerifier{ f: tt.f, @@ -148,7 +148,6 @@ func TestGroupVerifier_verify(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { ods := make([]sif.Descriptor, len(tt.objectIDs)) for i, id := range tt.objectIDs { @@ -215,7 +214,6 @@ func TestLegacyGroupVerifier_signatures(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { v := &legacyGroupVerifier{ f: tt.f, @@ -283,7 +281,6 @@ func TestLegacyGroupVerifier_verify(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { ods, err := getGroupObjects(tt.f, tt.groupID) if err != nil { @@ -348,7 +345,6 @@ func TestLegacyObjectVerifier_signatures(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { od, err := tt.f.GetDescriptor(sif.WithID(tt.id)) if err != nil { @@ -424,7 +420,6 @@ func TestLegacyObjectVerifier_verify(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { od, err := tt.f.GetDescriptor(sif.WithID(tt.id)) if err != nil { @@ -681,7 +676,6 @@ func TestNewVerifier(t *testing.T) { //nolint:maintidx } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { v, err := NewVerifier(tt.fi, tt.opts...) if got, want := err, tt.wantErr; !errors.Is(got, want) { @@ -849,7 +843,6 @@ func TestVerifier_AnySignedBy(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { v := Verifier{tasks: tt.tasks} @@ -945,7 +938,6 @@ func TestVerifier_AllSignedBy(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { v := Verifier{tasks: tt.tasks} @@ -1082,7 +1074,6 @@ func TestVerifier_Verify(t *testing.T) { testCallback: true, wantCBSignature: sigPGP, wantCBVerified: verifiedPGP, - wantCBKeys: []crypto.PublicKey{}, wantCBEntity: e, }, { @@ -1094,14 +1085,12 @@ func TestVerifier_Verify(t *testing.T) { testCallback: true, ignoreError: true, wantCBSignature: sigPGP, - wantCBKeys: []crypto.PublicKey{}, wantCBEntity: nil, wantCBErr: &SignatureNotValidError{ID: 3}, }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { var vr VerifyResult diff --git a/pkg/sif/add.go b/pkg/sif/add.go new file mode 100644 index 00000000..096409ad --- /dev/null +++ b/pkg/sif/add.go @@ -0,0 +1,85 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// Copyright (c) 2017, SingularityWare, LLC. All rights reserved. +// Copyright (c) 2017, Yannick Cote All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "fmt" + "time" +) + +// addOpts accumulates object add options. +type addOpts struct { + t time.Time +} + +// AddOpt are used to specify object add options. +type AddOpt func(*addOpts) error + +// OptAddDeterministic sets header/descriptor fields to values that support deterministic +// modification of images. +func OptAddDeterministic() AddOpt { + return func(ao *addOpts) error { + ao.t = time.Time{} + return nil + } +} + +// OptAddWithTime specifies t as the image modification time. +func OptAddWithTime(t time.Time) AddOpt { + return func(ao *addOpts) error { + ao.t = t + return nil + } +} + +// AddObject adds a new data object and its descriptor into the specified SIF file. +// +// By default, the image modification time is set to the current time for non-deterministic images, +// and unset otherwise. To override this, consider using OptAddDeterministic or OptAddWithTime. +func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error { + ao := addOpts{} + + if !f.isDeterministic() { + ao.t = time.Now() + } + + for _, opt := range opts { + if err := opt(&ao); err != nil { + return fmt.Errorf("%w", err) + } + } + + // Find an unused descriptor. + i := 0 + for _, rd := range f.rds { + if !rd.Used { + break + } + i++ + } + + if err := f.writeDataObject(i, di, ao.t); err != nil { + return fmt.Errorf("%w", err) + } + + if err := f.writeDescriptors(); err != nil { + return fmt.Errorf("%w", err) + } + + f.h.ModifiedAt = ao.t.Unix() + + if err := f.writeHeader(); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/pkg/sif/add_test.go b/pkg/sif/add_test.go new file mode 100644 index 00000000..dd96cef8 --- /dev/null +++ b/pkg/sif/add_test.go @@ -0,0 +1,159 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2023, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "errors" + "testing" + "time" + + "github.com/sebdah/goldie/v2" +) + +func TestAddObject(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + di DescriptorInput + opts []AddOpt + wantErr error + }{ + { + name: "ErrInsufficientCapacity", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptorCapacity(0), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + wantErr: errInsufficientCapacity, + }, + { + name: "ErrPrimaryPartition", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + ), + ), + }, + di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsSquash, PartPrimSys, "amd64"), + ), + wantErr: errPrimaryPartition, + }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + opts: []AddOpt{ + OptAddDeterministic(), + }, + }, + { + name: "WithTime", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + opts: []AddOpt{ + OptAddWithTime(time.Unix(946702800, 0)), + }, + }, + { + name: "Empty", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + }, + { + name: "EmptyNotAligned", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, + OptObjectAlignment(0), + ), + }, + { + name: "EmptyAligned", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, + OptObjectAlignment(128), + ), + }, + { + name: "NotEmpty", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + }, + di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + ), + }, + { + name: "NotEmptyNotAligned", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + }, + di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + OptObjectAlignment(0), + ), + }, + { + name: "NotEmptyAligned", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + }, + di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + OptObjectAlignment(128), + ), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + if got, want := f.AddObject(tt.di, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} diff --git a/pkg/sif/create.go b/pkg/sif/create.go index 57c49554..332327b0 100644 --- a/pkg/sif/create.go +++ b/pkg/sif/create.go @@ -12,38 +12,49 @@ package sif import ( - "encoding" "encoding/binary" "errors" "fmt" "io" + "math" "os" "time" "github.com/google/uuid" ) +var errAlignmentOverflow = errors.New("integer overflow when calculating alignment") + // nextAligned finds the next offset that satisfies alignment. -func nextAligned(offset int64, alignment int) int64 { - align64 := uint64(alignment) - offset64 := uint64(offset) +func nextAligned(offset int64, alignment int) (int64, error) { + align64 := int64(alignment) + + if align64 <= 0 || offset%align64 == 0 { + return offset, nil + } + + align64 -= offset % align64 - if align64 != 0 && offset64%align64 != 0 { - offset64 = (offset64 & ^(align64 - 1)) + align64 + if (math.MaxInt64 - offset) < align64 { + return 0, errAlignmentOverflow } - return int64(offset64) + return offset + align64, nil } // writeDataObjectAt writes the data object described by di to ws, using time t, recording details // in d. The object is written at the first position that satisfies the alignment requirements // described by di following offsetUnaligned. func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorInput, t time.Time, d *rawDescriptor) error { //nolint:lll - offset, err := ws.Seek(nextAligned(offsetUnaligned, di.opts.alignment), io.SeekStart) + offset, err := nextAligned(offsetUnaligned, di.opts.alignment) if err != nil { return err } + if _, err := ws.Seek(offset, io.SeekStart); err != nil { + return err + } + n, err := io.Copy(ws, di.r) if err != nil { return err @@ -60,9 +71,24 @@ func writeDataObjectAt(ws io.WriteSeeker, offsetUnaligned int64, di DescriptorIn return nil } +// calculatedDataSize calculates the size of the data section based on the in-use descriptors. +func (f *FileImage) calculatedDataSize() int64 { + dataEnd := f.DataOffset() + + f.WithDescriptors(func(d Descriptor) bool { + if objectEnd := d.Offset() + d.Size(); dataEnd < objectEnd { + dataEnd = objectEnd + } + return false + }) + + return dataEnd - f.DataOffset() +} + var ( errInsufficientCapacity = errors.New("insufficient descriptor capacity to add data object(s) to image") errPrimaryPartition = errors.New("image already contains a primary partition") + errObjectIDOverflow = errors.New("object ID would overflow") ) // writeDataObject writes the data object described by di to f, using time t, recording details in @@ -72,6 +98,11 @@ func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) erro return errInsufficientCapacity } + // We derive the ID from i, so make sure the ID will not overflow. + if int64(i) >= math.MaxUint32 { + return errObjectIDOverflow + } + // If this is a primary partition, verify there isn't another primary partition, and update the // architecture in the global header. if p, ok := di.opts.md.(partition); ok && p.Parttype == PartPrimSys { @@ -83,7 +114,9 @@ func (f *FileImage) writeDataObject(i int, di DescriptorInput, t time.Time) erro } d := &f.rds[i] - d.ID = uint32(i) + 1 + d.ID = uint32(i) + 1 //nolint:gosec // Overflow handled above. + + f.h.DataSize = f.calculatedDataSize() if err := writeDataObjectAt(f.rw, f.h.DataOffset+f.h.DataSize, di, t, d); err != nil { return err @@ -202,8 +235,16 @@ func OptCreateWithCloseOnUnload(b bool) CreateOpt { } } +var errDescriptorCapacityNotSupported = errors.New("descriptor capacity not supported") + // createContainer creates a new SIF container file in rw, according to opts. func createContainer(rw ReadWriter, co createOpts) (*FileImage, error) { + // The supported number of descriptors is limited by the unsigned 32-bit ID field in each + // rawDescriptor. + if co.descriptorCapacity >= math.MaxUint32 { + return nil, errDescriptorCapacityNotSupported + } + rds := make([]rawDescriptor, co.descriptorCapacity) rdsSize := int64(binary.Size(rds)) @@ -325,378 +366,3 @@ func CreateContainerAtPath(path string, opts ...CreateOpt) (*FileImage, error) { f.closeOnUnload = true return f, nil } - -// addOpts accumulates object add options. -type addOpts struct { - t time.Time -} - -// AddOpt are used to specify object add options. -type AddOpt func(*addOpts) error - -// OptAddDeterministic sets header/descriptor fields to values that support deterministic -// modification of images. -func OptAddDeterministic() AddOpt { - return func(ao *addOpts) error { - ao.t = time.Time{} - return nil - } -} - -// OptAddWithTime specifies t as the image modification time. -func OptAddWithTime(t time.Time) AddOpt { - return func(ao *addOpts) error { - ao.t = t - return nil - } -} - -// AddObject adds a new data object and its descriptor into the specified SIF file. -// -// By default, the image modification time is set to the current time for non-deterministic images, -// and unset otherwise. To override this, consider using OptAddDeterministic or OptAddWithTime. -func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error { - ao := addOpts{} - - if !f.isDeterministic() { - ao.t = time.Now() - } - - for _, opt := range opts { - if err := opt(&ao); err != nil { - return fmt.Errorf("%w", err) - } - } - - // Find an unused descriptor. - i := 0 - for _, rd := range f.rds { - if !rd.Used { - break - } - i++ - } - - if err := f.writeDataObject(i, di, ao.t); err != nil { - return fmt.Errorf("%w", err) - } - - if err := f.writeDescriptors(); err != nil { - return fmt.Errorf("%w", err) - } - - f.h.ModifiedAt = ao.t.Unix() - - if err := f.writeHeader(); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// isLast return true if the data object associated with d is the last in f. -func (f *FileImage) isLast(d *rawDescriptor) bool { - isLast := true - - end := d.Offset + d.Size - f.WithDescriptors(func(d Descriptor) bool { - isLast = d.Offset()+d.Size() <= end - return !isLast - }) - - return isLast -} - -// zeroReader is an io.Reader that returns a stream of zero-bytes. -type zeroReader struct{} - -func (zeroReader) Read(b []byte) (int, error) { - for i := range b { - b[i] = 0 - } - return len(b), nil -} - -// zero overwrites the data object described by d with a stream of zero bytes. -func (f *FileImage) zero(d *rawDescriptor) error { - if _, err := f.rw.Seek(d.Offset, io.SeekStart); err != nil { - return err - } - - _, err := io.CopyN(f.rw, zeroReader{}, d.Size) - return err -} - -// truncateAt truncates f at the start of the padded data object described by d. -func (f *FileImage) truncateAt(d *rawDescriptor) error { - start := d.Offset + d.Size - d.SizeWithPadding - - return f.rw.Truncate(start) -} - -// deleteOpts accumulates object deletion options. -type deleteOpts struct { - zero bool - compact bool - t time.Time -} - -// DeleteOpt are used to specify object deletion options. -type DeleteOpt func(*deleteOpts) error - -// OptDeleteZero specifies whether the deleted object should be zeroed. -func OptDeleteZero(b bool) DeleteOpt { - return func(do *deleteOpts) error { - do.zero = b - return nil - } -} - -// OptDeleteCompact specifies whether the image should be compacted following object deletion. -func OptDeleteCompact(b bool) DeleteOpt { - return func(do *deleteOpts) error { - do.compact = b - return nil - } -} - -// OptDeleteDeterministic sets header/descriptor fields to values that support deterministic -// modification of images. -func OptDeleteDeterministic() DeleteOpt { - return func(do *deleteOpts) error { - do.t = time.Time{} - return nil - } -} - -// OptDeleteWithTime specifies t as the image modification time. -func OptDeleteWithTime(t time.Time) DeleteOpt { - return func(do *deleteOpts) error { - do.t = t - return nil - } -} - -var errCompactNotImplemented = errors.New("compact not implemented for non-last object") - -// DeleteObject deletes the data object with id, according to opts. -// -// To zero the data region of the deleted object, use OptDeleteZero. To compact the file following -// object deletion, use OptDeleteCompact. -// -// By default, the image modification time is set to the current time for non-deterministic images, -// and unset otherwise. To override this, consider using OptDeleteDeterministic or -// OptDeleteWithTime. -func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { - do := deleteOpts{} - - if !f.isDeterministic() { - do.t = time.Now() - } - - for _, opt := range opts { - if err := opt(&do); err != nil { - return fmt.Errorf("%w", err) - } - } - - d, err := f.getDescriptor(WithID(id)) - if err != nil { - return fmt.Errorf("%w", err) - } - - if do.compact && !f.isLast(d) { - return fmt.Errorf("%w", errCompactNotImplemented) - } - - if do.zero { - if err := f.zero(d); err != nil { - return fmt.Errorf("%w", err) - } - } - - if do.compact { - if err := f.truncateAt(d); err != nil { - return fmt.Errorf("%w", err) - } - - f.h.DataSize -= d.SizeWithPadding - } - - f.h.DescriptorsFree++ - f.h.ModifiedAt = do.t.Unix() - - // If we remove the primary partition, set the global header Arch field to HdrArchUnknown - // to indicate that the SIF file doesn't include a primary partition and no dependency - // on any architecture exists. - if d.isPartitionOfType(PartPrimSys) { - f.h.Arch = hdrArchUnknown - } - - // Reset rawDescripter with empty struct - *d = rawDescriptor{} - - if err := f.writeDescriptors(); err != nil { - return fmt.Errorf("%w", err) - } - - if err := f.writeHeader(); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// setOpts accumulates object set options. -type setOpts struct { - t time.Time -} - -// SetOpt are used to specify object set options. -type SetOpt func(*setOpts) error - -// OptSetDeterministic sets header/descriptor fields to values that support deterministic -// modification of images. -func OptSetDeterministic() SetOpt { - return func(so *setOpts) error { - so.t = time.Time{} - return nil - } -} - -// OptSetWithTime specifies t as the image/object modification time. -func OptSetWithTime(t time.Time) SetOpt { - return func(so *setOpts) error { - so.t = t - return nil - } -} - -var ( - errNotPartition = errors.New("data object not a partition") - errNotSystem = errors.New("data object not a system partition") -) - -// SetPrimPart sets the specified system partition to be the primary one. -// -// By default, the image/object modification times are set to the current time for -// non-deterministic images, and unset otherwise. To override this, consider using -// OptSetDeterministic or OptSetWithTime. -func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error { - so := setOpts{} - - if !f.isDeterministic() { - so.t = time.Now() - } - - for _, opt := range opts { - if err := opt(&so); err != nil { - return fmt.Errorf("%w", err) - } - } - - descr, err := f.getDescriptor(WithID(id)) - if err != nil { - return fmt.Errorf("%w", err) - } - - if descr.DataType != DataPartition { - return fmt.Errorf("%w", errNotPartition) - } - - var p partition - if err := descr.getExtra(binaryUnmarshaler{&p}); err != nil { - return fmt.Errorf("%w", err) - } - - // if already primary system partition, nothing to do - if p.Parttype == PartPrimSys { - return nil - } - - if p.Parttype != PartSystem { - return fmt.Errorf("%w", errNotSystem) - } - - // If there is currently a primary system partition, update it. - if d, err := f.getDescriptor(WithPartitionType(PartPrimSys)); err == nil { - var p partition - if err := d.getExtra(binaryUnmarshaler{&p}); err != nil { - return fmt.Errorf("%w", err) - } - - p.Parttype = PartSystem - - if err := d.setExtra(p); err != nil { - return fmt.Errorf("%w", err) - } - - d.ModifiedAt = so.t.Unix() - } else if !errors.Is(err, ErrObjectNotFound) { - return fmt.Errorf("%w", err) - } - - // Update the descriptor of the new primary system partition. - p.Parttype = PartPrimSys - - if err := descr.setExtra(p); err != nil { - return fmt.Errorf("%w", err) - } - - descr.ModifiedAt = so.t.Unix() - - if err := f.writeDescriptors(); err != nil { - return fmt.Errorf("%w", err) - } - - f.h.Arch = p.Arch - f.h.ModifiedAt = so.t.Unix() - - if err := f.writeHeader(); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// SetMetadata sets the metadata of the data object with id to md, according to opts. -// -// By default, the image/object modification times are set to the current time for -// non-deterministic images, and unset otherwise. To override this, consider using -// OptSetDeterministic or OptSetWithTime. -func (f *FileImage) SetMetadata(id uint32, md encoding.BinaryMarshaler, opts ...SetOpt) error { - so := setOpts{} - - if !f.isDeterministic() { - so.t = time.Now() - } - - for _, opt := range opts { - if err := opt(&so); err != nil { - return fmt.Errorf("%w", err) - } - } - - rd, err := f.getDescriptor(WithID(id)) - if err != nil { - return fmt.Errorf("%w", err) - } - - if err := rd.setExtra(md); err != nil { - return fmt.Errorf("%w", err) - } - - rd.ModifiedAt = so.t.Unix() - - if err := f.writeDescriptors(); err != nil { - return fmt.Errorf("%w", err) - } - - f.h.ModifiedAt = so.t.Unix() - - if err := f.writeHeader(); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} diff --git a/pkg/sif/create_test.go b/pkg/sif/create_test.go index c13bd20c..afd938bf 100644 --- a/pkg/sif/create_test.go +++ b/pkg/sif/create_test.go @@ -11,6 +11,7 @@ package sif import ( "errors" + "math" "os" "testing" "time" @@ -19,27 +20,34 @@ import ( ) func TestNextAligned(t *testing.T) { - cases := []struct { - name string - offset int64 - align int - expected int64 + tests := []struct { + name string + offset int64 + align int + wantOffset int64 + wantErr error }{ - {name: "align 0 to 0", offset: 0, align: 0, expected: 0}, - {name: "align 1 to 0", offset: 1, align: 0, expected: 1}, - {name: "align 0 to 1024", offset: 0, align: 1024, expected: 0}, - {name: "align 1 to 1024", offset: 1, align: 1024, expected: 1024}, - {name: "align 1023 to 1024", offset: 1023, align: 1024, expected: 1024}, - {name: "align 1024 to 1024", offset: 1024, align: 1024, expected: 1024}, - {name: "align 1025 to 1024", offset: 1025, align: 1024, expected: 2048}, + {name: "align 0 to 0", offset: 0, align: 0, wantOffset: 0}, + {name: "align 1 to 0", offset: 1, align: 0, wantOffset: 1}, + {name: "align 0 to 1024", offset: 0, align: 1024, wantOffset: 0}, + {name: "align 1 to 1024", offset: 1, align: 1024, wantOffset: 1024}, + {name: "align 1023 to 1024", offset: 1023, align: 1024, wantOffset: 1024}, + {name: "align 1024 to 1024", offset: 1024, align: 1024, wantOffset: 1024}, + {name: "align 1025 to 1024", offset: 1025, align: 1024, wantOffset: 2048}, + {name: "align max to 1024", offset: math.MaxInt64, align: 1024, wantErr: errAlignmentOverflow}, + {name: "align max to max", offset: math.MaxInt64, align: math.MaxInt - 1, wantErr: errAlignmentOverflow}, } - for _, tc := range cases { - actual := nextAligned(tc.offset, tc.align) - if actual != tc.expected { - t.Errorf("nextAligned case: %q, offset: %d, align: %d, expecting: %d, actual: %d\n", - tc.name, tc.offset, tc.align, tc.expected, actual) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + offset, err := nextAligned(tt.offset, tt.align) + if got, want := err, tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + if got, want := offset, tt.wantOffset; got != want { + t.Errorf("got offset %v, want %v", got, want) + } + }) } } @@ -182,6 +190,13 @@ func TestCreateContainerAtPath(t *testing.T) { opts []CreateOpt wantErr error }{ + { + name: "ErrDescriptorCapacityNotSupported", + opts: []CreateOpt{ + OptCreateWithDescriptorCapacity(math.MaxUint32), + }, + wantErr: errDescriptorCapacityNotSupported, + }, { name: "ErrInsufficientCapacity", opts: []CreateOpt{ @@ -290,510 +305,3 @@ func TestCreateContainerAtPath(t *testing.T) { }) } } - -func TestAddObject(t *testing.T) { - tests := []struct { - name string - createOpts []CreateOpt - di DescriptorInput - opts []AddOpt - wantErr error - }{ - { - name: "ErrInsufficientCapacity", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptorCapacity(0), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), - wantErr: errInsufficientCapacity, - }, - { - name: "ErrPrimaryPartition", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsSquash, PartPrimSys, "386"), - ), - ), - }, - di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsSquash, PartPrimSys, "amd64"), - ), - wantErr: errPrimaryPartition, - }, - { - name: "Deterministic", - createOpts: []CreateOpt{ - OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), - OptCreateWithTime(time.Unix(946702800, 0)), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - opts: []AddOpt{ - OptAddDeterministic(), - }, - }, - { - name: "WithTime", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - opts: []AddOpt{ - OptAddWithTime(time.Unix(946702800, 0)), - }, - }, - { - name: "Empty", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - }, - { - name: "EmptyNotAligned", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, - OptObjectAlignment(0), - ), - }, - { - name: "EmptyAligned", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, - OptObjectAlignment(128), - ), - }, - { - name: "NotEmpty", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsSquash, PartPrimSys, "386"), - ), - }, - { - name: "NotEmptyNotAligned", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsSquash, PartPrimSys, "386"), - OptObjectAlignment(0), - ), - }, - { - name: "NotEmptyAligned", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsSquash, PartPrimSys, "386"), - OptObjectAlignment(128), - ), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b Buffer - - f, err := CreateContainer(&b, tt.createOpts...) - if err != nil { - t.Fatal(err) - } - - if got, want := f.AddObject(tt.di, tt.opts...), tt.wantErr; !errors.Is(got, want) { - t.Errorf("got error %v, want %v", got, want) - } - - if err := f.UnloadContainer(); err != nil { - t.Error(err) - } - - g := goldie.New(t, goldie.WithTestNameForDir(true)) - g.Assert(t, tt.name, b.Bytes()) - }) - } -} - -func TestDeleteObject(t *testing.T) { - tests := []struct { - name string - createOpts []CreateOpt - id uint32 - opts []DeleteOpt - wantErr error - }{ - { - name: "ErrObjectNotFound", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - id: 1, - wantErr: ErrObjectNotFound, - }, - { - name: "Zero", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - id: 1, - opts: []DeleteOpt{ - OptDeleteZero(true), - }, - }, - { - name: "Compact", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - id: 1, - opts: []DeleteOpt{ - OptDeleteCompact(true), - }, - }, - { - name: "ZeroCompact", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - id: 1, - opts: []DeleteOpt{ - OptDeleteZero(true), - OptDeleteCompact(true), - }, - }, - { - name: "Deterministic", - createOpts: []CreateOpt{ - OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - OptCreateWithTime(time.Unix(946702800, 0)), - }, - id: 1, - opts: []DeleteOpt{ - OptDeleteDeterministic(), - }, - }, - { - name: "WithTime", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - ), - }, - id: 1, - opts: []DeleteOpt{ - OptDeleteWithTime(time.Unix(946702800, 0)), - }, - }, - { - name: "PrimaryPartition", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsSquash, PartPrimSys, "386"), - ), - ), - }, - id: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b Buffer - - f, err := CreateContainer(&b, tt.createOpts...) - if err != nil { - t.Fatal(err) - } - - if got, want := f.DeleteObject(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { - t.Errorf("got error %v, want %v", got, want) - } - - if err := f.UnloadContainer(); err != nil { - t.Error(err) - } - - g := goldie.New(t, goldie.WithTestNameForDir(true)) - g.Assert(t, tt.name, b.Bytes()) - }) - } -} - -func TestDeleteObjectAndAddObject(t *testing.T) { - tests := []struct { - name string - id uint32 - opts []DeleteOpt - }{ - { - name: "Compact", - id: 2, - opts: []DeleteOpt{ - OptDeleteCompact(true), - }, - }, - { - name: "NoCompact", - id: 2, - }, - { - name: "Zero", - id: 2, - opts: []DeleteOpt{ - OptDeleteZero(true), - }, - }, - { - name: "ZeroCompact", - id: 2, - opts: []DeleteOpt{ - OptDeleteZero(true), - OptDeleteCompact(true), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b Buffer - - f, err := CreateContainer(&b, - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataGeneric, []byte("abc")), - getDescriptorInput(t, DataGeneric, []byte("def")), - ), - ) - if err != nil { - t.Fatal(err) - } - - if err := f.DeleteObject(tt.id, tt.opts...); err != nil { - t.Fatal(err) - } - - if err := f.AddObject(getDescriptorInput(t, DataGeneric, []byte("ghi"))); err != nil { - t.Fatal(err) - } - - g := goldie.New(t, goldie.WithTestNameForDir(true)) - g.Assert(t, tt.name, b.Bytes()) - }) - } -} - -func TestSetPrimPart(t *testing.T) { - tests := []struct { - name string - createOpts []CreateOpt - id uint32 - opts []SetOpt - wantErr error - }{ - { - name: "ErrObjectNotFound", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - }, - id: 1, - wantErr: ErrObjectNotFound, - }, - { - name: "Deterministic", - createOpts: []CreateOpt{ - OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsRaw, PartSystem, "386"), - ), - ), - OptCreateWithTime(time.Unix(946702800, 0)), - }, - id: 1, - opts: []SetOpt{ - OptSetDeterministic(), - }, - }, - { - name: "WithTime", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsRaw, PartPrimSys, "386"), - ), - getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsRaw, PartSystem, "amd64"), - ), - ), - }, - id: 2, - opts: []SetOpt{ - OptSetWithTime(time.Unix(946702800, 0)), - }, - }, - { - name: "One", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsRaw, PartSystem, "386"), - ), - ), - }, - id: 1, - }, - { - name: "Two", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, - OptPartitionMetadata(FsRaw, PartPrimSys, "386"), - ), - getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, - OptPartitionMetadata(FsRaw, PartSystem, "amd64"), - ), - ), - }, - id: 2, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b Buffer - - f, err := CreateContainer(&b, tt.createOpts...) - if err != nil { - t.Fatal(err) - } - - if got, want := f.SetPrimPart(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { - t.Errorf("got error %v, want %v", got, want) - } - - if err := f.UnloadContainer(); err != nil { - t.Error(err) - } - - g := goldie.New(t, goldie.WithTestNameForDir(true)) - g.Assert(t, tt.name, b.Bytes()) - }) - } -} - -func TestSetMetadata(t *testing.T) { - tests := []struct { - name string - createOpts []CreateOpt - id uint32 - opts []SetOpt - wantErr error - }{ - { - name: "Deterministic", - createOpts: []CreateOpt{ - OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), - OptCreateWithDescriptors( - getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), - ), - OptCreateWithTime(time.Unix(946702800, 0)), - }, - id: 1, - opts: []SetOpt{ - OptSetDeterministic(), - }, - }, - { - name: "WithTime", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), - ), - }, - id: 1, - opts: []SetOpt{ - OptSetWithTime(time.Unix(946702800, 0)), - }, - }, - { - name: "One", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), - ), - }, - id: 1, - }, - { - name: "Two", - createOpts: []CreateOpt{ - OptCreateDeterministic(), - OptCreateWithDescriptors( - getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), - getDescriptorInput(t, DataOCIBlob, []byte{0xfe, 0xed}), - ), - }, - id: 2, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b Buffer - - f, err := CreateContainer(&b, tt.createOpts...) - if err != nil { - t.Fatal(err) - } - - if got, want := f.SetMetadata(tt.id, newOCIBlobDigest(), tt.opts...), tt.wantErr; !errors.Is(got, want) { - t.Errorf("got error %v, want %v", got, want) - } - - if err := f.UnloadContainer(); err != nil { - t.Error(err) - } - - g := goldie.New(t, goldie.WithTestNameForDir(true)) - g.Assert(t, tt.name, b.Bytes()) - }) - } -} diff --git a/pkg/sif/delete.go b/pkg/sif/delete.go new file mode 100644 index 00000000..d481a38b --- /dev/null +++ b/pkg/sif/delete.go @@ -0,0 +1,167 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. +// Copyright (c) 2017, SingularityWare, LLC. All rights reserved. +// Copyright (c) 2017, Yannick Cote All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "fmt" + "io" + "time" +) + +// zeroReader is an io.Reader that returns a stream of zero-bytes. +type zeroReader struct{} + +func (zeroReader) Read(b []byte) (int, error) { + clear(b) + return len(b), nil +} + +// zero overwrites the data object described by d with a stream of zero bytes. +func (f *FileImage) zero(d *rawDescriptor) error { + if _, err := f.rw.Seek(d.Offset, io.SeekStart); err != nil { + return err + } + + _, err := io.CopyN(f.rw, zeroReader{}, d.Size) + return err +} + +// deleteOpts accumulates object deletion options. +type deleteOpts struct { + zero bool + compact bool + t time.Time +} + +// DeleteOpt are used to specify object deletion options. +type DeleteOpt func(*deleteOpts) error + +// OptDeleteZero specifies whether the deleted object should be zeroed. +func OptDeleteZero(b bool) DeleteOpt { + return func(do *deleteOpts) error { + do.zero = b + return nil + } +} + +// OptDeleteCompact specifies whether the image should be compacted following object deletion. +func OptDeleteCompact(b bool) DeleteOpt { + return func(do *deleteOpts) error { + do.compact = b + return nil + } +} + +// OptDeleteDeterministic sets header/descriptor fields to values that support deterministic +// modification of images. +func OptDeleteDeterministic() DeleteOpt { + return func(do *deleteOpts) error { + do.t = time.Time{} + return nil + } +} + +// OptDeleteWithTime specifies t as the image modification time. +func OptDeleteWithTime(t time.Time) DeleteOpt { + return func(do *deleteOpts) error { + do.t = t + return nil + } +} + +// DeleteObject deletes the data object with id, according to opts. If no matching descriptor is +// found, an error wrapping ErrObjectNotFound is returned. +// +// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the +// end of the FileImage following object deletion, use OptDeleteCompact. +// +// By default, the image modification time is set to the current time for non-deterministic images, +// and unset otherwise. To override this, consider using OptDeleteDeterministic or +// OptDeleteWithTime. +func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { + return f.DeleteObjects(WithID(id), opts...) +} + +// DeleteObjects deletes the data objects selected by fn, according to opts. If no descriptors are +// selected by fns, an error wrapping ErrObjectNotFound is returned. +// +// To zero the data region of the deleted object, use OptDeleteZero. To remove unused space at the +// end of the FileImage following object deletion, use OptDeleteCompact. +// +// By default, the image modification time is set to the current time for non-deterministic images, +// and unset otherwise. To override this, consider using OptDeleteDeterministic or +// OptDeleteWithTime. +func (f *FileImage) DeleteObjects(fn DescriptorSelectorFunc, opts ...DeleteOpt) error { + do := deleteOpts{} + + if !f.isDeterministic() { + do.t = time.Now() + } + + for _, opt := range opts { + if err := opt(&do); err != nil { + return fmt.Errorf("%w", err) + } + } + + var selected bool + + if err := f.withDescriptors(fn, func(d *rawDescriptor) error { + selected = true + + if do.zero { + if err := f.zero(d); err != nil { + return fmt.Errorf("%w", err) + } + } + + f.h.DescriptorsFree++ + + // If we remove the primary partition, set the global header Arch field to HdrArchUnknown + // to indicate that the SIF file doesn't include a primary partition and no dependency + // on any architecture exists. + if d.isPartitionOfType(PartPrimSys) { + f.h.Arch = hdrArchUnknown + } + + // Reset rawDescripter with empty struct + *d = rawDescriptor{} + + return nil + }); err != nil { + return fmt.Errorf("%w", err) + } + + if !selected { + return fmt.Errorf("%w", ErrObjectNotFound) + } + + f.h.ModifiedAt = do.t.Unix() + + if do.compact { + f.h.DataSize = f.calculatedDataSize() + + if err := f.rw.Truncate(f.h.DataOffset + f.h.DataSize); err != nil { + return fmt.Errorf("%w", err) + } + } + + if err := f.writeDescriptors(); err != nil { + return fmt.Errorf("%w", err) + } + + if err := f.writeHeader(); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/pkg/sif/delete_test.go b/pkg/sif/delete_test.go new file mode 100644 index 00000000..e84d791d --- /dev/null +++ b/pkg/sif/delete_test.go @@ -0,0 +1,356 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "errors" + "testing" + "time" + + "github.com/sebdah/goldie/v2" +) + +func TestDeleteObject(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + ids []uint32 + opts []DeleteOpt + wantErr error + }{ + { + name: "ErrObjectNotFound", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + ids: []uint32{1}, + wantErr: ErrObjectNotFound, + }, + { + name: "Compact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1, 2}, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "OneZero", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteZero(true), + }, + }, + { + name: "OneCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "OneZeroCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteZero(true), + OptDeleteCompact(true), + }, + }, + { + name: "TwoZero", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{2}, + opts: []DeleteOpt{ + OptDeleteZero(true), + }, + }, + { + name: "TwoCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{2}, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "TwoZeroCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + ids: []uint32{2}, + opts: []DeleteOpt{ + OptDeleteZero(true), + OptDeleteCompact(true), + }, + }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteDeterministic(), + }, + }, + { + name: "WithTime", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + }, + ids: []uint32{1}, + opts: []DeleteOpt{ + OptDeleteWithTime(time.Unix(946702800, 0)), + }, + }, + { + name: "PrimaryPartition", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + ), + ), + }, + ids: []uint32{1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + for _, id := range tt.ids { + if got, want := f.DeleteObject(id, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} + +func TestDeleteObjects(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + fn DescriptorSelectorFunc + opts []DeleteOpt + wantErr error + }{ + { + name: "ErrObjectNotFound", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + fn: WithID(1), + wantErr: ErrObjectNotFound, + }, + { + name: "NilSelectFunc", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + fn: nil, + wantErr: errNilSelectFunc, + }, + { + name: "DataType", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + fn: WithDataType(DataGeneric), + }, + { + name: "DataTypeCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataGeneric, []byte{0xfe, 0xed}), + ), + }, + fn: WithDataType(DataGeneric), + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "PrimaryPartitionCompact", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsSquash, PartPrimSys, "386"), + ), + ), + }, + fn: WithPartitionType(PartPrimSys), + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + if got, want := f.DeleteObjects(tt.fn, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} + +func TestDeleteObjectAndAddObject(t *testing.T) { + tests := []struct { + name string + id uint32 + opts []DeleteOpt + }{ + { + name: "Compact", + id: 2, + opts: []DeleteOpt{ + OptDeleteCompact(true), + }, + }, + { + name: "NoCompact", + id: 2, + }, + { + name: "Zero", + id: 2, + opts: []DeleteOpt{ + OptDeleteZero(true), + }, + }, + { + name: "ZeroCompact", + id: 2, + opts: []DeleteOpt{ + OptDeleteZero(true), + OptDeleteCompact(true), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte("abc")), + getDescriptorInput(t, DataGeneric, []byte("def")), + ), + ) + if err != nil { + t.Fatal(err) + } + + if err := f.DeleteObject(tt.id, tt.opts...); err != nil { + t.Fatal(err) + } + + if err := f.AddObject(getDescriptorInput(t, DataGeneric, []byte("ghi"))); err != nil { + t.Fatal(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} diff --git a/pkg/sif/descriptor.go b/pkg/sif/descriptor.go index c4768652..270c74ad 100644 --- a/pkg/sif/descriptor.go +++ b/pkg/sif/descriptor.go @@ -96,7 +96,9 @@ func newOCIBlobDigest() *ociBlob { // MarshalBinary encodes ob into binary format. func (ob *ociBlob) MarshalBinary() ([]byte, error) { - ob.digest.Hex = hex.EncodeToString(ob.hasher.Sum(nil)) + if ob.digest.Hex == "" { + ob.digest.Hex = hex.EncodeToString(ob.hasher.Sum(nil)) + } return ob.digest.MarshalText() } diff --git a/pkg/sif/descriptor_input_test.go b/pkg/sif/descriptor_input_test.go index 3a1606de..3b5258c0 100644 --- a/pkg/sif/descriptor_input_test.go +++ b/pkg/sif/descriptor_input_test.go @@ -199,8 +199,6 @@ func TestNewDescriptorInput(t *testing.T) { }, } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/sif/descriptor_test.go b/pkg/sif/descriptor_test.go index 56f6fb37..01cbf19e 100644 --- a/pkg/sif/descriptor_test.go +++ b/pkg/sif/descriptor_test.go @@ -525,7 +525,6 @@ func TestDescriptor_GetIntegrityReader(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { d := Descriptor{ raw: rd, diff --git a/pkg/sif/load_test.go b/pkg/sif/load_test.go index 7451b241..ea03c209 100644 --- a/pkg/sif/load_test.go +++ b/pkg/sif/load_test.go @@ -37,8 +37,6 @@ func TestLoadContainerFromPath(t *testing.T) { }, } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { f, err := LoadContainerFromPath(tt.path, tt.opts...) if err != nil { @@ -70,8 +68,6 @@ func TestLoadContainer(t *testing.T) { }, } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { rw, err := os.Open(filepath.Join(corpus, "one-group.sif")) if err != nil { diff --git a/pkg/sif/select.go b/pkg/sif/select.go index 1d03aefc..d18faaff 100644 --- a/pkg/sif/select.go +++ b/pkg/sif/select.go @@ -188,10 +188,16 @@ func multiSelectorFunc(fns ...DescriptorSelectorFunc) DescriptorSelectorFunc { } } +var errNilSelectFunc = errors.New("descriptor selector func must not be nil") + // withDescriptors calls onMatchFn with each in-use descriptor in f for which selectFn returns // true. If selectFn or onMatchFn return a non-nil error, the iteration halts, and the error is // returned to the caller. func (f *FileImage) withDescriptors(selectFn DescriptorSelectorFunc, onMatchFn func(*rawDescriptor) error) error { + if selectFn == nil { + return errNilSelectFunc + } + for i, d := range f.rds { if !d.Used { continue diff --git a/pkg/sif/set.go b/pkg/sif/set.go new file mode 100644 index 00000000..d0a0d117 --- /dev/null +++ b/pkg/sif/set.go @@ -0,0 +1,224 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. +// Copyright (c) 2017, SingularityWare, LLC. All rights reserved. +// Copyright (c) 2017, Yannick Cote All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "encoding" + "errors" + "fmt" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +// setOpts accumulates object set options. +type setOpts struct { + t time.Time +} + +// SetOpt are used to specify object set options. +type SetOpt func(*setOpts) error + +// OptSetDeterministic sets header/descriptor fields to values that support deterministic +// modification of images. +func OptSetDeterministic() SetOpt { + return func(so *setOpts) error { + so.t = time.Time{} + return nil + } +} + +// OptSetWithTime specifies t as the image/object modification time. +func OptSetWithTime(t time.Time) SetOpt { + return func(so *setOpts) error { + so.t = t + return nil + } +} + +var ( + errNotPartition = errors.New("data object not a partition") + errNotSystem = errors.New("data object not a system partition") +) + +// SetPrimPart sets the specified system partition to be the primary one. +// +// By default, the image/object modification times are set to the current time for +// non-deterministic images, and unset otherwise. To override this, consider using +// OptSetDeterministic or OptSetWithTime. +func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error { + so := setOpts{} + + if !f.isDeterministic() { + so.t = time.Now() + } + + for _, opt := range opts { + if err := opt(&so); err != nil { + return fmt.Errorf("%w", err) + } + } + + descr, err := f.getDescriptor(WithID(id)) + if err != nil { + return fmt.Errorf("%w", err) + } + + if descr.DataType != DataPartition { + return fmt.Errorf("%w", errNotPartition) + } + + var p partition + if err := descr.getExtra(binaryUnmarshaler{&p}); err != nil { + return fmt.Errorf("%w", err) + } + + // if already primary system partition, nothing to do + if p.Parttype == PartPrimSys { + return nil + } + + if p.Parttype != PartSystem { + return fmt.Errorf("%w", errNotSystem) + } + + // If there is currently a primary system partition, update it. + if d, err := f.getDescriptor(WithPartitionType(PartPrimSys)); err == nil { + var p partition + if err := d.getExtra(binaryUnmarshaler{&p}); err != nil { + return fmt.Errorf("%w", err) + } + + p.Parttype = PartSystem + + if err := d.setExtra(p); err != nil { + return fmt.Errorf("%w", err) + } + + d.ModifiedAt = so.t.Unix() + } else if !errors.Is(err, ErrObjectNotFound) { + return fmt.Errorf("%w", err) + } + + // Update the descriptor of the new primary system partition. + p.Parttype = PartPrimSys + + if err := descr.setExtra(p); err != nil { + return fmt.Errorf("%w", err) + } + + descr.ModifiedAt = so.t.Unix() + + if err := f.writeDescriptors(); err != nil { + return fmt.Errorf("%w", err) + } + + f.h.Arch = p.Arch + f.h.ModifiedAt = so.t.Unix() + + if err := f.writeHeader(); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} + +// SetMetadata sets the metadata of the data object with id to md, according to opts. +// +// By default, the image/object modification times are set to the current time for +// non-deterministic images, and unset otherwise. To override this, consider using +// OptSetDeterministic or OptSetWithTime. +func (f *FileImage) SetMetadata(id uint32, md encoding.BinaryMarshaler, opts ...SetOpt) error { + so := setOpts{} + + if !f.isDeterministic() { + so.t = time.Now() + } + + for _, opt := range opts { + if err := opt(&so); err != nil { + return fmt.Errorf("%w", err) + } + } + + rd, err := f.getDescriptor(WithID(id)) + if err != nil { + return fmt.Errorf("%w", err) + } + + if err := rd.setExtra(md); err != nil { + return fmt.Errorf("%w", err) + } + + rd.ModifiedAt = so.t.Unix() + + if err := f.writeDescriptors(); err != nil { + return fmt.Errorf("%w", err) + } + + f.h.ModifiedAt = so.t.Unix() + + if err := f.writeHeader(); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} + +// SetOCIBlobDigest updates the digest of the OCI blob object with id to h, according to opts. +// +// By default, the image/object modification times are set to the current time for +// non-deterministic images, and unset otherwise. To override this, consider using +// OptSetDeterministic or OptSetWithTime. +func (f *FileImage) SetOCIBlobDigest(id uint32, h v1.Hash, opts ...SetOpt) error { + rd, err := f.getDescriptor(WithID(id)) + if err != nil { + return fmt.Errorf("%w", err) + } + + if got := rd.DataType; got != DataOCIRootIndex && got != DataOCIBlob { + return &unexpectedDataTypeError{got, []DataType{DataOCIRootIndex, DataOCIBlob}} + } + + so := setOpts{} + + if !f.isDeterministic() { + so.t = time.Now() + } + + for _, opt := range opts { + if err := opt(&so); err != nil { + return fmt.Errorf("%w", err) + } + } + + md := &ociBlob{ + digest: h, + } + if err := rd.setExtra(md); err != nil { + return fmt.Errorf("%w", err) + } + + rd.ModifiedAt = so.t.Unix() + + if err := f.writeDescriptors(); err != nil { + return fmt.Errorf("%w", err) + } + + f.h.ModifiedAt = so.t.Unix() + + if err := f.writeHeader(); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/pkg/sif/set_test.go b/pkg/sif/set_test.go new file mode 100644 index 00000000..fb680758 --- /dev/null +++ b/pkg/sif/set_test.go @@ -0,0 +1,317 @@ +// Copyright (c) Contributors to the Apptainer project, established as +// Apptainer a Series of LF Projects LLC. +// For website terms of use, trademark policy, privacy policy and other +// project policies see https://lfprojects.org/policies +// Copyright (c) 2018-2024, Sylabs Inc. All rights reserved. +// This software is licensed under a 3-clause BSD license. Please consult the +// LICENSE file distributed with the sources of this project regarding your +// rights to use or distribute this software. + +package sif + +import ( + "errors" + "testing" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sebdah/goldie/v2" +) + +func TestSetPrimPart(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + id uint32 + opts []SetOpt + wantErr error + }{ + { + name: "ErrObjectNotFound", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + }, + id: 1, + wantErr: ErrObjectNotFound, + }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsRaw, PartSystem, "386"), + ), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + id: 1, + opts: []SetOpt{ + OptSetDeterministic(), + }, + }, + { + name: "WithTime", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsRaw, PartPrimSys, "386"), + ), + getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsRaw, PartSystem, "amd64"), + ), + ), + }, + id: 2, + opts: []SetOpt{ + OptSetWithTime(time.Unix(946702800, 0)), + }, + }, + { + name: "One", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsRaw, PartSystem, "386"), + ), + ), + }, + id: 1, + }, + { + name: "Two", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsRaw, PartPrimSys, "386"), + ), + getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, + OptPartitionMetadata(FsRaw, PartSystem, "amd64"), + ), + ), + }, + id: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + if got, want := f.SetPrimPart(tt.id, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} + +func TestSetMetadata(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + id uint32 + opts []SetOpt + wantErr error + }{ + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + id: 1, + opts: []SetOpt{ + OptSetDeterministic(), + }, + }, + { + name: "WithTime", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + }, + id: 1, + opts: []SetOpt{ + OptSetWithTime(time.Unix(946702800, 0)), + }, + }, + { + name: "One", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + }, + id: 1, + }, + { + name: "Two", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataOCIBlob, []byte{0xfe, 0xed}), + ), + }, + id: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + if got, want := f.SetMetadata(tt.id, newOCIBlobDigest(), tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + }) + } +} + +func TestFileImage_SetOCIBlobDigest(t *testing.T) { + tests := []struct { + name string + createOpts []CreateOpt + id uint32 + h v1.Hash + opts []SetOpt + wantErr error + }{ + { + name: "UnexpectedDataType", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + }, + id: 1, + h: v1.Hash{ + Algorithm: "sha256", + Hex: "93a6ab73f77ce27501f74bb35af9b4da5b964c62f96175a1bc0e8ba2ae0dec08", + }, + wantErr: &unexpectedDataTypeError{DataGeneric, []DataType{DataOCIBlob, DataOCIRootIndex}}, + }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + id: 1, + h: v1.Hash{ + Algorithm: "sha256", + Hex: "93a6ab73f77ce27501f74bb35af9b4da5b964c62f96175a1bc0e8ba2ae0dec08", + }, + opts: []SetOpt{ + OptSetDeterministic(), + }, + }, + { + name: "WithTime", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + }, + id: 1, + h: v1.Hash{ + Algorithm: "sha256", + Hex: "93a6ab73f77ce27501f74bb35af9b4da5b964c62f96175a1bc0e8ba2ae0dec08", + }, + opts: []SetOpt{ + OptSetWithTime(time.Unix(946702800, 0)), + }, + }, + { + name: "One", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + ), + }, + id: 1, + h: v1.Hash{ + Algorithm: "sha256", + Hex: "93a6ab73f77ce27501f74bb35af9b4da5b964c62f96175a1bc0e8ba2ae0dec08", + }, + }, + { + name: "Two", + createOpts: []CreateOpt{ + OptCreateDeterministic(), + OptCreateWithDescriptors( + getDescriptorInput(t, DataOCIBlob, []byte{0xfa, 0xce}), + getDescriptorInput(t, DataOCIBlob, []byte{0xfe, 0xed}), + ), + }, + id: 2, + h: v1.Hash{ + Algorithm: "sha256", + Hex: "93a6ab73f77ce27501f74bb35af9b4da5b964c62f96175a1bc0e8ba2ae0dec08", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var b Buffer + + f, err := CreateContainer(&b, tt.createOpts...) + if err != nil { + t.Fatal(err) + } + + if got, want := f.SetOCIBlobDigest(tt.id, tt.h, tt.opts...), tt.wantErr; !errors.Is(got, want) { + t.Errorf("got error %v, want %v", got, want) + } + + if err := f.UnloadContainer(); err != nil { + t.Error(err) + } + + if tt.wantErr == nil { + g := goldie.New(t, goldie.WithTestNameForDir(true)) + g.Assert(t, tt.name, b.Bytes()) + } + }) + } +} diff --git a/pkg/sif/sif_test.go b/pkg/sif/sif_test.go index 64565160..c01130b4 100644 --- a/pkg/sif/sif_test.go +++ b/pkg/sif/sif_test.go @@ -92,7 +92,6 @@ func TestHeader_GetIntegrityReader(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { b := bytes.Buffer{} diff --git a/pkg/sif/testdata/TestDeleteObject/OneCompact.golden b/pkg/sif/testdata/TestDeleteObject/OneCompact.golden new file mode 100644 index 0000000000000000000000000000000000000000..cb86f62d5719233811f5bdc7e2527fed9897d562 GIT binary patch literal 32180 zcmeI$u?fOZ5C-6j2x8|ZJ~vPVC54p(IDu|p?>OQhZX#%FVPeQj8o|y&_^x|ANBwM literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestDeleteObject/OneZero.golden b/pkg/sif/testdata/TestDeleteObject/OneZero.golden new file mode 100644 index 0000000000000000000000000000000000000000..ec7f18be315a571430b57f408ff038f1365911de GIT binary patch literal 32180 zcmeI$u?Ye}5CzaV5yZ?+JUdVXFM%JDMiV7nHVN#xp6&~Jm(yjIYEE`0RjXF5FkK+009C7 z2oNCfCjygYL^W!cts)-X&bM;u>3UuD*!D1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0zVOW-QQ7d B9xea? literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden b/pkg/sif/testdata/TestDeleteObject/OneZeroCompact.golden new file mode 100644 index 0000000000000000000000000000000000000000..ec7f18be315a571430b57f408ff038f1365911de GIT binary patch literal 32180 zcmeI$u?Ye}5CzaV5yZ?+JUdVXFM%JDMiV7nHVN#xp6&~Jm(yjIYEE`0RjXF5FkK+009C7 z2oNCfCjygYL^W!cts)-X&bM;u>3UuD*!D1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0zVOW-QQ7d B9xea? literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestDeleteObject/TwoCompact.golden b/pkg/sif/testdata/TestDeleteObject/TwoCompact.golden new file mode 100644 index 0000000000000000000000000000000000000000..74b6489cc62b849d79558a5c92de01b40fb09315 GIT binary patch literal 32178 zcmeI#F$%&!5CzbQjo5h;*BdB;k|LD{@C14TdykWocouDK)`To6BG?HQZ(#Ybd>DA$ zM0}rPy+1YEL%oZ%j+8#ji_7hqbB>a$(ioe|;iEm5ybd`lJwbo~0RjXF5FkK+009C7 z2>f1PzKN)+Zcq0f{knE?ppS?8@&Z4c&nIpMt1Utdv3nV{D9z6KE z7x8@^o89TK**Dv$S5eoW#q-PU*>zo%T$YNla*-d}bIEJYX{i$g2oNAZfB*pk1PBly zK!CvS1!n7rs!Dy*e)R3yGN4cTOIkw?1OAx#+ev@`0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D*rMc-DA$ zM0}rPy+1YEL%oZ%j+8#ji_7hqbB>a$(ioe|;iEm5ybd`lJwbo~0RjXF5FkK+009C7 z2>f1PzKN)+Zcq0f{knE?ppS?8KwexQhh+<;gwmDiTBAYEGH7z|O69DJl42u8& diff --git a/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden b/pkg/sif/testdata/TestDeleteObjectAndAddObject/Zero.golden index bacfe7aff919f467af09461be1bef713e4bd987d..483ef82e1e456df6e124e5615b31adfe2edccaa6 100644 GIT binary patch delta 30 mcmdn_n{nH3#t9XSY?~(vwexQhh+<;gyg6DZBAYoqBNG6_DGO5o delta 33 pcmdn?n{nrF#t9XSoSP>KwexQhh+<;gwmDiTBAbnYfgwF369D1$3tRvI diff --git a/pkg/sif/testdata/TestDeleteObjects/DataType.golden b/pkg/sif/testdata/TestDeleteObjects/DataType.golden new file mode 100644 index 0000000000000000000000000000000000000000..7a656c7a97df4e2c586e7849535e3db3acf2c849 GIT binary patch literal 32180 zcmeIup$&sj5C-5Am^wyz9ibtiL7+MS6SN!DGY*rmNd*?cLja0|r2aeeCzt>B~HxsV_ literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden b/pkg/sif/testdata/TestDeleteObjects/DataTypeCompact.golden similarity index 100% rename from pkg/sif/testdata/TestDeleteObject/ZeroCompact.golden rename to pkg/sif/testdata/TestDeleteObjects/DataTypeCompact.golden diff --git a/pkg/sif/testdata/TestDeleteObject/Zero.golden b/pkg/sif/testdata/TestDeleteObjects/ErrObjectNotFound.golden similarity index 99% rename from pkg/sif/testdata/TestDeleteObject/Zero.golden rename to pkg/sif/testdata/TestDeleteObjects/ErrObjectNotFound.golden index 036b3690a02c7a69c1fd86dd0562ce64f7395f3f..01584e247040c983c82f5263aec325702f4a1d1c 100644 GIT binary patch delta 15 Xcmdn=n{mT$#t9W0+qG*a3h)8|K+^{C delta 17 Zcmdn+n{m@`#t9XSOdA{ZYbOeD0RTyv2JZj> diff --git a/pkg/sif/testdata/TestDeleteObjects/NilSelectFunc.golden b/pkg/sif/testdata/TestDeleteObjects/NilSelectFunc.golden new file mode 100644 index 0000000000000000000000000000000000000000..ab0878c84b73ac4bb4e9f1e1bf4db5755721cdfa GIT binary patch literal 32180 zcmeI#y$QlV7zW@kB8Z)nNZddXloVDD-~_sXz2gK2aT7sX3uAsRHiDhlJO|0kB_A9- zyv;>?U)y4}+bq__GV)pE`RDNXaJpAj6)C4_;ypR(K9u{E$1aCyP7okKfB*pk1PBly zK!5-N0>2j+%_Fj`KBs5Ft$B-J25fB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ SfB*pk1PBoLyTIe}yuJV)Z!9_h literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestDeleteObjects/PrimaryPartitionCompact.golden b/pkg/sif/testdata/TestDeleteObjects/PrimaryPartitionCompact.golden new file mode 100644 index 0000000000000000000000000000000000000000..01584e247040c983c82f5263aec325702f4a1d1c GIT binary patch literal 32176 zcmeIuu?;{_5C-6Dbw+tc5D8JJ3}6B_&>P2UCU^=|5{3G^=1(sFUGYVH&)B!qP0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ NfB*pk1PJ^QH~}0E5BLB8 literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Deterministic.golden b/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Deterministic.golden new file mode 100644 index 0000000000000000000000000000000000000000..2f46146046ca74c373d7abdd81a0301f12aa4fef GIT binary patch literal 32178 zcmeI$F;2rU6adha4Z*<1#uZ4?CTW;JMW7B$8~`P0B9hLUC-;%BW?YDgg!SCa^e{gcNci8U*VHAX6@V2?$OP}^1o*%dFZqM&8 zUvGxppA4&K*B|5Yczc#EoTuHGE=I#yyqd<9`MyYi009C72oNAZfB*pk1PJ`Qz}jvQ zv|8U~zUTX-+GwFcpEv4@jsE29GL4JVB2Lnsyj`S4nYEiNtLivQ!l=nQW!X-OCNDdK zA}RBAf}r%{#^QCWp`w=CkK4hMA=PX98N>0RzZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!CuX1wLQC E0Aa;5*8l(j literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/One.golden b/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/One.golden new file mode 100644 index 0000000000000000000000000000000000000000..8bf924d76d47a3a3038b148938128f885a83e808 GIT binary patch literal 32178 zcmeI#F-ikL6b9glo!EF2XE)j1td$}tDXctz%&fcEXk%^fadHyRqODC#$dV$0jR+~e z2g8Hmhd1+eFNELgZgG8odwH|C3b6<={tO>KzkRM&t1$MnF|kXYZZDQkV}ITDqcNuu zAV7cs0RjXF5FkK+009F3FK}`Z!ep|Zvpyf2am&30eYdFZE&7}6A=S%cDy#auou)eF zW;!%Y*OyHdi=mn2Jgw5u=GjB4a$C>3x*XcNXsT4?F81@B%GAe)zKioi`jWR0AV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U TAV7cs0RjXF5Fqe3fv@*(j{P!P literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Two.golden b/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/Two.golden new file mode 100644 index 0000000000000000000000000000000000000000..b96f80bc0fa0483007bdb6b12c2d12a148f6caed GIT binary patch literal 32180 zcmeI$KTg9i6vuHFu^<*sf^5fsVkQt0r~?xRK+kq87Fd|sIZnkvxCs)h43w6pU69z2 zTJ*J){FLNLc0PHFqtx%?dU|nlb#^&DSK278e-tN#tr)3@?tU8x!&M!a`kv? z&JVX@eK>KhT1j2iuH}%=0ZT;rA#n!yn zt}Nuck(SM}ZlvekZlu&nkQ}=fV;OAp+O+6$?!2^=T@{~Ga3$E5f{9*CE_&#*SQ&I3 zitcyiyRl9L5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL rKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0;rp9_4ve7=4GK8{qb literal 0 HcmV?d00001 diff --git a/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/WithTime.golden b/pkg/sif/testdata/TestFileImage_SetOCIBlobDigest/WithTime.golden new file mode 100644 index 0000000000000000000000000000000000000000..eab6e655c544db9842183b8167b09e3f8c5782ae GIT binary patch literal 32178 zcmeI$u}T9m7zgmHo4EKU=B~ZFo|7Ud9h`gsNv?Nvsf)9_zD=J*d=y<>YHLe}3J!wM z;TH%WB!5Wq+rDJk@8fQAeSdp-Gr7t_o`vu;eEjhIv0N_SUgqQV=Giu}OP+2P7V~X= z+SH?MP9Z>m009C72oNAZfB*pk1pZ&(b` zS)Dh-SjAKi`?_wsqAo+;*Q1n%W$c?Yn#M9U)u^qCzNzxMjCpE9H%_sLU6^)l`2NE< zq-VJc0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N c0t5&UAV7cs0RjXF5FkK+009Dj7x;Yr0&9URd;kCd literal 0 HcmV?d00001