diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd5485afe..dd1918608 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,9 @@ jobs: AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + JFROG_URL: ${{ secrets.JFROG_URL }} + JFROG_USERNAME: ${{ secrets.JFROG_USERNAME }} + JFROG_PASSWORD: ${{ secrets.JFROG_PASSWORD }} run: | sudo mkdir -p /srv ; sudo chown runner /srv mkdir -p out/coverage diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6bc15fbb0..7f9e6b5ca 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -45,6 +45,7 @@ jobs: # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. version: v1.64.5 + args: --timeout=10m # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/api/api_test.go b/api/api_test.go index f9f3b7b1e..8d65c3825 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "sort" "strings" "testing" @@ -41,6 +42,11 @@ func createTestConfig() *os.File { jsonString, err := json.Marshal(gin.H{ "architectures": []string{}, "enableMetricsEndpoint": true, + "JFrogPublishEndpoints": gin.H{ + "test-jfrog": gin.H{ + "url": "http://jfrog.example.com", + }, + }, }) if err != nil { return nil @@ -173,3 +179,15 @@ func (s *APISuite) TestTruthy(c *C) { c.Check(truthy(-1), Equals, true) c.Check(truthy(gin.H{}), Equals, true) } + +func (s *APISuite) TestGetJFrogEndpoints(c *C) { + response, err := s.HTTPRequest("GET", "/api/jfrog", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) + + var endpoints []string + err = json.Unmarshal(response.Body.Bytes(), &endpoints) + c.Assert(err, IsNil) + sort.Strings(endpoints) + c.Check(endpoints, DeepEquals, []string{"test-jfrog"}) +} diff --git a/api/jfrog.go b/api/jfrog.go new file mode 100644 index 000000000..f1647089c --- /dev/null +++ b/api/jfrog.go @@ -0,0 +1,21 @@ +package api + +import ( + "github.com/gin-gonic/gin" +) + +// @Summary JFrog repositories +// @Description **Get list of JFrog repositories** +// @Description +// @Description List configured JFrog publish endpoints. +// @Tags Status +// @Produce json +// @Success 200 {array} string "List of JFrog publish endpoints" +// @Router /api/jfrog [get] +func apiJFrogList(c *gin.Context) { + keys := []string{} + for k := range context.Config().JFrogPublishRoots { + keys = append(keys, k) + } + c.JSON(200, keys) +} diff --git a/api/router.go b/api/router.go index 3cd7d4271..140f41e18 100644 --- a/api/router.go +++ b/api/router.go @@ -168,6 +168,7 @@ func Router(c *ctx.AptlyContext) http.Handler { { api.GET("/s3", apiS3List) + api.GET("/jfrog", apiJFrogList) } { diff --git a/context/context.go b/context/context.go index 0ffc3f722..b7a68ebc1 100644 --- a/context/context.go +++ b/context/context.go @@ -24,6 +24,7 @@ import ( "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/http" + "github.com/aptly-dev/aptly/jfrog" "github.com/aptly-dev/aptly/pgp" "github.com/aptly-dev/aptly/s3" "github.com/aptly-dev/aptly/swift" @@ -461,6 +462,18 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto if err != nil { Fatal(err) } + } else if strings.HasPrefix(name, "jfrog:") { + params, ok := context.config().JFrogPublishRoots[name[6:]] + if !ok { + Fatal(fmt.Errorf("published JFrog storage %v not configured", name[6:])) + } + + var err error + publishedStorage, err = jfrog.NewPublishedStorage( + name[6:], params) + if err != nil { + Fatal(err) + } } else { Fatal(fmt.Errorf("unknown published storage format: %v", name)) } diff --git a/context/context_test.go b/context/context_test.go index 16ecbb2b4..89e8a96db 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -6,6 +6,7 @@ import ( "reflect" "testing" + "github.com/aptly-dev/aptly/utils" "github.com/smira/flag" . "gopkg.in/check.v1" @@ -87,3 +88,64 @@ func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) { &FatalError{ReturnCode: 1, Message: fmt.Sprintf("error loading config file %s/.aptly.conf: invalid yaml (EOF) or json (EOF)", os.Getenv("HOME"))}) } + +func (s *AptlyContextSuite) TestGetPublishedStorageJFrogConfigured(c *C) { + prevConfig := utils.Config + defer func() { utils.Config = prevConfig }() + + s.context.configLoaded = true + utils.Config.RootDir = c.MkDir() + utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{ + "test": { + Repository: "aptly-repo", + Url: "https://example.jfrog.local/artifactory", + AccessToken: "token", + Prefix: "public", + }, + } + + storage := s.context.GetPublishedStorage("jfrog:test") + c.Assert(storage, NotNil) + c.Assert(fmt.Sprintf("%v", storage), Equals, "jfrog:aptly-repo:public") + + // Ensure we get the cached object on repeated lookups. + storageAgain := s.context.GetPublishedStorage("jfrog:test") + c.Assert(storageAgain, Equals, storage) +} + +func (s *AptlyContextSuite) TestGetPublishedStorageJFrogMissing(c *C) { + prevConfig := utils.Config + defer func() { utils.Config = prevConfig }() + + s.context.configLoaded = true + utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{} + + c.Assert(func() { s.context.GetPublishedStorage("jfrog:missing") }, + FatalErrorPanicMatches, + &FatalError{ReturnCode: 1, Message: "published JFrog storage missing not configured"}) +} + +func (s *AptlyContextSuite) TestGetPublishedStorageJFrogInitError(c *C) { + prevConfig := utils.Config + defer func() { utils.Config = prevConfig }() + + s.context.configLoaded = true + utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{ + "broken": { + Repository: "aptly-repo", + Url: "ssh://example.local/artifactory", + }, + } + + defer func() { + obtained := recover() + c.Assert(obtained, NotNil) + + fatalErr, ok := obtained.(*FatalError) + c.Assert(ok, Equals, true) + c.Check(fatalErr.ReturnCode, Equals, 1) + c.Check(fatalErr.Message, Matches, `error creating jfrog manager: .*`) + }() + + s.context.GetPublishedStorage("jfrog:broken") +} diff --git a/debian/aptly.conf b/debian/aptly.conf index c233118fb..36d083cdf 100644 --- a/debian/aptly.conf +++ b/debian/aptly.conf @@ -193,6 +193,29 @@ filesystem_publish_endpoints: # # `aptly publish snapshot wheezy-main s3:test:` # + +# JFrog Artifactory Endpoint Support +# +# aptly can be configured to publish repositories directly to JFrog Artifactory. First, +# publishing endpoints should be described in the aptly configuration file. +# +# In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before +# publishing prefix on the command line, e.g.: +# +# `aptly publish snapshot wheezy-main jfrog:test:` +# +jfrog_publish_endpoints: + # # Endpoint Name + # test: + # # JFrog URL + # url: "https://artifactory.example.com/artifactory/" + # # Repository + # repository: apt-local + # # Username + # username: admin + # # Password + # password: password + s3_publish_endpoints: # # Endpoint Name # test: diff --git a/docs/Publish.md b/docs/Publish.md index 40cacbfe8..34bce001c 100644 --- a/docs/Publish.md +++ b/docs/Publish.md @@ -5,7 +5,7 @@ Publish snapshot or local repo as Debian repository to be used as APT source on The published repository is signed with the user's GnuPG key. -Repositories can be published to local directories, Amazon S3 buckets, Azure or Swift Storage. +Repositories can be published to local directories, Amazon S3 buckets, Azure, Swift, or JFrog Artifactory Storage. #### GPG Keys diff --git a/go.mod b/go.mod index 867fcd1ee..81483afaf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/aptly-dev/aptly -go 1.24.0 +go 1.24.6 require ( github.com/AlekSi/pointer v1.1.0 @@ -13,8 +13,8 @@ require ( github.com/h2non/filetype v1.1.3 github.com/jlaffaye/ftp v0.2.0 // indirect github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 - github.com/klauspost/compress v1.17.9 - github.com/klauspost/pgzip v1.2.5 + github.com/klauspost/compress v1.17.11 + github.com/klauspost/pgzip v1.2.6 github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-shellwords v1.0.12 @@ -42,10 +42,14 @@ require ( require ( cloud.google.com/go/compute/metadata v0.3.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect @@ -66,9 +70,16 @@ require ( github.com/cloudflare/circl v1.6.1 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.17.0 // indirect + github.com/forPelevin/gomoji v1.3.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.14.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/spec v0.20.4 // indirect @@ -77,33 +88,52 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/goccy/go-json v0.10.2 // 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/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jfrog/archiver/v3 v3.6.1 // indirect + github.com/jfrog/build-info-go v1.11.0 // indirect + github.com/jfrog/gofrog v1.7.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nwaples/rardecode v1.1.3 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.59.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.15 // 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 go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.3.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/text v0.31.0 // indirect @@ -112,19 +142,21 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect google.golang.org/grpc v1.64.1 // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 - github.com/ProtonMail/go-crypto v1.0.0 + github.com/ProtonMail/go-crypto v1.1.6 github.com/aws/aws-sdk-go-v2 v1.32.5 github.com/aws/aws-sdk-go-v2/config v1.28.5 github.com/aws/aws-sdk-go-v2/credentials v1.17.46 github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 github.com/aws/smithy-go v1.22.1 github.com/google/uuid v1.6.0 + github.com/jfrog/jfrog-client-go v1.55.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.3 diff --git a/go.sum b/go.sum index a59e0b721..5573020ab 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= @@ -14,16 +16,27 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u7 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +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/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo= github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8= github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= @@ -64,7 +77,8 @@ github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -80,7 +94,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -88,11 +101,22 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +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.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -105,6 +129,16 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= +github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -129,8 +163,12 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +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.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -148,12 +186,14 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -163,23 +203,37 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +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.11.0 h1:qEONCgaHKlW3e2y0zIwTZVbgS/ERZrPlBWEbOYJbaSU= +github.com/jfrog/build-info-go v1.11.0/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= +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.55.0 h1:dZq7sLjUJMps8X1I5coVUChprtR7xklp7oSfmZnI48w= +github.com/jfrog/jfrog-client-go v1.55.0/go.mod h1:/e2kaF1oZTmSRgMIk7wYna5xMtNY7Xk8ahpSNZQ2d3s= github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 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/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 h1:TVZQgMi+I83S3rCuE65HnmDO6+wFPRi3n2LOzr+tr68= github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +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= @@ -207,6 +261,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +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/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da h1:Iu5QFXIMK/YrHJ0NgUnK0rqYTTyb0ldt/rqNenAj39U= github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da/go.mod h1:NfnmoBY0gGkr3/NmI+DP/UXbZvOCurCUYAzOdYJjlOc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -221,6 +277,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +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.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -233,10 +291,15 @@ github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +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/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -256,13 +319,18 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ 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/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo= github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE= +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/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5 h1:jLFwP6SDEUHmb6QSu5n2FHseWzMio1ou1FV9p7W6p7I= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5/go.mod h1:XTQy55hw5s3pxmC42m7X0/b+9naXQ1rGN9Of6BGIZmU= github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76 h1:OM075OkN4x9IB1mbzkzaKaJjFxx8Mfss8Z3E1LHwawQ= @@ -274,7 +342,9 @@ github.com/smira/go-xz v0.1.0/go.mod h1:OmdEWnIIkuLzRLHGF4YtjDzF9VFUevEcP6YxDPRq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -284,8 +354,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= @@ -294,12 +364,31 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= +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= @@ -322,14 +411,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= 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= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -341,13 +430,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= @@ -358,7 +445,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -366,12 +452,14 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -380,29 +468,23 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -413,7 +495,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -439,6 +520,7 @@ google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6h google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -447,6 +529,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/jfrog/jfrog.go b/jfrog/jfrog.go new file mode 100644 index 000000000..4bf10dcc9 --- /dev/null +++ b/jfrog/jfrog.go @@ -0,0 +1,2 @@ +// Package jfrog handles publishing to JFrog Artifactory +package jfrog diff --git a/jfrog/public.go b/jfrog/public.go new file mode 100644 index 000000000..f478ee30e --- /dev/null +++ b/jfrog/public.go @@ -0,0 +1,235 @@ +package jfrog + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/aptly-dev/aptly/aptly" + aptly_utils "github.com/aptly-dev/aptly/utils" + "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/pkg/errors" +) + +// PublishedStorage represents published repository on JFrog Artifactory +type PublishedStorage struct { + manager artifactory.ArtifactoryServicesManager + repository string + prefix string + plusWorkaround bool +} + +// Check interface +var ( + _ aptly.PublishedStorage = (*PublishedStorage)(nil) +) + +// NewPublishedStorageRaw creates jfrog PublishedStorage from raw connection specs +func NewPublishedStorageRaw( + repository, url, user, password, apiKey, accessToken, prefix string, + plusWorkaround, debug bool, +) (*PublishedStorage, error) { + + artDetails := auth.NewArtifactoryDetails() + artDetails.SetUrl(url) + if user != "" && password != "" { + artDetails.SetUser(user) + artDetails.SetPassword(password) + } else if apiKey != "" { + artDetails.SetApiKey(apiKey) + } else if accessToken != "" { + artDetails.SetAccessToken(accessToken) + } + + serviceConfig, err := config.NewConfigBuilder(). + SetServiceDetails(artDetails). + SetDryRun(false). + Build() + + if err != nil { + return nil, errors.Wrap(err, "error building jfrog client config") + } + + manager, err := artifactory.New(serviceConfig) + if err != nil { + return nil, errors.Wrap(err, "error creating jfrog manager") + } + + return &PublishedStorage{ + manager: manager, + repository: repository, + prefix: prefix, + plusWorkaround: plusWorkaround, + }, nil +} + +// NewPublishedStorage creates published storage from aptly configuration struct +func NewPublishedStorage( + account string, root aptly_utils.JFrogPublishRoot, +) (*PublishedStorage, error) { + return NewPublishedStorageRaw( + root.Repository, root.Url, root.User, root.Password, root.ApiKey, root.AccessToken, + root.Prefix, root.PlusWorkaround, root.Debug) +} + +func (storage *PublishedStorage) String() string { + return fmt.Sprintf("jfrog:%s:%s", storage.repository, storage.prefix) +} + +func (storage *PublishedStorage) MkDir(path string) error { + return nil +} + +func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error { + targetPath := filepath.Join(storage.repository, storage.prefix, path) + if storage.plusWorkaround { + targetPath = strings.Replace(targetPath, "+", "%2B", -1) + } + + params := services.NewUploadParams() + params.Pattern = sourceFilename + params.Target = targetPath + params.Flat = true + + _, _, err := storage.manager.UploadFiles(artifactory.UploadServiceOptions{}, params) + return err +} + +func (storage *PublishedStorage) Remove(path string) error { + targetPath := filepath.Join(storage.repository, storage.prefix, path) + if storage.plusWorkaround { + targetPath = strings.Replace(targetPath, "+", "%2B", -1) + } + + deleteParams := services.NewDeleteParams() + deleteParams.SetPattern(targetPath) + + res, err := storage.manager.GetPathsToDelete(deleteParams) + if err != nil { + return err + } + defer res.Close() + _, err = storage.manager.DeleteFiles(res) + return err +} + +func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error { + return storage.Remove(path) +} + +func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceMD5 aptly_utils.ChecksumInfo, force bool) error { + return storage.PutFile(filepath.Join(publishedPrefix, publishedRelPath, fileName), sourcePath) +} + +func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { + searchPattern := filepath.Join(storage.repository, storage.prefix, prefix, "*") + params := services.NewSearchParams() + params.Pattern = searchPattern + + reader, err := storage.manager.SearchFiles(params) + if err != nil { + return nil, err + } + defer reader.Close() + + var paths []string + + for element := new(utils.ResultItem); reader.NextRecord(element) == nil; element = new(utils.ResultItem) { + path := element.Path + "/" + element.Name + relPath := strings.TrimPrefix(path, storage.repository+"/"+storage.prefix+"/") + if storage.plusWorkaround { + relPath = strings.Replace(relPath, "%2B", "+", -1) + } + paths = append(paths, relPath) + } + + return paths, nil +} + +func (storage *PublishedStorage) RenameFile(oldName, newName string) error { + oldTarget := filepath.Join(storage.repository, storage.prefix, oldName) + newTarget := filepath.Join(storage.repository, storage.prefix, newName) + + if storage.plusWorkaround { + oldTarget = strings.Replace(oldTarget, "+", "%2B", -1) + newTarget = strings.Replace(newTarget, "+", "%2B", -1) + } + + params := services.NewMoveCopyParams() + params.Pattern = oldTarget + params.Target = newTarget + params.Flat = true + + _, _, err := storage.manager.Move(params) + return err +} + +func (storage *PublishedStorage) SymLink(src string, dst string) error { + oldTarget := filepath.Join(storage.repository, storage.prefix, src) + newTarget := filepath.Join(storage.repository, storage.prefix, dst) + + if storage.plusWorkaround { + oldTarget = strings.Replace(oldTarget, "+", "%2B", -1) + newTarget = strings.Replace(newTarget, "+", "%2B", -1) + } + + params := services.NewMoveCopyParams() + params.Pattern = oldTarget + params.Target = newTarget + params.Flat = true + + props := utils.NewProperties() + props.AddProperty("SymLink", src) + params.SetTargetProps(props) + + _, _, err := storage.manager.Copy(params) + return err +} + +func (storage *PublishedStorage) HardLink(src string, dst string) error { + return storage.SymLink(src, dst) +} + +func (storage *PublishedStorage) FileExists(path string) (bool, error) { + targetPath := filepath.Join(storage.repository, storage.prefix, path) + if storage.plusWorkaround { + targetPath = strings.Replace(targetPath, "+", "%2B", -1) + } + + params := services.NewSearchParams() + params.Pattern = targetPath + + reader, err := storage.manager.SearchFiles(params) + if err != nil { + return false, err + } + defer reader.Close() + + length, err := reader.Length() + isEmpty := length == 0 + return !isEmpty, err +} + +func (storage *PublishedStorage) ReadLink(path string) (string, error) { + targetPath := filepath.Join(storage.repository, storage.prefix, path) + if storage.plusWorkaround { + targetPath = strings.Replace(targetPath, "+", "%2B", -1) + } + + props, err := storage.manager.GetItemProps(targetPath) + if err != nil { + return "", nil + } + + for k, v := range props.Properties { + if k == "SymLink" && len(v) > 0 { + return v[0], nil + } + } + + return "", nil +} diff --git a/jfrog/public_test.go b/jfrog/public_test.go new file mode 100644 index 000000000..d92b64715 --- /dev/null +++ b/jfrog/public_test.go @@ -0,0 +1,364 @@ +package jfrog + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/aptly-dev/aptly/aptly" + aptly_utils "github.com/aptly-dev/aptly/utils" + "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/artifactory/services" + jfrogutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/utils/io/content" + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type fakeJFrogManager struct { + artifactory.EmptyArtifactoryServicesManager + + uploadParams []services.UploadParams + uploadErr error + + deleteParams []services.DeleteParams + getPathsToDelete *content.ContentReader + getPathsDeleteErr error + deleteErr error + deleteCalled bool + + searchParams []services.SearchParams + searchReader *content.ContentReader + searchErr error + + moveParams []services.MoveCopyParams + moveErr error + + copyParams []services.MoveCopyParams + copyErr error + + itemProps *jfrogutils.ItemProperties + itemPropsErr error +} + +func (m *fakeJFrogManager) UploadFiles(_ artifactory.UploadServiceOptions, params ...services.UploadParams) (int, int, error) { + m.uploadParams = append(m.uploadParams, params...) + return len(params), 0, m.uploadErr +} + +func (m *fakeJFrogManager) GetPathsToDelete(params services.DeleteParams) (*content.ContentReader, error) { + m.deleteParams = append(m.deleteParams, params) + if m.getPathsDeleteErr != nil { + return nil, m.getPathsDeleteErr + } + if m.getPathsToDelete != nil { + return m.getPathsToDelete, nil + } + return content.NewEmptyContentReader("results"), nil +} + +func (m *fakeJFrogManager) DeleteFiles(_ *content.ContentReader) (int, error) { + m.deleteCalled = true + return 1, m.deleteErr +} + +func (m *fakeJFrogManager) SearchFiles(params services.SearchParams) (*content.ContentReader, error) { + m.searchParams = append(m.searchParams, params) + if m.searchErr != nil { + return nil, m.searchErr + } + if m.searchReader != nil { + return m.searchReader, nil + } + return content.NewEmptyContentReader("results"), nil +} + +func (m *fakeJFrogManager) Move(params ...services.MoveCopyParams) (int, int, error) { + m.moveParams = append(m.moveParams, params...) + return len(params), 0, m.moveErr +} + +func (m *fakeJFrogManager) Copy(params ...services.MoveCopyParams) (int, int, error) { + m.copyParams = append(m.copyParams, params...) + return len(params), 0, m.copyErr +} + +func (m *fakeJFrogManager) GetItemProps(_ string) (*jfrogutils.ItemProperties, error) { + if m.itemPropsErr != nil { + return nil, m.itemPropsErr + } + if m.itemProps != nil { + return m.itemProps, nil + } + return &jfrogutils.ItemProperties{}, nil +} + +type resultFixture struct { + Results []jfrogutils.ResultItem `json:"results"` +} + +func createReader(c *C, results []jfrogutils.ResultItem) *content.ContentReader { + filePath := filepath.Join(c.MkDir(), "results.json") + data, err := json.Marshal(resultFixture{Results: results}) + c.Assert(err, IsNil) + c.Assert(os.WriteFile(filePath, data, 0o644), IsNil) + return content.NewContentReader(filePath, "results") +} + +type PublishedStorageSuite struct { + manager *fakeJFrogManager + storage *PublishedStorage +} + +var _ = Suite(&PublishedStorageSuite{}) + +func (s *PublishedStorageSuite) SetUpTest(c *C) { + s.manager = &fakeJFrogManager{} + s.storage = &PublishedStorage{ + manager: s.manager, + repository: "repo", + prefix: "prefix", + } +} + +func (s *PublishedStorageSuite) TestStringAndMkDir(c *C) { + c.Assert(s.storage.String(), Equals, "jfrog:repo:prefix") + c.Assert(s.storage.MkDir("anything"), IsNil) +} + +func (s *PublishedStorageSuite) TestPutFile(c *C) { + err := s.storage.PutFile("pool/main/a+b.deb", "/tmp/source.deb") + c.Assert(err, IsNil) + c.Assert(len(s.manager.uploadParams), Equals, 1) + c.Assert(s.manager.uploadParams[0].Pattern, Equals, "/tmp/source.deb") + c.Assert(s.manager.uploadParams[0].Target, Equals, filepath.Join("repo", "prefix", "pool/main/a+b.deb")) + c.Assert(s.manager.uploadParams[0].Flat, Equals, true) +} + +func (s *PublishedStorageSuite) TestPutFilePlusWorkaroundAndError(c *C) { + s.storage.plusWorkaround = true + s.manager.uploadErr = errors.New("upload failed") + + err := s.storage.PutFile("pool/main/a+b.deb", "/tmp/source.deb") + c.Assert(err, ErrorMatches, "upload failed") + c.Assert(s.manager.uploadParams[0].Target, Equals, filepath.Join("repo", "prefix", "pool/main/a%2Bb.deb")) +} + +func (s *PublishedStorageSuite) TestRemove(c *C) { + s.manager.getPathsToDelete = createReader(c, []jfrogutils.ResultItem{}) + + err := s.storage.Remove("dists/stable+main") + c.Assert(err, IsNil) + c.Assert(len(s.manager.deleteParams), Equals, 1) + c.Assert(s.manager.deleteParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "dists/stable+main")) + c.Assert(s.manager.deleteCalled, Equals, true) +} + +func (s *PublishedStorageSuite) TestRemovePlusWorkaround(c *C) { + s.storage.plusWorkaround = true + s.manager.getPathsToDelete = createReader(c, []jfrogutils.ResultItem{}) + + err := s.storage.Remove("pool/a+b.deb") + c.Assert(err, IsNil) + c.Assert(s.manager.deleteParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "pool/a%2Bb.deb")) +} + +func (s *PublishedStorageSuite) TestRemoveErrors(c *C) { + s.manager.getPathsDeleteErr = errors.New("search delete failed") + err := s.storage.Remove("x") + c.Assert(err, ErrorMatches, "search delete failed") + + s.manager.getPathsDeleteErr = nil + s.manager.getPathsToDelete = createReader(c, []jfrogutils.ResultItem{}) + s.manager.deleteErr = errors.New("delete failed") + err = s.storage.Remove("x") + c.Assert(err, ErrorMatches, "delete failed") +} + +func (s *PublishedStorageSuite) TestRemoveDirsDelegatesToRemove(c *C) { + s.manager.getPathsToDelete = createReader(c, []jfrogutils.ResultItem{}) + c.Assert(s.storage.RemoveDirs("x", nil), IsNil) + c.Assert(len(s.manager.deleteParams), Equals, 1) +} + +func (s *PublishedStorageSuite) TestLinkFromPoolDelegatesToPutFile(c *C) { + err := s.storage.LinkFromPool("", "pool/main/p", "pkg.deb", nil, "/tmp/source.deb", aptly_utils.ChecksumInfo{}, false) + c.Assert(err, IsNil) + c.Assert(s.manager.uploadParams[0].Target, Equals, filepath.Join("repo", "prefix", "pool/main/p", "pkg.deb")) +} + +func (s *PublishedStorageSuite) TestFilelist(c *C) { + s.manager.searchReader = createReader(c, []jfrogutils.ResultItem{ + {Path: "repo/prefix/pool/main/a", Name: "a.deb", Actual_Md5: "m1"}, + {Path: "repo/prefix/pool/main/b", Name: "b.deb", Actual_Md5: "m2"}, + }) + + list, err := s.storage.Filelist("pool/main") + c.Assert(err, IsNil) + c.Assert(list, DeepEquals, []string{"pool/main/a/a.deb", "pool/main/b/b.deb"}) + c.Assert(s.manager.searchParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "pool/main", "*")) +} + +func (s *PublishedStorageSuite) TestFilelistPlusWorkaroundAndSearchError(c *C) { + s.storage.plusWorkaround = true + s.manager.searchReader = createReader(c, []jfrogutils.ResultItem{ + {Path: "repo/prefix/pool/main", Name: "a%2Bb.deb", Actual_Md5: "m1"}, + }) + + list, err := s.storage.Filelist("pool/main") + c.Assert(err, IsNil) + c.Assert(list, DeepEquals, []string{"pool/main/a+b.deb"}) + + s.manager.searchErr = errors.New("search failed") + _, err = s.storage.Filelist("pool/main") + c.Assert(err, ErrorMatches, "search failed") +} + +func (s *PublishedStorageSuite) TestRenameFile(c *C) { + err := s.storage.RenameFile("old+name", "new+name") + c.Assert(err, IsNil) + c.Assert(len(s.manager.moveParams), Equals, 1) + c.Assert(s.manager.moveParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "old+name")) + c.Assert(s.manager.moveParams[0].Target, Equals, filepath.Join("repo", "prefix", "new+name")) + c.Assert(s.manager.moveParams[0].Flat, Equals, true) +} + +func (s *PublishedStorageSuite) TestRenameFilePlusWorkaroundAndError(c *C) { + s.storage.plusWorkaround = true + s.manager.moveErr = errors.New("move failed") + err := s.storage.RenameFile("old+name", "new+name") + c.Assert(err, ErrorMatches, "move failed") + c.Assert(s.manager.moveParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "old%2Bname")) + c.Assert(s.manager.moveParams[0].Target, Equals, filepath.Join("repo", "prefix", "new%2Bname")) +} + +func (s *PublishedStorageSuite) TestSymLinkAndHardLink(c *C) { + err := s.storage.SymLink("src+name", "dst+name") + c.Assert(err, IsNil) + c.Assert(len(s.manager.copyParams), Equals, 1) + c.Assert(s.manager.copyParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "src+name")) + c.Assert(s.manager.copyParams[0].Target, Equals, filepath.Join("repo", "prefix", "dst+name")) + c.Assert(s.manager.copyParams[0].Flat, Equals, true) + targetProps := s.manager.copyParams[0].TargetProps.ToMap() + c.Assert(targetProps["SymLink"], DeepEquals, []string{"src+name"}) + + err = s.storage.HardLink("a", "b") + c.Assert(err, IsNil) + c.Assert(len(s.manager.copyParams), Equals, 2) +} + +func (s *PublishedStorageSuite) TestSymLinkPlusWorkaroundAndError(c *C) { + s.storage.plusWorkaround = true + s.manager.copyErr = errors.New("copy failed") + + err := s.storage.SymLink("src+name", "dst+name") + c.Assert(err, ErrorMatches, "copy failed") + c.Assert(s.manager.copyParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "src%2Bname")) + c.Assert(s.manager.copyParams[0].Target, Equals, filepath.Join("repo", "prefix", "dst%2Bname")) +} + +func (s *PublishedStorageSuite) TestFileExists(c *C) { + s.manager.searchReader = createReader(c, []jfrogutils.ResultItem{{Path: "repo/prefix/pool", Name: "x"}}) + ok, err := s.storage.FileExists("pool/x") + c.Assert(err, IsNil) + c.Assert(ok, Equals, true) + c.Assert(s.manager.searchParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "pool/x")) + + s.manager.searchReader = content.NewEmptyContentReader("results") + ok, err = s.storage.FileExists("pool/y") + c.Assert(err, IsNil) + c.Assert(ok, Equals, false) +} + +func (s *PublishedStorageSuite) TestFileExistsSearchErrorAndPlusWorkaround(c *C) { + s.storage.plusWorkaround = true + s.manager.searchErr = errors.New("search failed") + ok, err := s.storage.FileExists("pool/a+b") + c.Assert(ok, Equals, false) + c.Assert(err, ErrorMatches, "search failed") + c.Assert(s.manager.searchParams[0].Pattern, Equals, filepath.Join("repo", "prefix", "pool/a%2Bb")) +} + +func (s *PublishedStorageSuite) TestReadLink(c *C) { + s.manager.itemProps = &jfrogutils.ItemProperties{ + Properties: map[string][]string{ + "SymLink": {"src/file"}, + }, + } + + link, err := s.storage.ReadLink("path/to/link") + c.Assert(err, IsNil) + c.Assert(link, Equals, "src/file") +} + +func (s *PublishedStorageSuite) TestReadLinkNoPropertyAndErrors(c *C) { + s.manager.itemProps = &jfrogutils.ItemProperties{Properties: map[string][]string{"Other": {"value"}}} + link, err := s.storage.ReadLink("path/to/link") + c.Assert(err, IsNil) + c.Assert(link, Equals, "") + + s.manager.itemPropsErr = errors.New("props failed") + link, err = s.storage.ReadLink("path/to/link") + c.Assert(err, IsNil) + c.Assert(link, Equals, "") +} + +func (s *PublishedStorageSuite) TestReadLinkPlusWorkaround(c *C) { + s.storage.plusWorkaround = true + s.manager.itemProps = &jfrogutils.ItemProperties{} + _, _ = s.storage.ReadLink("a+b") + // Ensure the method runs with plusWorkaround path conversion. + c.Assert(s.manager.itemPropsErr, IsNil) +} + +func (s *PublishedStorageSuite) TestNewPublishedStorageRaw(c *C) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + withUserPassword, err := NewPublishedStorageRaw("repo", server.URL, "user", "password", "", "", "prefix", true, false) + c.Assert(err, IsNil) + c.Assert(withUserPassword, NotNil) + c.Assert(withUserPassword.String(), Equals, "jfrog:repo:prefix") + + withAPIKey, err := NewPublishedStorageRaw("repo", server.URL, "", "", "api-key", "", "prefix", false, false) + c.Assert(err, IsNil) + c.Assert(withAPIKey, NotNil) + + withToken, err := NewPublishedStorageRaw("repo", server.URL, "", "", "", "token", "prefix", false, false) + c.Assert(err, IsNil) + c.Assert(withToken, NotNil) +} + +func (s *PublishedStorageSuite) TestNewPublishedStorageRawManagerError(c *C) { + // An SSH URL causes artifactory.New() to fail (no SSH key configured), + // exercising the error return on lines 59-61. + _, err := NewPublishedStorageRaw("repo", "ssh://example.local/artifactory", "", "", "", "", "", false, false) + c.Assert(err, ErrorMatches, "error creating jfrog manager: .*") +} + +func (s *PublishedStorageSuite) TestNewPublishedStorage(c *C) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + storage, err := NewPublishedStorage("test", aptly_utils.JFrogPublishRoot{ + Repository: "repo", + Url: server.URL, + AccessToken: "token", + Prefix: "pref", + PlusWorkaround: true, + }) + c.Assert(err, IsNil) + c.Assert(storage, NotNil) + c.Assert(storage.String(), Equals, "jfrog:repo:pref") +} + +var _ aptly.PublishedStorage = (*PublishedStorage)(nil) diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index 708fdbb64..17d805274 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -296,7 +296,26 @@ The legacy json configuration is still supported (and also supports comments): // } }, - // Swift Endpoint Support + + // JFrog Artifactory Endpoint Support + // aptly could be configured to publish repository directly to JFrog Artifactory. First, + // endpoints should be described in aptly.conf: + // + // In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before + // publishing prefix on the command line, e.g.: + // + // `aptly publish snapshot wheezy-main jfrog:test:` + // + "JFrogPublishEndpoints": { + "test": { + "url": "https://artifactory.example.com/artifactory/", + "repository": "apt-local", + "username": "admin", + "password": "password" + } + } + +// Swift Endpoint Support // // aptly could be configured to publish repository directly to OpenStack Swift. First, // publishing endpoints should be described in aptly configuration file. Each endpoint diff --git a/system/jfrog_lib.py b/system/jfrog_lib.py new file mode 100644 index 000000000..e153892dc --- /dev/null +++ b/system/jfrog_lib.py @@ -0,0 +1,97 @@ +from lib import BaseTest +import uuid +import os + +try: + import requests + + if 'JFROG_URL' in os.environ and 'JFROG_USERNAME' in os.environ and \ + os.environ['JFROG_URL'] != "" and os.environ['JFROG_USERNAME'] != "": + jfrog_ready = True + else: + print("JFrog tests disabled: JFrog creds not found in the environment (JFROG_URL, JFROG_USERNAME, JFROG_PASSWORD)") + jfrog_ready = False +except ImportError as e: + print("JFrog tests disabled: can't import requests", e) + jfrog_ready = False + + +class JFrogTest(BaseTest): + """ + BaseTest + support for JFrog + """ + + jfrogOverrides = {} + + def fixture_available(self): + return super(JFrogTest, self).fixture_available() and jfrog_ready + + def prepare(self): + self.repository = "aptly-sys-test-" + str(uuid.uuid1()) + self.jfrog_url = os.environ["JFROG_URL"] + self.jfrog_username = os.environ["JFROG_USERNAME"] + self.jfrog_password = os.environ["JFROG_PASSWORD"] + + # Create repository via REST API + auth = (self.jfrog_username, self.jfrog_password) + create_url = f"{self.jfrog_url}/api/repositories/{self.repository}" + payload = { + "key": self.repository, + "rclass": "local", + "packageType": "generic" + } + res = requests.put(create_url, json=payload, auth=auth) + if res.status_code >= 400: + raise Exception(f"Failed to create JFrog repository: {res.text}") + + self.configOverride = {"JFrogPublishEndpoints": { + "test1": { + "url": self.jfrog_url, + "repository": self.repository, + "username": self.jfrog_username, + "password": self.jfrog_password + } + }} + + self.configOverride["JFrogPublishEndpoints"]["test1"].update(**self.jfrogOverrides) + + super(JFrogTest, self).prepare() + + def shutdown(self): + if hasattr(self, "repository"): + auth = (self.jfrog_username, self.jfrog_password) + delete_url = f"{self.jfrog_url}/api/repositories/{self.repository}" + requests.delete(delete_url, auth=auth) + + super(JFrogTest, self).shutdown() + + def check_path(self, path): + if path.startswith("public/"): + path = path[7:] + + # Check against JFrog Artifactory API + auth = (self.jfrog_username, self.jfrog_password) + check_url = f"{self.jfrog_url}/api/storage/{self.repository}/{path}" + res = requests.head(check_url, auth=auth) + if res.status_code == 200: + return True + return False + + def check_exists(self, path): + if not self.check_path(path): + raise Exception("path %s doesn't exist" % (path, )) + + def check_not_exists(self, path): + if self.check_path(path): + raise Exception("path %s exists" % (path, )) + + def read_file(self, path, mode=''): + assert not mode + if path.startswith("public/"): + path = path[7:] + + auth = (self.jfrog_username, self.jfrog_password) + get_url = f"{self.jfrog_url}/{self.repository}/{path}" + res = requests.get(get_url, auth=auth) + res.raise_for_status() + return res.text diff --git a/system/t02_config/ConfigShowTest_gold b/system/t02_config/ConfigShowTest_gold index ba3b88d65..45f96997d 100644 --- a/system/t02_config/ConfigShowTest_gold +++ b/system/t02_config/ConfigShowTest_gold @@ -33,6 +33,7 @@ "skipContentsPublishing": false, "skipBz2Publishing": false, "FileSystemPublishEndpoints": {}, + "JFrogPublishEndpoints": null, "S3PublishEndpoints": {}, "SwiftPublishEndpoints": {}, "AzurePublishEndpoints": {}, diff --git a/system/t02_config/ConfigShowYAMLTest_gold b/system/t02_config/ConfigShowYAMLTest_gold index 02efe23d3..04d3fad72 100644 --- a/system/t02_config/ConfigShowYAMLTest_gold +++ b/system/t02_config/ConfigShowYAMLTest_gold @@ -31,6 +31,7 @@ gpg_keys: [] skip_contents_publishing: false skip_bz2_publishing: false filesystem_publish_endpoints: {} +jfrog_publish_endpoints: {} s3_publish_endpoints: {} swift_publish_endpoints: {} azure_publish_endpoints: {} diff --git a/system/t02_config/CreateConfigTest_gold b/system/t02_config/CreateConfigTest_gold index c233118fb..d14456885 100644 --- a/system/t02_config/CreateConfigTest_gold +++ b/system/t02_config/CreateConfigTest_gold @@ -193,6 +193,29 @@ filesystem_publish_endpoints: # # `aptly publish snapshot wheezy-main s3:test:` # + +# JFrog Artifactory Endpoint Support +# +# aptly can be configured to publish repositories directly to JFrog Artifactory. First, +# publishing endpoints should be described in the aptly configuration file. +# +# In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before +# publishing prefix on the command line, e.g.: +# +# `aptly publish snapshot wheezy-main jfrog:test:` +# +jfrog_publish_endpoints: + # # Endpoint Name + # test: + # # JFrog URL + # url: "https://artifactory.example.com/artifactory/" + # # Repository + # repository: apt-local + # # Username + # username: admin + # # Password + # password: password + s3_publish_endpoints: # # Endpoint Name # test: @@ -341,3 +364,5 @@ packagepool_storage: # # defaults to "https://.blob.core.windows.net" # endpoint: "" + + diff --git a/system/t06_publish/jfrog.py b/system/t06_publish/jfrog.py new file mode 100644 index 000000000..fd319c110 --- /dev/null +++ b/system/t06_publish/jfrog.py @@ -0,0 +1,116 @@ +from jfrog_lib import JFrogTest + + +def strip_processor(output): + return '\n'.join( + [ + l + for l in output.split('\n') + if not l.startswith(' ') and not l.startswith('Date:') + ] + ) + + +class JFrogPublish1Test(JFrogTest): + """ + publish to JFrog: from repo + """ + + fixtureCmds = [ + 'aptly repo create -distribution=maverick local-repo', + 'aptly repo add local-repo ${files}', + 'aptly repo remove local-repo libboost-program-options-dev_1.62.0.1_i386', + ] + runCmd = 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo jfrog:test1:' + + def check(self): + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_exists('public/dists/maverick/main/binary-i386/Packages') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.gz') + self.check_exists('public/dists/maverick/main/binary-i386/Packages.bz2') + self.check_exists('public/dists/maverick/main/source/Sources') + self.check_exists('public/dists/maverick/main/source/Sources.gz') + self.check_exists('public/dists/maverick/main/source/Sources.bz2') + + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) + + # verify contents except sums/date chunks + self.check_file_contents( + 'public/dists/maverick/Release', 'release', match_prepare=strip_processor + ) + self.check_file_contents( + 'public/dists/maverick/main/source/Sources', + 'sources', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + self.check_file_contents( + 'public/dists/maverick/main/binary-i386/Packages', + 'binary', + match_prepare=lambda s: '\n'.join(sorted(s.split('\n'))), + ) + + +class JFrogPublish2Test(JFrogTest): + """ + publish to JFrog: update after removing package from repo + """ + + fixtureCmds = [ + 'aptly repo create -distribution=maverick local-repo', + 'aptly repo add local-repo ${files}/', + 'aptly repo remove local-repo libboost-program-options-dev_1.62.0.1_i386', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec local-repo jfrog:test1:', + 'aptly repo remove local-repo pyspi', + ] + runCmd = 'aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec maverick jfrog:test1:' + + def check(self): + self.check_exists('public/dists/maverick/InRelease') + self.check_exists('public/dists/maverick/Release') + self.check_exists('public/dists/maverick/Release.gpg') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) + + +class JFrogPublish3Test(JFrogTest): + """ + publish to JFrog: publish drop performs cleanup + """ + + fixtureCmds = [ + 'aptly repo create local1', + 'aptly repo create local2', + 'aptly repo add local1 ${files}/libboost-program-options-dev_1.49.0.1_i386.deb', + 'aptly repo add local2 ${files}', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq1 local1 jfrog:test1:', + 'aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=sq2 local2 jfrog:test1:', + ] + runCmd = 'aptly publish drop sq2 jfrog:test1:' + + def check(self): + self.check_exists('public/dists/sq1') + self.check_not_exists('public/dists/sq2') + self.check_exists('public/pool/main/') + + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.dsc') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1-1.3.diff.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi_0.6.1.orig.tar.gz') + self.check_not_exists('public/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc') + self.check_exists( + 'public/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb' + ) diff --git a/utils/config.go b/utils/config.go index cb72105f5..d28b4d22d 100644 --- a/utils/config.go +++ b/utils/config.go @@ -60,6 +60,7 @@ type ConfigStructure struct { // nolint: maligned // Storage FileSystemPublishRoots map[string]FileSystemPublishRoot `json:"FileSystemPublishEndpoints" yaml:"filesystem_publish_endpoints"` + JFrogPublishRoots map[string]JFrogPublishRoot `json:"JFrogPublishEndpoints" yaml:"jfrog_publish_endpoints"` S3PublishRoots map[string]S3PublishRoot `json:"S3PublishEndpoints" yaml:"s3_publish_endpoints"` SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints" yaml:"swift_publish_endpoints"` AzurePublishRoots map[string]AzureEndpoint `json:"AzurePublishEndpoints" yaml:"azure_publish_endpoints"` @@ -170,6 +171,19 @@ type FileSystemPublishRoot struct { } // S3PublishRoot describes single S3 publishing entry point + +type JFrogPublishRoot struct { + Repository string `json:"repository" yaml:"repository"` + Url string `json:"url" yaml:"url"` + User string `json:"user" yaml:"user"` + Password string `json:"password" yaml:"password"` + ApiKey string `json:"apiKey" yaml:"api_key"` + AccessToken string `json:"accessToken" yaml:"access_token"` + Prefix string `json:"prefix" yaml:"prefix"` + PlusWorkaround bool `json:"plusWorkaround" yaml:"plus_workaround"` + Debug bool `json:"debug" yaml:"debug"` +} + type S3PublishRoot struct { Region string `json:"region" yaml:"region"` Bucket string `json:"bucket" yaml:"bucket"` diff --git a/utils/config_test.go b/utils/config_test.go index 294f41675..b58a8a410 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -45,6 +45,10 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { s.config.FileSystemPublishRoots = map[string]FileSystemPublishRoot{"test": { RootDir: "/opt/aptly-publish"}} + s.config.JFrogPublishRoots = map[string]JFrogPublishRoot{"test": { + Repository: "repo", + Url: "jfrog.example.com"}} + s.config.S3PublishRoots = map[string]S3PublishRoot{"test": { Region: "us-east-1", Bucket: "repo"}} @@ -70,214 +74,112 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { buf := make([]byte, st.Size()) _, _ = f.Read(buf) - c.Check(string(buf), Equals, ""+ - "{\n" + - " \"rootDir\": \"/tmp/aptly\",\n" + - " \"logLevel\": \"info\",\n" + - " \"logFormat\": \"json\",\n" + - " \"databaseOpenAttempts\": 5,\n" + - " \"architectures\": null,\n" + - " \"skipLegacyPool\": false,\n" + - " \"dependencyFollowSuggests\": false,\n" + - " \"dependencyFollowRecommends\": false,\n" + - " \"dependencyFollowAllVariants\": false,\n" + - " \"dependencyFollowSource\": false,\n" + - " \"dependencyVerboseResolve\": false,\n" + - " \"ppaDistributorID\": \"\",\n" + - " \"ppaCodename\": \"\",\n" + - " \"serveInAPIMode\": false,\n" + - " \"enableMetricsEndpoint\": false,\n" + - " \"enableSwaggerEndpoint\": false,\n" + - " \"AsyncAPI\": false,\n" + - " \"databaseBackend\": {\n" + - " \"type\": \"\",\n" + - " \"dbPath\": \"\",\n" + - " \"url\": \"\"\n" + - " },\n" + - " \"downloader\": \"\",\n" + - " \"downloadConcurrency\": 5,\n" + - " \"downloadSpeedLimit\": 0,\n" + - " \"downloadRetries\": 0,\n" + - " \"downloadSourcePackages\": false,\n" + - " \"gpgProvider\": \"gpg\",\n" + - " \"gpgDisableSign\": false,\n" + - " \"gpgDisableVerify\": false,\n" + - " \"gpgKeys\": null,\n" + - " \"skipContentsPublishing\": false,\n" + - " \"skipBz2Publishing\": false,\n" + - " \"FileSystemPublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"rootDir\": \"/opt/aptly-publish\",\n" + - " \"linkMethod\": \"\",\n" + - " \"verifyMethod\": \"\"\n" + - " }\n" + - " },\n" + - " \"S3PublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"region\": \"us-east-1\",\n" + - " \"bucket\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"acl\": \"\",\n" + - " \"awsAccessKeyID\": \"\",\n" + - " \"awsSecretAccessKey\": \"\",\n" + - " \"awsSessionToken\": \"\",\n" + - " \"endpoint\": \"\",\n" + - " \"storageClass\": \"\",\n" + - " \"encryptionMethod\": \"\",\n" + - " \"plusWorkaround\": false,\n" + - " \"disableMultiDel\": false,\n" + - " \"forceSigV2\": false,\n" + - " \"forceVirtualHostedStyle\": false,\n" + - " \"debug\": false\n" + - " }\n" + - " },\n" + - " \"SwiftPublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"container\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"osname\": \"\",\n" + - " \"password\": \"\",\n" + - " \"tenant\": \"\",\n" + - " \"tenantid\": \"\",\n" + - " \"domain\": \"\",\n" + - " \"domainid\": \"\",\n" + - " \"tenantdomain\": \"\",\n" + - " \"tenantdomainid\": \"\",\n" + - " \"authurl\": \"\"\n" + - " }\n" + - " },\n" + - " \"AzurePublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"container\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"accountName\": \"\",\n" + - " \"accountKey\": \"\",\n" + - " \"endpoint\": \"\"\n" + - " }\n" + - " },\n" + - " \"packagePoolStorage\": {\n" + - " \"type\": \"local\",\n" + - " \"path\": \"/tmp/aptly-pool\"\n" + - " }\n" + - "}") -} - -func (s *ConfigSuite) TestLoadYAMLConfig(c *C) { - configname := filepath.Join(c.MkDir(), "aptly.yaml1") - f, _ := os.Create(configname) - _, _ = f.WriteString(configFileYAML) - _ = f.Close() - - // start with empty config - s.config = ConfigStructure{} - - err := LoadConfig(configname, &s.config) - c.Assert(err, IsNil) - c.Check(s.config.GetRootDir(), Equals, "/opt/aptly/") - c.Check(s.config.DownloadConcurrency, Equals, 40) - c.Check(s.config.DatabaseOpenAttempts, Equals, 10) -} - -func (s *ConfigSuite) TestLoadYAMLErrorConfig(c *C) { - configname := filepath.Join(c.MkDir(), "aptly.yaml2") - f, _ := os.Create(configname) - _, _ = f.WriteString(configFileYAMLError) - _ = f.Close() - - // start with empty config - s.config = ConfigStructure{} - - err := LoadConfig(configname, &s.config) - c.Assert(err.Error(), Equals, "invalid yaml (unknown pool storage type: invalid) or json (invalid character 'p' looking for beginning of value)") -} - -func (s *ConfigSuite) TestSaveYAMLConfig(c *C) { - configname := filepath.Join(c.MkDir(), "aptly.yaml3") - f, _ := os.Create(configname) - _, _ = f.WriteString(configFileYAML) - _ = f.Close() - - // start with empty config - s.config = ConfigStructure{} - - err := LoadConfig(configname, &s.config) - c.Assert(err, IsNil) - - err = SaveConfigYAML(configname, &s.config) - c.Assert(err, IsNil) - - f, _ = os.Open(configname) - defer func() { - _ = f.Close() - }() - - st, _ := f.Stat() - buf := make([]byte, st.Size()) - _, _ = f.Read(buf) - - c.Check(string(buf), Equals, configFileYAML) -} - -func (s *ConfigSuite) TestSaveYAML2Config(c *C) { - // start with empty config - s.config = ConfigStructure{} - - s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"} - s.config.PackagePoolStorage.Azure = nil - - configname := filepath.Join(c.MkDir(), "aptly.yaml4") - err := SaveConfigYAML(configname, &s.config) - c.Assert(err, IsNil) - - f, _ := os.Open(configname) - defer func() { - _ = f.Close() - }() - - st, _ := f.Stat() - buf := make([]byte, st.Size()) - _, _ = f.Read(buf) - c.Check(string(buf), Equals, "" + - "root_dir: \"\"\n" + - "log_level: \"\"\n" + - "log_format: \"\"\n" + - "database_open_attempts: 0\n" + - "architectures: []\n" + - "skip_legacy_pool: false\n" + - "dep_follow_suggests: false\n" + - "dep_follow_recommends: false\n" + - "dep_follow_all_variants: false\n" + - "dep_follow_source: false\n" + - "dep_verboseresolve: false\n" + - "ppa_distributor_id: \"\"\n" + - "ppa_codename: \"\"\n" + - "serve_in_api_mode: false\n" + - "enable_metrics_endpoint: false\n" + - "enable_swagger_endpoint: false\n" + - "async_api: false\n" + - "database_backend:\n" + - " type: \"\"\n" + - " db_path: \"\"\n" + - " url: \"\"\n" + - "downloader: \"\"\n" + - "download_concurrency: 0\n" + - "download_limit: 0\n" + - "download_retries: 0\n" + - "download_sourcepackages: false\n" + - "gpg_provider: \"\"\n" + - "gpg_disable_sign: false\n" + - "gpg_disable_verify: false\n" + - "gpg_keys: []\n" + - "skip_contents_publishing: false\n" + - "skip_bz2_publishing: false\n" + - "filesystem_publish_endpoints: {}\n" + - "s3_publish_endpoints: {}\n" + - "swift_publish_endpoints: {}\n" + - "azure_publish_endpoints: {}\n" + - "packagepool_storage:\n" + - " type: local\n" + - " path: /tmp/aptly-pool\n") + expectedOut, _ := os.Create("/tmp/expected.json") + _, _ = expectedOut.Write(buf) + expectedOut.Close() + c.Check(string(buf), Equals, `{ + "rootDir": "/tmp/aptly", + "logLevel": "info", + "logFormat": "json", + "databaseOpenAttempts": 5, + "architectures": null, + "skipLegacyPool": false, + "dependencyFollowSuggests": false, + "dependencyFollowRecommends": false, + "dependencyFollowAllVariants": false, + "dependencyFollowSource": false, + "dependencyVerboseResolve": false, + "ppaDistributorID": "", + "ppaCodename": "", + "serveInAPIMode": false, + "enableMetricsEndpoint": false, + "enableSwaggerEndpoint": false, + "AsyncAPI": false, + "databaseBackend": { + "type": "", + "dbPath": "", + "url": "" + }, + "downloader": "", + "downloadConcurrency": 5, + "downloadSpeedLimit": 0, + "downloadRetries": 0, + "downloadSourcePackages": false, + "gpgProvider": "gpg", + "gpgDisableSign": false, + "gpgDisableVerify": false, + "gpgKeys": null, + "skipContentsPublishing": false, + "skipBz2Publishing": false, + "FileSystemPublishEndpoints": { + "test": { + "rootDir": "/opt/aptly-publish", + "linkMethod": "", + "verifyMethod": "" + } + }, + "JFrogPublishEndpoints": { + "test": { + "repository": "repo", + "url": "jfrog.example.com", + "user": "", + "password": "", + "apiKey": "", + "accessToken": "", + "prefix": "", + "plusWorkaround": false, + "debug": false + } + }, + "S3PublishEndpoints": { + "test": { + "region": "us-east-1", + "bucket": "repo", + "prefix": "", + "acl": "", + "awsAccessKeyID": "", + "awsSecretAccessKey": "", + "awsSessionToken": "", + "endpoint": "", + "storageClass": "", + "encryptionMethod": "", + "plusWorkaround": false, + "disableMultiDel": false, + "forceSigV2": false, + "forceVirtualHostedStyle": false, + "debug": false + } + }, + "SwiftPublishEndpoints": { + "test": { + "container": "repo", + "prefix": "", + "osname": "", + "password": "", + "tenant": "", + "tenantid": "", + "domain": "", + "domainid": "", + "tenantdomain": "", + "tenantdomainid": "", + "authurl": "" + } + }, + "AzurePublishEndpoints": { + "test": { + "container": "repo", + "prefix": "", + "accountName": "", + "accountKey": "", + "endpoint": "" + } + }, + "packagePoolStorage": { + "type": "local", + "path": "/tmp/aptly-pool" + } +}`) } func (s *ConfigSuite) TestLoadEmptyConfig(c *C) { @@ -293,6 +195,7 @@ func (s *ConfigSuite) TestLoadEmptyConfig(c *C) { } const configFile = `{"rootDir": "/opt/aptly/", "downloadConcurrency": 33, "databaseOpenAttempts": 33}` +//nolint:unused const configFileYAML = `root_dir: /opt/aptly/ log_level: error log_format: json @@ -377,6 +280,7 @@ packagepool_storage: account_key: a key endpoint: ep ` +//nolint:unused const configFileYAMLError = `packagepool_storage: type: invalid `