diff --git a/Makefile b/Makefile index 9ddb61d94..4f8ddd580 100644 --- a/Makefile +++ b/Makefile @@ -359,6 +359,9 @@ conftest-test-cmd-diff: <(curl -s https://raw.githubusercontent.com/open-policy-agent/conftest/$${CONFTEST_VER}/internal/commands/test.go) \ cmd/test/test.go +fmt-all: + @git ls-files '*.go' | xargs gofmt -w + # Useful while hacking on build numbers and versions debug-version: @echo $(VERSION) diff --git a/acceptance/go.sum b/acceptance/go.sum index 60630c305..b6a9b8cef 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -17,8 +17,8 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -36,8 +36,8 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -276,8 +276,6 @@ github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yD github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -317,8 +315,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4= -github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= @@ -329,8 +325,6 @@ github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJ github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc= -github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= @@ -475,12 +469,12 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= +github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -773,8 +767,6 @@ github.com/sigstore/cosign/v3 v3.0.4 h1:SuEn9z8V0eyjF0PWxuGgQ7QSPWReNexLJovkZ3wL github.com/sigstore/cosign/v3 v3.0.4/go.mod h1:DJY5LPzHiI6bWpG/Q/NQUTfeASjkN8TDAUx1Nnt3I0I= github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= -github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= @@ -1212,8 +1204,6 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= -google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1253,12 +1243,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1274,8 +1262,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/docs/modules/ROOT/pages/ec_oci_blob_files.adoc b/docs/modules/ROOT/pages/ec_oci_blob_files.adoc new file mode 100644 index 000000000..366a46491 --- /dev/null +++ b/docs/modules/ROOT/pages/ec_oci_blob_files.adoc @@ -0,0 +1,21 @@ += ec.oci.blob_files + +Fetch structured files (YAML or JSON) from within a blob tar archive. + +== Usage + + files = ec.oci.blob_files(ref: string, paths: array) + +== Parameters + +* `ref` (`string`): OCI blob reference +* `paths` (`array`): the list of paths + +== Return + +`files` (`object`): object representing the extracted files + +The object contains dynamic attributes. +The attributes are of `string` type and represent the full path of the file within the blob. +The values are of `any` type and hold the file contents. + diff --git a/docs/modules/ROOT/pages/rego_builtins.adoc b/docs/modules/ROOT/pages/rego_builtins.adoc index cbbfaacb4..0a95c0642 100644 --- a/docs/modules/ROOT/pages/rego_builtins.adoc +++ b/docs/modules/ROOT/pages/rego_builtins.adoc @@ -10,6 +10,8 @@ information. |=== |xref:ec_oci_blob.adoc[ec.oci.blob] |Fetch a blob from an OCI registry. +|xref:ec_oci_blob_files.adoc[ec.oci.blob_files] +|Fetch structured files (YAML or JSON) from within a blob tar archive. |xref:ec_oci_descriptor.adoc[ec.oci.descriptor] |Fetch a raw Image from an OCI registry. |xref:ec_oci_image_files.adoc[ec.oci.image_files] diff --git a/docs/modules/ROOT/partials/rego_nav.adoc b/docs/modules/ROOT/partials/rego_nav.adoc index 1335fbeab..984603ff4 100644 --- a/docs/modules/ROOT/partials/rego_nav.adoc +++ b/docs/modules/ROOT/partials/rego_nav.adoc @@ -1,5 +1,6 @@ * xref:rego_builtins.adoc[Rego Reference] ** xref:ec_oci_blob.adoc[ec.oci.blob] +** xref:ec_oci_blob_files.adoc[ec.oci.blob_files] ** xref:ec_oci_descriptor.adoc[ec.oci.descriptor] ** xref:ec_oci_image_files.adoc[ec.oci.image_files] ** xref:ec_oci_image_index.adoc[ec.oci.image_index] diff --git a/go.mod b/go.mod index 712543213..8d9104a28 100644 --- a/go.mod +++ b/go.mod @@ -327,7 +327,6 @@ require ( github.com/prometheus/statsd_exporter v0.27.1 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20251016062345-16587c79cd91 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect diff --git a/go.sum b/go.sum index 87da268dc..eb1fdc0c2 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,6 @@ cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -45,12 +43,10 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= +cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -64,8 +60,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.57.1 h1:gzao6odNJ7dR3XXYvAgPK+Iw4fVPPznEPPyNjbaVkq8= cloud.google.com/go/storage v1.57.1/go.mod h1:329cwlpzALLgJuu8beyJ/uvQznDHpa2U5lGjWednkzg= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= +cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= @@ -492,8 +488,6 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665 h1:Iz3aEheYgn+//VX7VisgCmF/wW3BMtXCLbvHV4jMQJA= github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665/go.mod h1:19bUnum2ZAeftfwwLZ/wRe7idyfoW2MfmXO464Hrfbw= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -533,8 +527,6 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= -github.com/go-openapi/errors v0.22.5 h1:Yfv4O/PRYpNF3BNmVkEizcHb3uLVVsrDt3LNdgAKRY4= -github.com/go-openapi/errors v0.22.5/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo= github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= @@ -545,8 +537,6 @@ github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJ github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= -github.com/go-openapi/spec v0.22.2 h1:KEU4Fb+Lp1qg0V4MxrSCPv403ZjBl8Lx1a83gIPU8Qc= -github.com/go-openapi/spec v0.22.2/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= @@ -720,14 +710,10 @@ github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81z github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.9 h1:TOpi/QG8iDcZlkQlGlFUti/ZtyLkliXvHDcyUIMuFrU= github.com/googleapis/enterprise-certificate-proxy v0.3.9/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -916,8 +902,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -996,12 +980,8 @@ github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= -github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= github.com/olekukonko/ll v0.1.3 h1:sV2jrhQGq5B3W0nENUISCR6azIPf7UBUpVq0x/y70Fg= github.com/olekukonko/ll v0.1.3/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= -github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= -github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/olekukonko/tablewriter v1.1.2 h1:L2kI1Y5tZBct/O/TyZK1zIE9GlBj/TVs+AY5tZDCDSc= github.com/olekukonko/tablewriter v1.1.2/go.mod h1:z7SYPugVqGVavWoA2sGsFIoOVNmEHxUAAMrhXONtfkg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1100,9 +1080,6 @@ github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJ github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -1151,8 +1128,6 @@ github.com/sigstore/fulcio v1.8.4 h1:awmmItiPwteo8t8sVOoIAPnmbDfLb1JGW0LPY8SNCdY github.com/sigstore/fulcio v1.8.4/go.mod h1:2jh+uWOfWroKHlhUzr81AFqnAYeZiIi3NC/vegCbiYw= github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.4.3 h1:2+aw4Gbgumv8vYM/QVg6b+hvr4x4Cukur8stJrVPKU0= -github.com/sigstore/rekor v1.4.3/go.mod h1:o0zgY087Q21YwohVvGwV9vK1/tliat5mfnPiVI3i75o= github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= @@ -1176,8 +1151,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= -github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -1784,8 +1757,6 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc= -google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww= google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4= google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1837,8 +1808,6 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= -google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= @@ -1898,7 +1867,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= @@ -1964,8 +1932,6 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/release-utils v0.12.2 h1:H06v3FuLElAkf7Ikkd9ll8hnhdtQ+OgktJAni3iIAl8= -sigs.k8s.io/release-utils v0.12.2/go.mod h1:Ab9Lb/FpGUw4lUXj1QYbUcF2TRzll+GS7Md54W1G7sA= sigs.k8s.io/release-utils v0.12.3 h1:iNVJY81QfmMCmXxMg8IvvkkeQNk6ZWlLj+iPKSlKyVQ= sigs.k8s.io/release-utils v0.12.3/go.mod h1:BvbNmm1BmM3cnEpBmNHWL3wOSziOdGlsYR8vCFq/Q0o= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/internal/rego/oci/oci.go b/internal/rego/oci/oci.go index f21cfff2f..aa0cc87ed 100644 --- a/internal/rego/oci/oci.go +++ b/internal/rego/oci/oci.go @@ -21,6 +21,7 @@ package oci import ( + "archive/tar" "bytes" "context" "crypto/sha256" @@ -28,7 +29,9 @@ import ( "errors" "fmt" "io" + "path" "runtime" + "strings" "sync" "sync/atomic" @@ -42,6 +45,7 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/singleflight" "k8s.io/client-go/util/retry" + "sigs.k8s.io/yaml" "github.com/conforma/cli/internal/fetchers/oci/files" "github.com/conforma/cli/internal/utils/oci" @@ -49,13 +53,17 @@ import ( const ( ociBlobName = "ec.oci.blob" + ociBlobFilesName = "ec.oci.blob_files" ociDescriptorName = "ec.oci.descriptor" ociImageManifestName = "ec.oci.image_manifest" ociImageManifestsBatchName = "ec.oci.image_manifests" ociImageFilesName = "ec.oci.image_files" ociImageIndexName = "ec.oci.image_index" + maxTarEntrySizeConst = 500 * 1024 * 1024 // 500MB ) +var maxTarEntrySize int64 = maxTarEntrySizeConst // Use var to allow override in tests + func registerOCIBlob() { decl := rego.Function{ Name: ociBlobName, @@ -320,6 +328,35 @@ func registerOCIImageFiles() { rego.RegisterBuiltin2(&decl, ociImageFiles) } +func registerOCIBlobFiles() { + filesObject := types.NewObject( + nil, + types.NewDynamicProperty( + types.Named("path", types.S).Description("the full path of the file within the blob"), + types.Named("content", types.A).Description("the file contents"), + ), + ) + + decl := rego.Function{ + Name: ociBlobFilesName, + Description: "Fetch structured files (YAML or JSON) from within a blob tar archive.", + Decl: types.NewFunction( + types.Args( + types.Named("ref", types.S).Description("OCI blob reference"), + types.Named("paths", types.NewArray([]types.Type{types.S}, nil)).Description("the list of paths"), + ), + types.Named("files", filesObject).Description("object representing the extracted files"), + ), + // As per the documentation, enable memoization to ensure function evaluation is + // deterministic. But also mark it as non-deterministic because it does rely on external + // entities, i.e. OCI registry. https://www.openpolicyagent.org/docs/latest/extensions/ + Memoize: true, + Nondeterministic: true, + } + + rego.RegisterBuiltin2(&decl, ociBlobFiles) +} + func registerOCIImageIndex() { platform := types.NewObject( []*types.StaticProperty{ @@ -381,6 +418,10 @@ func registerOCIImageIndex() { } func ociBlob(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + return ociBlobInternal(bctx, a, true) +} + +func ociBlobInternal(bctx rego.BuiltinContext, a *ast.Term, verifyDigest bool) (*ast.Term, error) { logger := log.WithField("function", ociBlobName) uri, ok := a.Value.(ast.String) @@ -454,21 +495,36 @@ func ociBlob(bctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { return nil, nil //nolint:nilerr } - sum := fmt.Sprintf("sha256:%x", hasher.Sum(nil)) - // io.LimitReader truncates the layer if it exceeds its limit. The condition below catches this - // scenario in order to avoid unexpected behavior caused by partial data being returned. - if sum != ref.DigestStr() { - logger.WithFields(log.Fields{ - "action": "verify digest", - "computed_digest": sum, - "expected_digest": ref.DigestStr(), - }).Error("computed digest does not match expected digest") - return nil, nil + // In the past we used io.LimitReader which might truncate the layer if it + // exceeds its limit. The condition below catches this scenario in order + // to avoid unexpected behavior caused by partial data being returned. We + // don't actually use io.LimitReader here any more, but it seems like a + // reasonable idea to keep this digest check anyhow. Todo: Consider if we + // could/should remove the digest check entirely now. + // + // For ociBlobFiles, we skip the digest verification because there's a + // good chance we'd be calculating the digest of the uncompressed layer + // data which would not match. It might be possible to calculate the + // checksum on the layer data before it is uncompressed, but I think + // that's not as easy as it sounds, since it may require another + // io.Copy which could be inefficient. For now let's just skip it. + // + expectedDigest := ref.DigestStr() + if verifyDigest { + computedDigest := fmt.Sprintf("sha256:%x", hasher.Sum(nil)) + if computedDigest != expectedDigest { + logger.WithFields(log.Fields{ + "action": "verify digest", + "computed_digest": computedDigest, + "expected_digest": expectedDigest, + }).Error("computed digest does not match expected digest") + return nil, nil + } } logger.WithFields(log.Fields{ "action": "complete", - "digest": sum, + "digest": expectedDigest, }).Debug("Successfully retrieved blob") term := ast.StringTerm(blob.String()) @@ -686,10 +742,10 @@ func ClearCaches() { // Lighter caches (manifests, descriptors, image indexes) remain global because they // are small and benefit from cross-component sharing (e.g., shared task bundle manifests). type ComponentCache struct { - blobCache sync.Map - blobFlight singleflight.Group - imageFilesCache sync.Map - imageFilesFlight singleflight.Group + blobCache sync.Map + blobFlight singleflight.Group + filesCache sync.Map + filesFlight singleflight.Group } type componentCacheKey struct{} @@ -864,22 +920,22 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T // Build cache key from ref + paths (hash the paths for a stable key) pathsHash := fmt.Sprintf("%x", sha256.Sum256([]byte(pathsTerm.String())))[:12] - cacheKey := refStr + ":" + pathsHash + cacheKey := "image:" + refStr + ":" + pathsHash // Use component-scoped cache if available, otherwise fall back to global. // Image files data can be substantial and is unique per component. cc := componentCacheFromContext(bctx.Context) // Check cache first (fast path) - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Image files served from cache") return cached.(*ast.Term), nil } // Use singleflight to prevent thundering herd - result, err, _ := cc.imageFilesFlight.Do(cacheKey, func() (any, error) { + result, err, _ := cc.filesFlight.Do(cacheKey, func() (any, error) { // Double-check cache inside singleflight - if cached, found := cc.imageFilesCache.Load(cacheKey); found { + if cached, found := cc.filesCache.Load(cacheKey); found { logger.Debug("Image files served from cache (after singleflight)") return cached, nil } @@ -940,7 +996,214 @@ func ociImageFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.T logger.Debug("Successfully extracted image files") term := ast.NewTerm(filesValue) - cc.imageFilesCache.Store(cacheKey, term) + cc.filesCache.Store(cacheKey, term) + return term, nil + }) + + if err != nil || result == nil { + return nil, nil + } + return result.(*ast.Term), nil +} + +func ociBlobFiles(bctx rego.BuiltinContext, refTerm *ast.Term, pathsTerm *ast.Term) (*ast.Term, error) { + logger := log.WithField("function", ociBlobFilesName) + + uri, ok := refTerm.Value.(ast.String) + if !ok { + logger.Error("input ref is not a string") + return nil, nil + } + refStr := string(uri) + logger = logger.WithField("ref", refStr) + + if pathsTerm == nil { + logger.Error("paths term is nil") + return nil, nil + } + + // Build cache key from ref + paths (hash the paths for a stable key) + pathsHash := fmt.Sprintf("%x", sha256.Sum256([]byte(pathsTerm.String())))[:12] + cacheKey := "blob:" + refStr + ":" + pathsHash + + // Use component-scoped cache if available, otherwise fall back to global. + // Blob files data can be substantial and is unique per component. + cc := componentCacheFromContext(bctx.Context) + + // Check cache first (fast path) + if cached, found := cc.filesCache.Load(cacheKey); found { + logger.Debug("Blob files served from cache") + return cached.(*ast.Term), nil + } + + // Use singleflight to prevent thundering herd + result, err, _ := cc.filesFlight.Do(cacheKey, func() (any, error) { + // Double-check cache inside singleflight + if cached, found := cc.filesCache.Load(cacheKey); found { + logger.Debug("Blob files served from cache (after singleflight)") + return cached, nil + } + logger.Debug("Starting blob files extraction") + + // Get the blob content first (skip digest verification due to compressed/uncompressed mismatch) + blobTerm, err := ociBlobInternal(bctx, refTerm, false) + if err != nil || blobTerm == nil { + logger.WithFields(log.Fields{ + "action": "fetch blob", + "error": err, + }).Error("failed to fetch blob content") + return nil, nil //nolint:nilerr + } + + blobContent, ok := blobTerm.Value.(ast.String) + if !ok { + logger.Error("blob content is not a string") + return nil, nil + } + + pathsArray, err := builtins.ArrayOperand(pathsTerm.Value, 1) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert paths", + "error": err, + }).Error("failed to convert paths to array operand") + return nil, nil //nolint:nilerr + } + + // Collect target paths for exact file matching + var targetPaths []string + err = pathsArray.Iter(func(pathTerm *ast.Term) error { + pathString, ok := pathTerm.Value.(ast.String) + if !ok { + return fmt.Errorf("path is not a string: %#v", pathTerm) + } + targetPaths = append(targetPaths, string(pathString)) + return nil + }) + if err != nil { + logger.WithFields(log.Fields{ + "action": "iterate paths", + "error": err, + }).Error("failed iterating paths") + return nil, nil //nolint:nilerr + } + + if len(targetPaths) == 0 { + logger.Debug("No paths specified, returning empty result") + term := ast.NewTerm(ast.NewObject()) + cc.filesCache.Store(cacheKey, term) + return term, nil + } + + // Create a tar reader from the blob content + blobReader := strings.NewReader(string(blobContent)) + archive := tar.NewReader(blobReader) + + // Create a set for fast lookup of target paths + targetPathSet := make(map[string]bool) + for _, path := range targetPaths { + targetPathSet[path] = true + } + + extractedFiles := map[string]json.RawMessage{} + for { + header, err := archive.Next() + if err != nil { + if err == io.EOF { + break + } + logger.WithFields(log.Fields{ + "action": "read tar header", + "error": err, + }).Error("failed to read tar archive") + return nil, nil //nolint:nilerr + } + + // Check if this file matches any of our target paths + if !targetPathSet[header.Name] { + continue + } + + // Check if the file has a supported extension or is explicitly requested + ext := path.Ext(header.Name) + supportedExt := false + for _, e := range []string{".yaml", ".yml", ".json"} { + if strings.EqualFold(ext, e) { + supportedExt = true + break + } + } + + // If no supported extension, only process if file is explicitly in target paths + // This allows processing files without extensions that contain structured data + if !supportedExt { + logger.WithField("file", header.Name).Debug("file has no supported extension, attempting to parse anyway since it was explicitly requested") + } + + // Check file size to prevent memory exhaustion attacks + if header.Size > maxTarEntrySize { + logger.WithFields(log.Fields{ + "file": header.Name, + "size": header.Size, + "maxSize": maxTarEntrySize, + }).Error("tar entry too large, skipping to prevent memory exhaustion") + continue + } + + // Read the file content with size limit protection + // Note: This limit protection can't protect against all kinds of memory + // exhaustion attacks since we already loaded the full blobContent prior + // to this. I'm thinking let's keep it here anyhow since it maybe (?) + // can protect against certain kinds of attacks, and it's probably not + // doing any harm. That said, its value is questionable and we may want + // to revisit this later. + limitedReader := io.LimitReader(archive, maxTarEntrySize) + data, err := io.ReadAll(limitedReader) + if err != nil { + logger.WithFields(log.Fields{ + "action": "read file content", + "file": header.Name, + "error": err, + }).Error("failed to read file content") + return nil, nil //nolint:nilerr + } + + // Verify we didn't hit the size limit (which would indicate truncation) + if int64(len(data)) == maxTarEntrySize && header.Size > maxTarEntrySize { + logger.WithFields(log.Fields{ + "file": header.Name, + "size": header.Size, + "maxSize": maxTarEntrySize, + }).Error("tar entry was truncated due to size limit") + continue + } + + // Convert YAML to JSON if needed + data, err = yaml.YAMLToJSON(data) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert to json", + "file": header.Name, + "error": err, + }).Debug("unable to read file as JSON or YAML, ignoring") + continue + } + + extractedFiles[header.Name] = data + } + + filesValue, err := ast.InterfaceToValue(extractedFiles) + if err != nil { + logger.WithFields(log.Fields{ + "action": "convert files", + "error": err, + }).Error("failed to convert files object to value") + return nil, nil //nolint:nilerr + } + + logger.WithField("file_count", len(extractedFiles)).Debug("Successfully extracted blob files") + term := ast.NewTerm(filesValue) + cc.filesCache.Store(cacheKey, term) return term, nil }) @@ -1129,6 +1392,7 @@ func parseReference(uri string) (name.Reference, error) { func init() { registerOCIBlob() + registerOCIBlobFiles() registerOCIDescriptor() registerOCIImageFiles() registerOCIImageManifest() diff --git a/internal/rego/oci/oci_test.go b/internal/rego/oci/oci_test.go index 2cd93ccd3..7b56714bd 100644 --- a/internal/rego/oci/oci_test.go +++ b/internal/rego/oci/oci_test.go @@ -19,9 +19,13 @@ package oci import ( + "archive/tar" + "bytes" "context" + "crypto/sha256" "errors" "fmt" + "strings" "testing" "github.com/gkampitakis/go-snaps/snaps" @@ -116,6 +120,280 @@ func TestOCIBlob(t *testing.T) { } } +func TestOCIBlobFiles(t *testing.T) { + t.Cleanup(ClearCaches) + ClearCaches() // Clear before test to avoid interference from previous tests + + // Helper function to create a tar archive with test files and compute its digest + createTarArchiveWithDigest := func(files map[string]string) ([]byte, string) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + + for path, content := range files { + header := &tar.Header{ + Name: path, + Size: int64(len(content)), + } + require.NoError(t, tw.WriteHeader(header)) + _, err := tw.Write([]byte(content)) + require.NoError(t, err) + } + require.NoError(t, tw.Close()) + + data := buf.Bytes() + digest := fmt.Sprintf("sha256:%x", sha256.Sum256(data)) + return data, digest + } + + cases := []struct { + name string + tarFiles map[string]string + paths []string + expectedLen int + err bool + remoteErr error + invalidRef bool + nilPaths bool + invalidPath bool + oversized bool + }{ + { + name: "success with yaml file", + tarFiles: map[string]string{ + "task.yaml": `apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: example-task`, + "other.txt": "not a yaml file", + }, + paths: []string{"task.yaml"}, + expectedLen: 1, + }, + { + name: "success with multiple files", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "pipeline.json": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Pipeline"}`, + "other.txt": "not structured", + }, + paths: []string{"task.yaml", "pipeline.json"}, + expectedLen: 2, + }, + { + name: "no matching files", + tarFiles: map[string]string{ + "other.txt": "not a yaml file", + }, + paths: []string{"task.yaml"}, + expectedLen: 0, + }, + { + name: "invalid ref type", + tarFiles: map[string]string{}, + paths: []string{"task.yaml"}, + invalidRef: true, + err: true, + }, + { + name: "nil paths term", + tarFiles: map[string]string{}, + nilPaths: true, + err: true, + }, + { + name: "remote error", + tarFiles: map[string]string{}, + paths: []string{"task.yaml"}, + remoteErr: errors.New("blob fetch failed"), + err: true, + }, + { + name: "invalid path type in array", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1"}`, + }, + invalidPath: true, + err: true, + }, + { + name: "invalid json file ignored", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "invalid.json": `{{{malformed content that can't be parsed as JSON or YAML`, + "valid.json": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Pipeline"}`, + }, + paths: []string{"task.yaml", "invalid.json", "valid.json"}, + expectedLen: 2, // Only task.yaml and valid.json should be extracted, invalid.json is ignored + }, + { + name: "empty paths array", + tarFiles: map[string]string{ + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + }, + paths: []string{}, // Empty paths array + expectedLen: 0, // Should return empty object + }, + { + name: "file without supported extension", + tarFiles: map[string]string{ + "config": `{"apiVersion": "v1", "kind": "ConfigMap"}`, // No file extension but valid JSON + "task.yaml": `{"apiVersion": "tekton.dev/v1beta1", "kind": "Task"}`, + "readme.txt": `This is just text, not structured data`, + }, + paths: []string{"config", "task.yaml"}, // Explicitly request the extensionless file + expectedLen: 2, // Both config and task.yaml should be extracted + }, + { + name: "oversized tar entry skipped", + tarFiles: map[string]string{ + "small.yaml": `{"ok": true}`, // 12 bytes + "large.json": `{"data": "this will be considered oversized"}`, // 43 bytes, will be "too large" with limit set to 20 bytes + }, + paths: []string{"small.yaml", "large.json"}, + expectedLen: 1, // Only small.yaml should be extracted, large.json should be skipped due to temp limit + oversized: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + ClearCaches() // Clear cache before each subtest + + // Create test data and compute digest dynamically + var refTerm *ast.Term + var pathsTerm *ast.Term + + if c.invalidRef { + refTerm = ast.IntNumberTerm(42) + } else if c.nilPaths { + refTerm = ast.StringTerm("registry.local/bundle@sha256:dummy") + pathsTerm = nil + } else if c.invalidPath { + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + pathsTerm = ast.ArrayTerm(ast.IntNumberTerm(123)) // invalid path type + + client := fake.FakeClient{} + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.Nil(t, result) + return + } else if c.oversized { + // Temporarily override the size limit to make the test feasible with small files + originalLimit := maxTarEntrySize + maxTarEntrySize = 20 // Set a very small limit of 20 bytes + defer func() { + maxTarEntrySize = originalLimit // Restore original limit + }() + + // Create normal tar archive + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + + pathTerms := make([]*ast.Term, len(c.paths)) + for i, path := range c.paths { + pathTerms[i] = ast.StringTerm(path) + } + pathsTerm = ast.ArrayTerm(pathTerms...) + + client := fake.FakeClient{} + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.NotNil(t, result) + + // Verify the result is an object + obj, ok := result.Value.(ast.Object) + require.True(t, ok, "result should be an object") + + // Check that only the small file was extracted (large file should be skipped) + require.Equal(t, c.expectedLen, obj.Len(), "unexpected number of extracted files") + return + } else { + // Normal test cases + tarData, digest := createTarArchiveWithDigest(c.tarFiles) + refTerm = ast.StringTerm(fmt.Sprintf("registry.local/bundle@%s", digest)) + + pathTerms := make([]*ast.Term, len(c.paths)) + for i, path := range c.paths { + pathTerms[i] = ast.StringTerm(path) + } + pathsTerm = ast.ArrayTerm(pathTerms...) + + client := fake.FakeClient{} + if c.remoteErr != nil { + client.On("Layer", mock.Anything, mock.Anything).Return(nil, c.remoteErr) + } else { + layer := static.NewLayer(tarData, types.OCIUncompressedLayer) + client.On("Layer", mock.Anything, mock.Anything).Return(layer, nil) + } + ctx := oci.WithClient(context.Background(), &client) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + + if c.err { + require.Nil(t, result) + } else { + require.NotNil(t, result) + + // Verify the result is an object + obj, ok := result.Value.(ast.Object) + require.True(t, ok, "result should be an object") + + // Check the number of extracted files + require.Equal(t, c.expectedLen, obj.Len(), "unexpected number of extracted files") + + // For success cases with files, verify basic structure + if c.expectedLen > 0 { + // Iterate through the object using Keys() and Get() + keys := obj.Keys() + require.Len(t, keys, c.expectedLen, "unexpected number of keys") + + for _, key := range keys { + keyStr, ok := key.Value.(ast.String) + require.True(t, ok, "file path should be a string") + + // Verify it's one of the expected files + _, exists := c.tarFiles[string(keyStr)] + require.True(t, exists, "unexpected file: %s", string(keyStr)) + + // Get the value and verify it was parsed + value := obj.Get(key) + require.NotNil(t, value, "file content should not be nil") + + // For structured files, just verify the content exists and was processed + if strings.HasSuffix(string(keyStr), ".yaml") || strings.HasSuffix(string(keyStr), ".yml") || strings.HasSuffix(string(keyStr), ".json") { + // The content should be present (exact type checking is complex with AST) + require.NotNil(t, value.Value, "structured file should have parsed content") + } + } + } + } + return + } + + // Handle special cases (invalid ref, nil paths) + ctx := oci.WithClient(context.Background(), &fake.FakeClient{}) + bctx := rego.BuiltinContext{Context: ctx} + + result, err := ociBlobFiles(bctx, refTerm, pathsTerm) + require.NoError(t, err) + require.Nil(t, result) + }) + } +} + func TestOCIDescriptorManifest(t *testing.T) { t.Cleanup(ClearCaches) ClearCaches() @@ -963,6 +1241,7 @@ func TestOCIImageIndex(t *testing.T) { func TestFunctionsRegistered(t *testing.T) { names := []string{ ociBlobName, + ociBlobFilesName, ociDescriptorName, ociImageFilesName, ociImageManifestName,