diff --git a/go.mod b/go.mod index 1e1c2d26..f86e8097 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/go-git/go-billy/v5 v5.7.0 github.com/go-git/go-git/v5 v5.16.5 github.com/google/go-github/v50 v50.2.0 + github.com/jfrog/jfrog-client-go v1.52.0 github.com/julienschmidt/httprouter v1.3.0 github.com/masterminds/sprig v2.22.0+incompatible github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 @@ -50,10 +51,12 @@ require ( require ( code.cloudfoundry.org/tlsconfig v0.42.0 // indirect dario.cat/mergo v1.0.2 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect @@ -69,7 +72,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charlievieth/fs v0.0.3 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudfoundry/go-socks5 v0.0.0-20250423223041-4ad5fea42851 // indirect @@ -80,13 +82,15 @@ require ( github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/cppforlife/go-semi-semantic v0.0.0-20160921010311-576b6af77ae4 // indirect github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect - github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/forPelevin/gomoji v1.3.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -94,12 +98,15 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.5 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -114,11 +121,16 @@ require ( github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/jfrog/archiver/v3 v3.6.1 // indirect + github.com/jfrog/build-info-go v1.10.10 // indirect + github.com/jfrog/gofrog v1.7.6 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/klauspost/compress v1.18.3 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -127,34 +139,41 @@ require ( github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/nwaples/rardecode v1.1.3 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20 // indirect - github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sergi/go-diff v1.4.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/tools v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect diff --git a/go.sum b/go.sum index ca23a558..babfa4fe 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8af github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= +github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= @@ -21,6 +23,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -71,10 +75,10 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charlievieth/fs v0.0.3 h1:3lZQXTj4PbE81CVPwALSn+JoyCNXkZgORHN6h2XHGlg= github.com/charlievieth/fs v0.0.3/go.mod h1:hD4sRzto1Hw8zCua76tNVKZxaeZZr1RiKftjAJQRLLo= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= @@ -111,8 +115,8 @@ github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flf github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= -github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= -github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -125,6 +129,9 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -134,6 +141,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/forPelevin/gomoji v1.3.0 h1:WPIOLWB1bvRYlKZnSSEevLt3IfKlLs+tK+YA9fFYlkE= +github.com/forPelevin/gomoji v1.3.0/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -174,11 +183,15 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1 github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -193,6 +206,8 @@ github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/v github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= @@ -235,6 +250,14 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= +github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= +github.com/jfrog/build-info-go v1.10.10 h1:2nOFjV7SX1uisi2rQK7fb4Evm7YkSOdmssrm6Tf4ipc= +github.com/jfrog/build-info-go v1.10.10/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= +github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= +github.com/jfrog/jfrog-client-go v1.52.0 h1:MCmHviUqj3X7iqyOokTkyvV5yBWFwZYDPVXYikl4nf0= +github.com/jfrog/jfrog-client-go v1.52.0/go.mod h1:uRmT8Q1SJymIzId01v0W1o8mGqrRfrwUF53CgEMsH0U= github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -245,10 +268,14 @@ github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PW github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -275,6 +302,8 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1 h1:D4O2wLxB384TS3ohBJMfolnxb4qG github.com/maxbrunsfeld/counterfeiter/v6 v6.12.1/go.mod h1:RuJdxo0oI6dClIaMzdl3hewq3a065RH65dofJP03h8I= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -301,6 +330,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= +github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.5.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -317,6 +348,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= +github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pivotal-cf-experimental/gomegamatchers v0.0.0-20180326192815-e36bfcc98c3a h1:K20a2viyp6kZgY41ESLne0eOXyY9DarmwA4q6zQ686w= github.com/pivotal-cf-experimental/gomegamatchers v0.0.0-20180326192815-e36bfcc98c3a/go.mod h1:HdFegZwXOoRNyrqaOX6FC1zMkbA2k1/ktb2anj1E0K8= github.com/pivotal-cf/go-pivnet/v7 v7.0.3-0.20251210214834-422885a2f23e h1:rKR7vxvuQJChVzddSW/MFDxR4XAoehq5hBKezTPtV/g= @@ -327,8 +360,8 @@ github.com/pivotal-cf/om v0.0.0-20251215210555-e86ddeb670b9 h1:KzsnaBGK3+XPS3mxV github.com/pivotal-cf/om v0.0.0-20251215210555-e86ddeb670b9/go.mod h1:5+3rPqYSSSxSKLqFQEJA9EIVNqz5+UCnmakfxdbe4yw= github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20 h1:DR5eMfe2+6GzLkVyWytdtgUxgbPiOfvKDuqityTV3y8= github.com/pivotal-cf/paraphernalia v0.0.0-20180203224945-a64ae2051c20/go.mod h1:Y3IqE20LKprEpLkXb7gXinJf4vvDdQe/BS8E4kL/dgE= -github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= -github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -348,8 +381,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= -github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= -github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= @@ -382,6 +415,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= +github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -392,8 +427,23 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -401,26 +451,26 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0 h1:OXSUzgmIFkcC4An+mv+lqqZSndTffXpjAyoR+1f8k/A= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.64.0/go.mod h1:1A4GVLFIm54HFqVdOpWmukap7rgb0frrE3zWXohLPdM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.step.sm/crypto v0.75.0 h1:UAHYD6q6ggYyzLlIKHv1MCUVjZIesXRZpGTlRC/HSHw= @@ -437,6 +487,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/internal/commands/update_stemcell.go b/internal/commands/update_stemcell.go index 4b20dbb4..0f0ecb0a 100644 --- a/internal/commands/update_stemcell.go +++ b/internal/commands/update_stemcell.go @@ -90,13 +90,12 @@ func (update UpdateStemcell) Execute(args []string) error { } if err != nil { + if component.IsErrNotFound(err) { + return fmt.Errorf("couldn't find release %q", rel.Name) + } return fmt.Errorf("while finding release %q, encountered error: %w", rel.Name, err) } - if component.IsErrNotFound(err) { - return fmt.Errorf("couldn't find release %q", rel.Name) - } - if remote.RemotePath == rel.RemotePath && remote.RemoteSource == rel.RemoteSource { update.Logger.Printf("No change for release %q\n", rel.Name) diff --git a/internal/commands/update_stemcell_test.go b/internal/commands/update_stemcell_test.go index 35e7f157..48076016 100644 --- a/internal/commands/update_stemcell_test.go +++ b/internal/commands/update_stemcell_test.go @@ -615,13 +615,13 @@ var _ = Describe("UpdateStemcell", func() { releaseSource.GetMatchedReleaseReturns(cargo.BOSHReleaseTarballLock{}, component.ErrNotFound) }) - It("errors", func() { + It("returns a user-friendly not-found message", func() { err := update.Execute([]string{"--kilnfile", kilnfilePath, "--version", newStemcellVersion}) - Expect(err).To(MatchError(And( - ContainSubstring(component.ErrNotFound.Error()), - ContainSubstring(release1Name), - ))) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("couldn't find release")) + Expect(err.Error()).To(ContainSubstring(release1Name)) + Expect(err.Error()).NotTo(ContainSubstring("while finding release")) }) }) diff --git a/internal/component/artifactory_release_source.go b/internal/component/artifactory_release_source.go index 9b9826b8..16ebefe0 100644 --- a/internal/component/artifactory_release_source.go +++ b/internal/component/artifactory_release_source.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/sha1" "encoding/hex" - "encoding/json" "errors" "fmt" "io" @@ -18,12 +17,41 @@ import ( "regexp" "strings" "text/template" + "time" - "github.com/Masterminds/semver/v3" + "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/artifactory/auth" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/Masterminds/semver/v3" "github.com/pivotal-cf/kiln/pkg/cargo" ) +const ( + // https://github.com/Masterminds/semver/blob/1558ca3488226e3490894a145e831ad58a5ff958/version.go#L44 + semverRegex = `v?(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?` + + `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` + + `(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` + + reReleaseVersionGroup = "bosh_version" + reStemcellVersionGroup = "bosh_stemcell_version" +) + +type SearchResponseFile struct { + Repo string `json:"repo"` + Path string `json:"path"` + Name string `json:"name"` + Type string `json:"type"` + Size int `json:"size"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + SHA256 string `json:"sha256"` + SHA1 string `json:"actual_sha1"` +} + type ArtifactoryReleaseSource struct { cargo.ReleaseSourceConfig Client *http.Client @@ -31,25 +59,10 @@ type ArtifactoryReleaseSource struct { ID string } -type ArtifactoryFileMetadata struct { - Checksums struct { - Sha1 string `json:"sha1"` - Sha256 string `json:"sha256"` - MD5 string `json:"md5"` - } `json:"checksums"` -} - -type ArtifactoryFolderInfo struct { - Children []struct { - URI string `json:"uri"` - Folder bool `json:"folder"` - } `json:"children"` -} - -type ArtifactoryFileInfo struct { - Checksums struct { - SHA1 string `json:"sha1"` - } `json:"checksums"` +type ArtifactoryFile struct { + URI string `json:"uri"` + Folder bool `json:"folder"` + SHA1 string } // NewArtifactoryReleaseSource will provision a new ArtifactoryReleaseSource Project @@ -89,6 +102,7 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe if err != nil { return Local{}, err } + defer closeAndIgnoreError(resp.Body) if resp.StatusCode != http.StatusOK { return Local{}, fmt.Errorf("failed to download %s release from artifactory with error code %d", remoteRelease.Name, resp.StatusCode) @@ -104,9 +118,8 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe hash := sha1.New() - mw := io.MultiWriter(out,hash) + mw := io.MultiWriter(out, hash) _, err = io.Copy(mw, resp.Body) - _ = resp.Body.Close() if err != nil { return Local{}, err } @@ -116,31 +129,6 @@ func (ars *ArtifactoryReleaseSource) DownloadRelease(releaseDir string, remoteRe return Local{Lock: remoteRelease, LocalPath: filePath}, nil } -func (ars *ArtifactoryReleaseSource) getFileSHA1(release cargo.BOSHReleaseTarballLock) (string, error) { - fullURL := ars.ArtifactoryHost + "/api/storage/" + ars.Repo + "/" + release.RemotePath - - resp, err := ars.getWithAuth(fullURL) - if err != nil { - return "", err - } - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to get %s release info from artifactory with error code %d", release.Name, resp.StatusCode) - } - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - var artifactoryFileInfo ArtifactoryFileInfo - - if err := json.Unmarshal(responseBody, &artifactoryFileInfo); err != nil { - return "", fmt.Errorf("json is malformed: %w", err) - } - - return artifactoryFileInfo.Checksums.SHA1, nil -} - func (ars *ArtifactoryReleaseSource) Configuration() cargo.ReleaseSourceConfig { return ars.ReleaseSourceConfig } @@ -148,38 +136,9 @@ func (ars *ArtifactoryReleaseSource) Configuration() cargo.ReleaseSourceConfig { // GetMatchedRelease uses the Name and Version and if supported StemcellOS and StemcellVersion // fields on Requirement to download a specific release. func (ars *ArtifactoryReleaseSource) GetMatchedRelease(spec cargo.BOSHReleaseTarballSpecification) (cargo.BOSHReleaseTarballLock, error) { - remotePath, err := ars.RemotePath(spec) - if err != nil { - return cargo.BOSHReleaseTarballLock{}, err - } - - fullUrl := fmt.Sprintf("%s/%s/%s/%s", ars.ArtifactoryHost, "api/storage", ars.Repo, remotePath) - response, err := ars.getWithAuth(fullUrl) - if err != nil { - return cargo.BOSHReleaseTarballLock{}, err - } - defer func() { - _ = response.Body.Close() - }() - - switch response.StatusCode { - case http.StatusOK: - case http.StatusNotFound: - return cargo.BOSHReleaseTarballLock{}, ErrNotFound - default: - return cargo.BOSHReleaseTarballLock{}, fmt.Errorf("unexpected http status: %s", http.StatusText(response.StatusCode)) - } - - matchedRelease := cargo.BOSHReleaseTarballLock{ - Name: spec.Name, - Version: spec.Version, - RemotePath: remotePath, - RemoteSource: ars.ID, - } - - matchedRelease.SHA1, err = ars.getFileSHA1(matchedRelease) + matchedRelease, err := ars.findReleaseVersion(spec, spec) if err != nil { - return cargo.BOSHReleaseTarballLock{}, err + return cargo.BOSHReleaseTarballLock{}, wrapVPNError(err) } return matchedRelease, nil @@ -188,89 +147,94 @@ func (ars *ArtifactoryReleaseSource) GetMatchedRelease(spec cargo.BOSHReleaseTar // FindReleaseVersion may use any of the fields on Requirement to return the best matching // release. func (ars *ArtifactoryReleaseSource) FindReleaseVersion(spec cargo.BOSHReleaseTarballSpecification, _ bool) (cargo.BOSHReleaseTarballLock, error) { - remotePath, err := ars.RemotePath(spec) - if err != nil { - return cargo.BOSHReleaseTarballLock{}, err - } + searchSpec := spec + searchSpec.Version = "*" // we need to look at all available versions before deciding on the best match + foundRelease, err := ars.findReleaseVersion(spec, searchSpec) + return foundRelease, wrapVPNError(err) +} - fullUrl := fmt.Sprintf("%s/%s/%s/%s", ars.ArtifactoryHost, "api/storage", ars.Repo, path.Dir(remotePath)) +func (ars *ArtifactoryReleaseSource) findReleaseVersion(spec, searchSpec cargo.BOSHReleaseTarballSpecification) (cargo.BOSHReleaseTarballLock, error) { + if spec.StemcellOS != "" { + if spec.StemcellVersion == "" { + return cargo.BOSHReleaseTarballLock{}, errors.New("stemcell version is required when stemcell os is set") + } + } - response, err := ars.getWithAuth(fullUrl) + re, err := ars.regexPatternFromSpec(spec) if err != nil { return cargo.BOSHReleaseTarballLock{}, err } - defer func() { - _ = response.Body.Close() - }() - - switch response.StatusCode { - case http.StatusOK: - case http.StatusNotFound: - return cargo.BOSHReleaseTarballLock{}, ErrNotFound - default: - return cargo.BOSHReleaseTarballLock{}, fmt.Errorf("unexpected http status: %s", http.StatusText(response.StatusCode)) - } - - var artifactoryFolderInfo ArtifactoryFolderInfo - var _ *semver.Constraints - - responseBody, err := io.ReadAll(response.Body) + constraint, err := spec.VersionConstraints() if err != nil { return cargo.BOSHReleaseTarballLock{}, err } - - if err := json.Unmarshal(responseBody, &artifactoryFolderInfo); err != nil { - return cargo.BOSHReleaseTarballLock{}, fmt.Errorf("json from %s is malformed: %w", response.Request.URL.Host, err) - } - - semverPattern, err := regexp.Compile(`([-v])\d+(.\d+)*`) + remoteSearchPath, err := ars.RemotePath(searchSpec) if err != nil { return cargo.BOSHReleaseTarballLock{}, err } - - foundRelease := cargo.BOSHReleaseTarballLock{} - constraint, err := spec.VersionConstraints() + artifactoryFiles, err := ars.searchAql(remoteSearchPath) if err != nil { return cargo.BOSHReleaseTarballLock{}, err } - for _, releases := range artifactoryFolderInfo.Children { - if releases.Folder { + names := re.SubexpNames() + hasStemcellGroup := false + for _, name := range names { + if name == reStemcellVersionGroup { + hasStemcellGroup = true + break + } + } + + foundRelease := cargo.BOSHReleaseTarballLock{} + for _, artifactoryFile := range artifactoryFiles { + if artifactoryFile.Folder { + continue + } + matches := re.FindStringSubmatch(artifactoryFile.URI) + if matches == nil { continue } - versions := semverPattern.FindAllString(filepath.Base(releases.URI), -1) - version := versions[0] - stemcellVersion := versions[len(versions)-1] - version = strings.ReplaceAll(version, "-", "") - version = strings.ReplaceAll(version, "v", "") - stemcellVersion = strings.ReplaceAll(stemcellVersion, "-", "") - if len(versions) > 1 && stemcellVersion != spec.StemcellVersion { + matchedGroups := map[string]string{} + for i, n := range names { + if i == 0 || n == "" { + continue + } + matchedGroups[n] = matches[i] + } + + version := matchedGroups[reReleaseVersionGroup] + if hasStemcellGroup && matchedGroups[reStemcellVersionGroup] != spec.StemcellVersion { continue } + if version != "" { - newVersion, _ := semver.NewVersion(version) + newVersion, err := semver.NewVersion(version) + if err != nil { + continue + } if !constraint.Check(newVersion) { continue } - remotePathToUpdate := path.Dir(remotePath) + releases.URI - if (foundRelease == cargo.BOSHReleaseTarballLock{}) { foundRelease = cargo.BOSHReleaseTarballLock{ Name: spec.Name, Version: version, - RemotePath: remotePathToUpdate, + RemotePath: artifactoryFile.URI, RemoteSource: ars.ReleaseSourceConfig.ID, + SHA1: artifactoryFile.SHA1, } } else { - foundVersion, _ := semver.NewVersion(foundRelease.Version) + foundVersion, _ := semver.NewVersion(foundRelease.Version) // foundRelease.Version was already validated if newVersion.GreaterThan(foundVersion) { foundRelease = cargo.BOSHReleaseTarballLock{ Name: spec.Name, Version: version, - RemotePath: remotePathToUpdate, + RemotePath: artifactoryFile.URI, RemoteSource: ars.ReleaseSourceConfig.ID, + SHA1: artifactoryFile.SHA1, } } } @@ -281,12 +245,33 @@ func (ars *ArtifactoryReleaseSource) FindReleaseVersion(spec cargo.BOSHReleaseTa return cargo.BOSHReleaseTarballLock{}, ErrNotFound } - foundRelease.SHA1, err = ars.getFileSHA1(foundRelease) + return foundRelease, nil +} + +const ( + versionPlaceholder = "\x00VERSION_CAPTURE_GROUP\x00" + stemcellVersionPlaceholder = "\x00STEMCELL_VERSION_CAPTURE_GROUP\x00" +) + +func (ars *ArtifactoryReleaseSource) regexPatternFromSpec(spec cargo.BOSHReleaseTarballSpecification) (*regexp.Regexp, error) { + versionGroup := fmt.Sprintf(`(?P<%s>(%s))`, reReleaseVersionGroup, semverRegex) + stemcellVersionGroup := fmt.Sprintf(`(?P<%s>(%s))`, reStemcellVersionGroup, semverRegex) + + regexSpec := spec + regexSpec.Version = versionPlaceholder + regexSpec.StemcellVersion = stemcellVersionPlaceholder + + rawPath, err := ars.RemotePath(regexSpec) if err != nil { - return cargo.BOSHReleaseTarballLock{}, err + return nil, err } - return foundRelease, nil + escaped := regexp.QuoteMeta(rawPath) + escaped = strings.ReplaceAll(escaped, regexp.QuoteMeta(versionPlaceholder), versionGroup) + escaped = strings.ReplaceAll(escaped, regexp.QuoteMeta(stemcellVersionPlaceholder), stemcellVersionGroup) + + re, err := regexp.Compile("^" + escaped + "$") + return re, err } func (ars *ArtifactoryReleaseSource) RemotePath(spec cargo.BOSHReleaseTarballSpecification) (string, error) { @@ -319,6 +304,83 @@ func (ars *ArtifactoryReleaseSource) getWithAuth(url string) (*http.Response, er return response, wrapVPNError(err) } +func aqlQuery(repo, pathPattern string) string { + pathMatcher := path.Dir(pathPattern) + fileMatcher := path.Base(pathPattern) + return fmt.Sprintf(`{"repo": %[1]q, "$and": [ + { "path": { "$match": %[2]q } }, + { "name": { "$match": %[3]q } } + ]}`, repo, + pathMatcher, + fileMatcher) +} + +func (ars *ArtifactoryReleaseSource) searchAql(pathPattern string) ([]ArtifactoryFile, error) { + am, err := ars.buildArtifactoryServiceManager() + if err != nil { + return nil, err + } + + aql := utils.Aql{ItemsFind: aqlQuery(ars.Repo, pathPattern)} + cr, err := am.SearchFiles(services.SearchParams{ + CommonParams: &utils.CommonParams{ + Aql: aql, + }, + }) + if err != nil { + return nil, err + } + defer func() { _ = cr.Close() }() + + var files []SearchResponseFile + for { + var file SearchResponseFile + err = cr.NextRecord(&file) + if err != nil { + break + } + files = append(files, file) + } + if crErr := cr.GetError(); crErr != nil { + return nil, fmt.Errorf("reading AQL search results: %w", crErr) + } + + var arFiles []ArtifactoryFile + for _, result := range files { + arFiles = append(arFiles, ArtifactoryFile{ + URI: path.Join(result.Path, result.Name), + Folder: false, + SHA1: result.SHA1, + }) + } + return arFiles, nil +} + +func (ars *ArtifactoryReleaseSource) buildArtifactoryServiceManager() (artifactory.ArtifactoryServicesManager, error) { + rtDetails := auth.NewArtifactoryDetails() + rtDetails.SetUser(ars.Username) + rtDetails.SetPassword(ars.Password) + rtDetails.SetUrl(ars.ArtifactoryHost) + builder := jfroghttpclient.JfrogClientBuilder() + builder.SetHttpClient(ars.Client) + jfHttpClient, err := builder.Build() + if err != nil { + return nil, err + } + rtDetails.SetClient(jfHttpClient) + + configBuilder := config.NewConfigBuilder() + configuration, err := configBuilder.SetServiceDetails(rtDetails).SetHttpRetries(3).SetHttpRetryWaitMilliSecs(100).Build() + if err != nil { + return nil, err + } + am, err := artifactory.New(configuration) + if err != nil { + return nil, fmt.Errorf("creating artifactory service manager: %w", err) + } + return am, nil +} + type vpnError struct { wrapped error } @@ -336,5 +398,9 @@ func wrapVPNError(err error) error { if errors.As(err, &x) { return &vpnError{wrapped: err} } + // the jfrog api seems to discard type info + if err != nil && strings.Contains(err.Error(), "lookup :") { + return &vpnError{wrapped: err} + } return err } diff --git a/internal/component/artifactory_release_source_test.go b/internal/component/artifactory_release_source_test.go index ec540eba..2f6c8915 100644 --- a/internal/component/artifactory_release_source_test.go +++ b/internal/component/artifactory_release_source_test.go @@ -1,6 +1,7 @@ package component_test import ( + "encoding/json" "fmt" "io" "log" @@ -24,6 +25,17 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() { correctPassword = "mango_rice!" ) + type ApiStorageChildren struct { + Path string `json:"path"` + Repo string `json:"repo"` + Name string `json:"name"` + ActualSha1 string `json:"actual_sha1"` + Folder bool `json:"folder"` + } + type ApiStorageListing struct { + Children []ApiStorageChildren `json:"results"` + } + var ( source *component.ArtifactoryReleaseSource config cargo.ReleaseSourceConfig @@ -49,6 +61,15 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() { artifactoryRouter.NotFound = http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { log.Fatalf("handler on fake artifactory server not found not found for request: %s %s", req.Method, req.URL) }) + artifactoryRouter.Handler(http.MethodGet, "/artifactory/api/system/ping", http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + })) + artifactoryRouter.Handler(http.MethodGet, "/api/system/version", http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.Header().Set("Content-Type", "application/json") + res.WriteHeader(http.StatusOK) + _, err := res.Write([]byte(`{"version": "7.0.0", "revision": "0"}`)) + Expect(err).ToNot(HaveOccurred()) + })) }) JustBeforeEach(func() { logger := log.New(GinkgoWriter, "", 0) @@ -63,111 +84,604 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() { }) Describe("read operations", func() { - BeforeEach(func() { - requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + When("release files exist", func() { + When("the stemcell info is only in the path", func() { + BeforeEach(func() { + config.PathTemplate = "bosh-releases/{{.StemcellOS}}/{{.StemcellVersion}}/{{.Name}}/{{.Name}}-{{.Version}}.tgz" + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) - artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { - res.WriteHeader(http.StatusOK) - // language=json - _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) - }), requireAuth)) - artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { - res.WriteHeader(http.StatusOK) - // language=json - _, _ = io.WriteString(res, `{"children": [{"uri": "/mango-2.3.4-smoothie-9.9.tgz", "folder": false}]}`) - }), requireAuth)) - artifactoryRouter.Handler(http.MethodGet, "/artifactory/basket/bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { - res.WriteHeader(http.StatusOK) - f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) - if err != nil { - log.Fatal("failed to open some release test artifact") - } - defer closeAndIgnoreError(f) - _, _ = io.Copy(res, f) - }), requireAuth)) - }) - When("the server has the a file at the expected path", func() { - It("resolves the lock from the spec", func() { // testing GetMatchedRelease - resultLock, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ - Name: "mango", - Version: "2.3.4", - StemcellOS: "smoothie", - StemcellVersion: "9.9", + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "invalid", + "mango-2.3.3.tgz", + "mango-2.3.4-build.1.tgz", + "mango-2.3.4.tgz", + "mango-2.3.5.tgz", + "mango-2.3.4-build.2.tgz", + "mango-2.3.5-notices.zip", + "notices-mango-2.3.5.zip", + "orange-10.0.0.tgz", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases/smoothie/9.9/mango", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) }) + It("resolves the lock from the spec", func() { // testing GetMatchedRelease + resultLock, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }) - Expect(resultErr).NotTo(HaveOccurred()) - Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ - Name: "mango", - Version: "2.3.4", - // StemcellOS: "smoothie", - // StemcellVersion: "9.9", - RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", - RemoteSource: "some-mango-tree", - SHA1: "some-sha", - })) - }) + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + SHA1: "some-sha", + })) + }) - It("finds the bosh release", func() { // testing FindReleaseVersion - resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ - Name: "mango", - Version: "2.3.4", - StemcellOS: "smoothie", - StemcellVersion: "9.9", - }, false) + It("finds the bosh release", func() { // testing FindReleaseVersion + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "*", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) - Expect(resultErr).NotTo(HaveOccurred()) - Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ - Name: "mango", - Version: "2.3.4", - // StemcellOS: "smoothie", - // StemcellVersion: "9.9", - SHA1: "some-sha", - RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", - RemoteSource: "some-mango-tree", - })) - }) + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.5", + //StemcellOS: "smoothie", + //StemcellVersion: "9.9", + SHA1: "some-sha", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.5.tgz", + RemoteSource: "some-mango-tree", + })) + }) - It("downloads the release", func() { // teesting DownloadRelease - By("calling FindReleaseVersion") - local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ - Name: "mango", - Version: "2.3.4", - RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", - RemoteSource: "some-mango-tree", + It("downloads the release", func() { // teesting DownloadRelease + By("calling FindReleaseVersion") + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(local.LocalPath).To(BeAnExistingFile()) + }) + When("the server URL ends in /artifactory", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = server.URL + "/artifactory" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + + It("downloads the release", func() { + By("calling FindReleaseVersion") + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(local.LocalPath).To(BeAnExistingFile()) + }) }) + When("the server URL is malformed", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = ":improper-url/formatting" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + It("returns an error", func() { + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + }) - Expect(resultErr).NotTo(HaveOccurred()) - Expect(local.LocalPath).To(BeAnExistingFile()) + Expect(resultErr).To(HaveOccurred()) + Expect(local).To(Equal(component.Local{})) + }) + }) }) - When("the server URL ends in /artifactory", func() { - JustBeforeEach(func() { - logger := log.New(GinkgoWriter, "", 0) - config.ArtifactoryHost = server.URL + "/artifactory" - source = component.NewArtifactoryReleaseSource(config, logger) - source.Client = server.Client() + When("there is no stemcell configured", func() { + BeforeEach(func() { + config.PathTemplate = "bosh-releases/{{.Name}}-{{.Version}}.tgz" + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "invalid", + "mango-2.3.3.tgz", + "mango-2.3.4-build.1.tgz", + "mango-2.3.4.tgz", + "mango-2.3.4-build.2.tgz", + "mango-2.3.5-notices.zip", + "notices-mango-2.3.5.zip", + "orange-10.0.0.tgz", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + + It("resolves the lock from the spec", func() { // testing GetMatchedRelease + resultLock, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + RemotePath: "bosh-releases/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + SHA1: "some-sha", + })) + }) + + It("finds the bosh release", func() { // testing FindReleaseVersion + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + //StemcellOS: "smoothie", + //StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + SHA1: "some-sha", + RemotePath: "bosh-releases/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + })) }) - It("downloads the release", func() { + It("downloads the release", func() { // testing DownloadRelease By("calling FindReleaseVersion") local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ Name: "mango", Version: "2.3.4", - RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemotePath: "bosh-releases/mango-2.3.4.tgz", RemoteSource: "some-mango-tree", }) Expect(resultErr).NotTo(HaveOccurred()) Expect(local.LocalPath).To(BeAnExistingFile()) }) + + It("still finds the release when the spec has stemcell info from the lock file", func() { + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "*", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + SHA1: "some-sha", + RemotePath: "bosh-releases/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + })) + }) + + When("the server URL ends in /artifactory", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = server.URL + "/artifactory" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + + It("downloads the release", func() { + By("calling FindReleaseVersion") + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + RemotePath: "bosh-releases/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(local.LocalPath).To(BeAnExistingFile()) + }) + }) + When("the server URL is malformed", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = ":improper-url/formatting" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + It("returns an error", func() { + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).To(HaveOccurred()) + Expect(local).To(Equal(component.Local{})) + }) + }) }) - When("the server URL is malformed", func() { - JustBeforeEach(func() { - logger := log.New(GinkgoWriter, "", 0) - config.ArtifactoryHost = ":improper-url/formatting" - source = component.NewArtifactoryReleaseSource(config, logger) - source.Client = server.Client() - }) - It("returns an error", func() { + When("there are pre-releases and full releases", func() { + BeforeEach(func() { + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "mango-2.3.4-build.1-smoothie-9.9.tgz", + "mango-2.3.4-smoothie-9.9.tgz", + "mango-2.3.4-build.2-smoothie-9.9.tgz", + "mango-3.0.0-build.1-smoothie-9.9.tgz", + "mango-4.0.0-build.1.tgz", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases/smoothie/9.9/mango", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + When("we allow pre-releases", func() { + It("finds the latest version", func() { + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: ">0-0", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "3.0.0-build.1", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + SHA1: "some-sha", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-3.0.0-build.1-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + })) + }) + }) + When("we disallow pre-releases", func() { + It("finds the latest bosh version", func() { // testing FindReleaseVersion + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "*", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + SHA1: "some-sha", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + })) + }) + + }) + }) + When("there are only pre-releases", func() { + BeforeEach(func() { + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "mango-2.3.4-build.1-smoothie-9.9.tgz", + "mango-2.3.4-build.3-smoothie-9.9.tgz", + "mango-2.3.4-build.2-smoothie-9.9.tgz", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases/smoothie/9.9/mango", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + When("we allow pre-releases", func() { + It("finds the latest version", func() { // testing FindReleaseVersion + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: ">=0.0.0-build.0", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4-build.3", + SHA1: "some-sha", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-build.3-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + })) + }) + }) + When("dont allow pre-releases", func() { + It("returns ErrNotFound", func() { + _, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "*", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).To(HaveOccurred()) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) + }) + + }) + }) + When("there are pre and full releases and invalid files", func() { + BeforeEach(func() { + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "", + "invalid", + "mango-2.3.3-smoothie-9.9.tgz", + "mango-2.3.4-build.1-smoothie-9.9.tgz", + "mango-2.3.4-smoothie-9.9.tgz", + "mango-2.3.4-build.2-smoothie-9.9.tgz", + "mango-2.3.5-notices.zip", + "notices-mango-2.3.5.zip", + "orange-10.0.0-smoothie-9.9.tgz", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases/smoothie/9.9/mango", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + It("resolves the lock from the spec", func() { // testing GetMatchedRelease + resultLock, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + SHA1: "some-sha", + })) + }) + + It("finds the bosh release", func() { // testing FindReleaseVersion + resultLock, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(resultLock).To(Equal(cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + // StemcellOS: "smoothie", + // StemcellVersion: "9.9", + SHA1: "some-sha", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + })) + }) + + It("downloads the release", func() { // teesting DownloadRelease + By("calling FindReleaseVersion") local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ Name: "mango", Version: "2.3.4", @@ -175,14 +689,60 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() { RemoteSource: "some-mango-tree", }) - Expect(resultErr).To(HaveOccurred()) - Expect(local).To(Equal(component.Local{})) + Expect(resultErr).NotTo(HaveOccurred()) + Expect(local.LocalPath).To(BeAnExistingFile()) + }) + When("the server URL ends in /artifactory", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = server.URL + "/artifactory" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + + It("downloads the release", func() { + By("calling FindReleaseVersion") + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).NotTo(HaveOccurred()) + Expect(local.LocalPath).To(BeAnExistingFile()) + }) + }) + When("the server URL is malformed", func() { + JustBeforeEach(func() { + logger := log.New(GinkgoWriter, "", 0) + config.ArtifactoryHost = ":improper-url/formatting" + source = component.NewArtifactoryReleaseSource(config, logger) + source.Client = server.Client() + }) + It("returns an error", func() { + local, resultErr := source.DownloadRelease(releasesDirectory, cargo.BOSHReleaseTarballLock{ + Name: "mango", + Version: "2.3.4", + RemotePath: "bosh-releases/smoothie/9.9/mango/mango-2.3.4-smoothie-9.9.tgz", + RemoteSource: "some-mango-tree", + }) + + Expect(resultErr).To(HaveOccurred()) + Expect(local).To(Equal(component.Local{})) + }) }) }) }) }) When("not behind the corporate firewall", func() { + BeforeEach(func() { + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + }), requireAuth)) + }) JustBeforeEach(func() { source.Client.Transport = dnsFailure{} }) @@ -224,22 +784,173 @@ var _ = Describe("interacting with BOSH releases on Artifactory", func() { }) }) + Describe("regex pattern matching", func() { + When("a release name contains a dot and only a non-matching file is returned", func() { + BeforeEach(func() { + config.PathTemplate = "bosh-releases/{{.Name}}-{{.Version}}.tgz" + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases", + Name: "myXrelease-2.3.4.tgz", + ActualSha1: "some-sha", + }) + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + + It("does not match a file where the dot is replaced by another character", func() { + _, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "my.release", + Version: "2.3.4", + }) + + Expect(resultErr).To(HaveOccurred()) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) + }) + }) + + When("a file extension dot is replaced by another character", func() { + BeforeEach(func() { + config.PathTemplate = "bosh-releases/{{.Name}}-{{.Version}}.tgz" + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases", + Name: "mango-2.3.4Xtgz", + ActualSha1: "some-sha", + }) + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + + It("does not match the file", func() { + _, resultErr := source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + }) + + Expect(resultErr).To(HaveOccurred()) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) + }) + }) + }) + When("a bosh release is not found", func() { - BeforeEach(func() { - artifactoryRouter.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = fmt.Fprintln(w, `{"errors":[{"status":404,"message":"File not found."}]}`) + When("there are no files", func() { + BeforeEach(func() { + apiStorageListing := ApiStorageListing{} + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusNotFound) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + It("returns ErrNotFound", func() { + _, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "missing-release", + Version: "1.2.3", + StemcellOS: "ubuntu-jammy", + StemcellVersion: "1.234", + }, false) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) + + _, resultErr = source.GetMatchedRelease(cargo.BOSHReleaseTarballSpecification{ + Name: "missing-release", + Version: "1.2.3", + StemcellOS: "ubuntu-jammy", + StemcellVersion: "1.234", + }) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) }) }) - It("returns ErrNotFound", func() { - _, err := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ - Name: "missing-release", - Version: "1.2.3", - StemcellOS: "ubuntu-jammy", - StemcellVersion: "1.234", - }, false) - - Expect(component.IsErrNotFound(err)).To(BeTrue()) + When("there are invalid files", func() { + BeforeEach(func() { + requireAuth := requireBasicAuthMiddleware(correctUsername, correctPassword) + + apiStorageListing := ApiStorageListing{} + for _, filename := range []string{ + "", + "invalid", + "mango-2.3.4-invalid.zip", + "mango-2.3.4-invalid-9.9.zip", + "mango-2.3.4-invalid-smoothie-9.9.zip", + "invalid-mango-2.3.4.zip", + } { + apiStoragePath := fmt.Sprintf("/api/storage/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, apiStoragePath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.WriteString(res, `{"checksums": {"sha1": "some-sha"}}`) + }), requireAuth)) + + downloadPath := fmt.Sprintf("/artifactory/basket/bosh-releases/smoothie/9.9/mango/%s", filename) + artifactoryRouter.Handler(http.MethodGet, downloadPath, applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + f, err := os.Open(filepath.Join("testdata", "some-release.tgz")) + if err != nil { + log.Fatal("failed to open some release test artifact") + } + defer closeAndIgnoreError(f) + _, _ = io.Copy(res, f) + }), requireAuth)) + + apiStorageListing.Children = append(apiStorageListing.Children, ApiStorageChildren{ + Path: "bosh-releases/smoothie/9.9/mango", + Name: filename, + ActualSha1: "some-sha", + }) + } + + apiStorageListingBytes, err := json.Marshal(apiStorageListing) + Expect(err).NotTo(HaveOccurred()) + + artifactoryRouter.Handler(http.MethodGet, "/api/storage/basket/bosh-releases/smoothie/9.9/mango", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + + artifactoryRouter.Handler(http.MethodPost, "/api/search/aql", applyMiddleware(http.HandlerFunc(func(res http.ResponseWriter, _ *http.Request) { + res.WriteHeader(http.StatusOK) + // language=json + _, _ = io.Writer.Write(res, apiStorageListingBytes) + }), requireAuth)) + }) + It("returns ErrNotFound", func() { // testing FindReleaseVersion + _, resultErr := source.FindReleaseVersion(cargo.BOSHReleaseTarballSpecification{ + Name: "mango", + Version: "2.3.4", + StemcellOS: "smoothie", + StemcellVersion: "9.9", + }, false) + + Expect(resultErr).To(HaveOccurred()) + Expect(component.IsErrNotFound(resultErr)).To(BeTrue()) + }) }) }) })