From 4b8f0c42acf9d8b8240c93d6167a67e6c2907c91 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 11:07:27 -0400 Subject: [PATCH 01/14] initial commit for JFrog support --- api/api_test.go | 3 + api/jfrog.go | 21 ++ api/router.go | 1 + context/context.go | 13 + jfrog/jfrog.go | 2 + jfrog/public.go | 238 +++++++++++++++ system/t02_config/ConfigShowYAMLTest_gold | 1 + utils/config.go | 14 + utils/config_test.go | 348 ++++++++-------------- 9 files changed, 415 insertions(+), 226 deletions(-) create mode 100644 api/jfrog.go create mode 100644 jfrog/jfrog.go create mode 100644 jfrog/public.go diff --git a/api/api_test.go b/api/api_test.go index afb5e65b..835361bb 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -51,6 +51,9 @@ func createTestConfig() *os.File { "GcsPublishEndpoints": map[string]map[string]string{ "test-gcs": { "bucket": "bucket-gcs", + "JFrogPublishEndpoints": gin.H{ + "test-jfrog": gin.H{ + "url": "http://jfrog.example.com", }, }, }) diff --git a/api/jfrog.go b/api/jfrog.go new file mode 100644 index 00000000..f1647089 --- /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 d7704b9f..1818b2a1 100644 --- a/api/router.go +++ b/api/router.go @@ -178,6 +178,7 @@ func Router(c *ctx.AptlyContext) http.Handler { { api.GET("/s3", apiS3List) api.GET("/gcs", apiGCSList) + api.GET("/jfrog", apiJFrogList) } { diff --git a/context/context.go b/context/context.go index 8236f635..cb7d2c8d 100644 --- a/context/context.go +++ b/context/context.go @@ -25,6 +25,7 @@ import ( "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/gcs" "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" @@ -476,6 +477,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/jfrog/jfrog.go b/jfrog/jfrog.go new file mode 100644 index 00000000..4bf10dcc --- /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 00000000..71866058 --- /dev/null +++ b/jfrog/public.go @@ -0,0 +1,238 @@ +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 + pathCache map[string]string +} + +// 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 + var md5s []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) + md5s = append(md5s, element.Actual_Md5) + } + + 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/system/t02_config/ConfigShowYAMLTest_gold b/system/t02_config/ConfigShowYAMLTest_gold index c863aa2c..942e4233 100644 --- a/system/t02_config/ConfigShowYAMLTest_gold +++ b/system/t02_config/ConfigShowYAMLTest_gold @@ -32,6 +32,7 @@ gpg_keys: [] skip_contents_publishing: false skip_bz2_publishing: false filesystem_publish_endpoints: {} +jfrog_publish_endpoints: {} s3_publish_endpoints: {} gcs_publish_endpoints: {} swift_publish_endpoints: {} diff --git a/utils/config.go b/utils/config.go index d725339a..0732b710 100644 --- a/utils/config.go +++ b/utils/config.go @@ -61,6 +61,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"` GCSPublishRoots map[string]GCSPublishRoot `json:"GcsPublishEndpoints" yaml:"gcs_publish_endpoints"` SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints" yaml:"swift_publish_endpoints"` @@ -172,6 +173,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 2e5b0045..e571528c 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"}} @@ -73,232 +77,124 @@ 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" + - " \"ppaBaseURL\": \"\",\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" + - " \"GcsPublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"bucket\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"credentialsFile\": \"\",\n" + - " \"serviceAccountJSON\": \"\",\n" + - " \"project\": \"\",\n" + - " \"endpoint\": \"\",\n" + - " \"acl\": \"\",\n" + - " \"storageClass\": \"\",\n" + - " \"encryptionKey\": \"\",\n" + - " \"disableMultiDel\": 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" + - "ppa_baseurl: \"\"\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" + - "gcs_publish_endpoints: {}\n" + - "swift_publish_endpoints: {}\n" + - "azure_publish_endpoints: {}\n" + - "packagepool_storage:\n" + - " type: local\n" + - " path: /tmp/aptly-pool\n") + 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": "", + "ppaBaseURL": "", + "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 + } + }, + "GcsPublishEndpoints": { + "test": { + "bucket": "repo", + "prefix": "", + "credentialsFile": "", + "serviceAccountJSON": "", + "project": "", + "endpoint": "", + "acl": "", + "storageClass": "", + "encryptionKey": "", + "disableMultiDel": 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) { From 214c151194e359152284b27d74702b699ba967db Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Thu, 9 Apr 2026 10:55:53 -0400 Subject: [PATCH 02/14] fix: resolve golangci-lint failures in jfrog storage implementation - Remove unused pathCache field from PublishedStorage struct - Remove unused md5s accumulation in Filelist() - Fix unchecked error return on expectedOut.Write in config_test.go - Suppress unused linter on configFileYAML/configFileYAMLError constants (retained to avoid merge conflicts with feat/pls/gcs-support) - Add --timeout=10m to golangci-lint workflow - add back removed tests --- .github/workflows/golangci-lint.yml | 1 + jfrog/public.go | 9 +- utils/config_test.go | 122 ++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 6bc15fbb..7f9e6b5c 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/jfrog/public.go b/jfrog/public.go index 71866058..f478ee30 100644 --- a/jfrog/public.go +++ b/jfrog/public.go @@ -21,7 +21,6 @@ type PublishedStorage struct { repository string prefix string plusWorkaround bool - pathCache map[string]string } // Check interface @@ -138,16 +137,14 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { defer reader.Close() var paths []string - var md5s []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 + "/") + relPath := strings.TrimPrefix(path, storage.repository+"/"+storage.prefix+"/") if storage.plusWorkaround { - relPath = strings.Replace(relPath, "%2B", "+", -1) + relPath = strings.Replace(relPath, "%2B", "+", -1) } paths = append(paths, relPath) - md5s = append(md5s, element.Actual_Md5) } return paths, nil diff --git a/utils/config_test.go b/utils/config_test.go index e571528c..29143ef9 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -197,6 +197,127 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { }`) } +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"+ + "ppa_baseurl: \"\"\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"+ + "jfrog_publish_endpoints: {}\n"+ + "s3_publish_endpoints: {}\n"+ + "gcs_publish_endpoints: {}\n"+ + "swift_publish_endpoints: {}\n"+ + "azure_publish_endpoints: {}\n"+ + "packagepool_storage:\n"+ + " type: local\n"+ + " path: /tmp/aptly-pool\n") +} + func (s *ConfigSuite) TestLoadEmptyConfig(c *C) { configname := filepath.Join(c.MkDir(), "aptly.yaml5") f, _ := os.Create(configname) @@ -250,6 +371,7 @@ filesystem_publish_endpoints: root_dir: /opt/srv/aptly_public link_method: hardlink verify_method: md5 +jfrog_publish_endpoints: {} s3_publish_endpoints: test: region: us-east-1 From b28daa84174acd6e1a949d13267998e29b77bca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Thu, 18 Jun 2026 13:52:09 +0200 Subject: [PATCH 03/14] initial commit for JFrog support # Conflicts: # api/api_test.go # api/router.go # go.mod # go.sum # utils/config_test.go --- .github/workflows/ci.yml | 3 + api/api_test.go | 18 +++++- debian/aptly.conf | 23 +++++++ docs/Publish.md | 2 +- man/aptly.1.ronn.tmpl | 18 ++++++ system/jfrog_lib.py | 99 ++++++++++++++++++++++++++++++ system/t06_publish/jfrog.py | 116 ++++++++++++++++++++++++++++++++++++ 7 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 system/jfrog_lib.py create mode 100644 system/t06_publish/jfrog.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4c6efb1..a2cc14a7 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/api/api_test.go b/api/api_test.go index 835361bb..b6a5b11f 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -51,8 +51,10 @@ func createTestConfig() *os.File { "GcsPublishEndpoints": map[string]map[string]string{ "test-gcs": { "bucket": "bucket-gcs", - "JFrogPublishEndpoints": gin.H{ - "test-jfrog": gin.H{ + }, + }, + "JFrogPublishEndpoints": map[string]map[string]string{ + "test-jfrog": { "url": "http://jfrog.example.com", }, }, @@ -189,6 +191,18 @@ func (s *APISuite) TestTruthy(c *C) { 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"}) +} + func (s *APISuite) TestGetS3Endpoints(c *C) { response, err := s.HTTPRequest("GET", "/api/s3", nil) c.Assert(err, IsNil) diff --git a/debian/aptly.conf b/debian/aptly.conf index 93177314..98e75bce 100644 --- a/debian/aptly.conf +++ b/debian/aptly.conf @@ -196,6 +196,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 d6a8fd75..36b0edc4 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/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index 04364310..acfcc612 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -356,6 +356,24 @@ The legacy json configuration is still supported (and also supports comments): // } }, + // 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, diff --git a/system/jfrog_lib.py b/system/jfrog_lib.py new file mode 100644 index 00000000..6dc1f5dc --- /dev/null +++ b/system/jfrog_lib.py @@ -0,0 +1,99 @@ +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/t06_publish/jfrog.py b/system/t06_publish/jfrog.py new file mode 100644 index 00000000..fd319c11 --- /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' + ) From 62447479124d6ef9d21c6a7655131ce8183e1100 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 11:26:30 -0400 Subject: [PATCH 04/14] chore: fix flake8 linting errors in jfrog test wrappers --- system/jfrog_lib.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/system/jfrog_lib.py b/system/jfrog_lib.py index 6dc1f5dc..e153892d 100644 --- a/system/jfrog_lib.py +++ b/system/jfrog_lib.py @@ -31,7 +31,7 @@ class JFrogTest(BaseTest): 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}" @@ -65,11 +65,10 @@ class JFrogTest(BaseTest): 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}" @@ -90,10 +89,9 @@ class JFrogTest(BaseTest): 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 - From 041eeff67ddc17a7e3013872c10ecd506ac58fb3 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 11:33:17 -0400 Subject: [PATCH 05/14] test: update ConfigShowTest_gold with JFrogPublishEndpoints --- system/t02_config/ConfigShowTest_gold | 1 + 1 file changed, 1 insertion(+) diff --git a/system/t02_config/ConfigShowTest_gold b/system/t02_config/ConfigShowTest_gold index 46b57cee..8f12e639 100644 --- a/system/t02_config/ConfigShowTest_gold +++ b/system/t02_config/ConfigShowTest_gold @@ -34,6 +34,7 @@ "skipContentsPublishing": false, "skipBz2Publishing": false, "FileSystemPublishEndpoints": {}, + "JFrogPublishEndpoints": null, "S3PublishEndpoints": {}, "GcsPublishEndpoints": {}, "SwiftPublishEndpoints": {}, From fbad25e2b553a94efeda3deadf28ce10e75e69ff Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 13:27:24 -0400 Subject: [PATCH 06/14] fix: add jfrog parameters to default config generation test --- system/t02_config/CreateConfigTest_gold | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/system/t02_config/CreateConfigTest_gold b/system/t02_config/CreateConfigTest_gold index 93177314..d8e39c9d 100644 --- a/system/t02_config/CreateConfigTest_gold +++ b/system/t02_config/CreateConfigTest_gold @@ -196,6 +196,35 @@ 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. +# +# The destination Artifactory repo should be of the "generic" type, not "debian". +# +# 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 + # # Jfrog credentials to authenticate to Artifactory. If not supplied, the + # # environment variables `JFROG_USERNAME`, `JFROG_PASSWORD`, `JFROG_APIKEY`, + # # and `JFROG_ACCESSTOKEN` can be used + # # Authentication requires one of: user+pass, api key, or access token + # username: admin + # password: password + # api_key: api_key + # access_token: access_token + s3_publish_endpoints: # # Endpoint Name # test: @@ -396,3 +425,5 @@ packagepool_storage: # # defaults to "https://.blob.core.windows.net" # endpoint: "" + + From ce070ec010a779e0215ffb2200b4933ec915ab53 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 16:02:12 -0400 Subject: [PATCH 07/14] fix: attempt to fox code coverage --- context/context_test.go | 50 +++++- jfrog/public_test.go | 348 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 6 deletions(-) create mode 100644 jfrog/public_test.go diff --git a/context/context_test.go b/context/context_test.go index 16ecbb2b..63e0bccf 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -2,10 +2,10 @@ package context import ( "fmt" - "os" "reflect" "testing" + "github.com/aptly-dev/aptly/utils" "github.com/smira/flag" . "gopkg.in/check.v1" @@ -79,11 +79,49 @@ func (s *AptlyContextSuite) SetUpTest(c *C) { } func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) { - // https://github.com/aptly-dev/aptly/issues/711 - // This will fail on account of us not having a config, so the - // storage never exists. + prevConfig := utils.Config + defer func() { utils.Config = prevConfig }() + + s.context.configLoaded = true + utils.Config.FileSystemPublishRoots = map[string]utils.FileSystemPublishRoot{} + c.Assert(func() { s.context.GetPublishedStorage("filesystem:fuji") }, FatalErrorPanicMatches, - &FatalError{ReturnCode: 1, Message: fmt.Sprintf("error loading config file %s/.aptly.conf: invalid yaml (EOF) or json (EOF)", - os.Getenv("HOME"))}) + &FatalError{ReturnCode: 1, Message: "published local storage fuji not configured"}) +} + +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"}) } diff --git a/jfrog/public_test.go b/jfrog/public_test.go new file mode 100644 index 00000000..e88755f4 --- /dev/null +++ b/jfrog/public_test.go @@ -0,0 +1,348 @@ +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) 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) 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) From 7c47b8662f3c3a3a57faff511b0fc945ca2cfbc5 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 19:31:01 -0400 Subject: [PATCH 08/14] update --- context/context_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/context/context_test.go b/context/context_test.go index 63e0bccf..678ef103 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -2,6 +2,7 @@ package context import ( "fmt" + "os" "reflect" "testing" @@ -79,15 +80,13 @@ func (s *AptlyContextSuite) SetUpTest(c *C) { } func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) { - prevConfig := utils.Config - defer func() { utils.Config = prevConfig }() - - s.context.configLoaded = true - utils.Config.FileSystemPublishRoots = map[string]utils.FileSystemPublishRoot{} - + // https://github.com/aptly-dev/aptly/issues/711 + // This will fail on account of us not having a config, so the + // storage never exists. c.Assert(func() { s.context.GetPublishedStorage("filesystem:fuji") }, FatalErrorPanicMatches, - &FatalError{ReturnCode: 1, Message: "published local storage fuji not configured"}) + &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) { From 7a2a82c60e170e21473a04163c7ee7a6d9465ca2 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Tue, 7 Apr 2026 20:21:37 -0400 Subject: [PATCH 09/14] more code coverage --- jfrog/public_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jfrog/public_test.go b/jfrog/public_test.go index e88755f4..d92b6471 100644 --- a/jfrog/public_test.go +++ b/jfrog/public_test.go @@ -159,6 +159,15 @@ func (s *PublishedStorageSuite) TestRemove(c *C) { 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") @@ -327,6 +336,13 @@ func (s *PublishedStorageSuite) TestNewPublishedStorageRaw(c *C) { 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) From 9502f3833f146d3c33523ebc5497e16073abdabc Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Wed, 8 Apr 2026 22:45:50 -0400 Subject: [PATCH 10/14] test(jfrog): cover JFrog storage init error path and fix branch lint/coverage hygiene --- context/context_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/context/context_test.go b/context/context_test.go index 678ef103..89e8a96d 100644 --- a/context/context_test.go +++ b/context/context_test.go @@ -124,3 +124,28 @@ func (s *AptlyContextSuite) TestGetPublishedStorageJFrogMissing(c *C) { 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") +} From acc79d2cf60b5eb85025d9747833f8c8cd681ab2 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Thu, 9 Apr 2026 11:29:08 -0400 Subject: [PATCH 11/14] fix: upgrade ulikunitz/xz to v0.5.15 to fix 32-bit build failures v0.5.14 introduced a `1 << 31` constant assigned to an `int` variable in lzma/reader.go, which overflows on 32-bit architectures (i386, armhf). v0.5.15 fixes this by using int64. --- go.mod | 36 +++++++++++++++++-- go.sum | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 79914a73..8803d618 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 @@ -14,7 +14,7 @@ require ( github.com/jlaffaye/ftp v0.2.0 // indirect github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 github.com/klauspost/compress v1.18.2 - github.com/klauspost/pgzip v1.2.5 + 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 @@ -49,13 +49,17 @@ require ( cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/pubsub/v2 v2.4.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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // 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.7.8 // 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.4.21 // indirect @@ -77,12 +81,19 @@ require ( github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // 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/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // 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-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -94,28 +105,40 @@ 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/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/gookit/color v1.5.4 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // 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/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/cpuid/v2 v2.2.11 // 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/pkg/xattr v0.4.12 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -123,8 +146,14 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // 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/spiffe/go-spiffe/v2 v2.6.0 // 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.opencensus.io v0.24.0 // indirect @@ -140,6 +169,7 @@ require ( 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.49.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/text v0.33.0 // indirect @@ -149,6 +179,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.79.3 // 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 ) @@ -164,6 +195,7 @@ require ( github.com/aws/smithy-go v1.24.2 github.com/fsouza/fake-gcs-server v1.53.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 534ed0e4..72089d9a 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ cloud.google.com/go/storage v1.60.0 h1:oBfZrSOCimggVNz9Y/bXY35uUcts7OViubeddTTVz cloud.google.com/go/storage v1.60.0/go.mod h1:q+5196hXfejkctrnx+VYU8RKQr/L3c0cBIlrjmiAKE0= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= +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= @@ -38,6 +40,8 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQK 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= @@ -50,12 +54,21 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +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.4.0 h1:Zq/pbM3F5DFgJiMouxEdSVY44MVoQNEKp5d5QxIQceQ= github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= 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.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= @@ -96,6 +109,8 @@ github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 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/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= @@ -123,12 +138,21 @@ 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -145,6 +169,8 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/forPelevin/gomoji v1.3.0 h1:WPIOLWB1bvRYlKZnSSEevLt3IfKlLs+tK+YA9fFYlkE= +github.com/forPelevin/gomoji v1.3.0/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -159,6 +185,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-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= @@ -192,12 +228,14 @@ 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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -239,6 +277,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dq github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -252,25 +292,39 @@ 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.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +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.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +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= @@ -304,6 +358,8 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0= github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM= +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= @@ -318,6 +374,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= @@ -330,12 +388,17 @@ 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/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +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= @@ -370,6 +433,11 @@ 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= @@ -383,7 +451,9 @@ github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xI 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= @@ -403,14 +473,33 @@ 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/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY= github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= 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= @@ -459,9 +548,12 @@ 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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -484,6 +576,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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= @@ -508,12 +601,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= @@ -523,6 +618,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -597,6 +693,7 @@ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBN google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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= @@ -605,6 +702,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= From 3333a643cb26dc60601880857559d6f406e5fd4b Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Thu, 16 Apr 2026 16:21:18 -0400 Subject: [PATCH 12/14] fix(jfrog): fix LinkFromPool source path handling and restore YAML config tests --- jfrog/public.go | 42 ++++++++++++++++- jfrog/public_test.go | 109 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/jfrog/public.go b/jfrog/public.go index f478ee30..ec12decc 100644 --- a/jfrog/public.go +++ b/jfrog/public.go @@ -2,6 +2,8 @@ package jfrog import ( "fmt" + "io" + "os" "path/filepath" "strings" @@ -122,7 +124,45 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress } 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) + sourceFilename := sourcePath + cleanup := func() {} + + if sourcePool != nil { + if localPool, ok := sourcePool.(aptly.LocalPackagePool); ok { + sourceFilename = localPool.FullPath(sourcePath) + } else { + src, err := sourcePool.Open(sourcePath) + if err != nil { + return err + } + defer func() { _ = src.Close() }() + + tmpFile, err := os.CreateTemp("", "aptly-jfrog-pool-*") + if err != nil { + return err + } + + if _, err := io.Copy(tmpFile, src); err != nil { + _ = tmpFile.Close() + _ = os.Remove(tmpFile.Name()) + return err + } + + if err := tmpFile.Close(); err != nil { + _ = os.Remove(tmpFile.Name()) + return err + } + + sourceFilename = tmpFile.Name() + cleanup = func() { + _ = os.Remove(sourceFilename) + } + } + } + + defer cleanup() + + return storage.PutFile(filepath.Join(publishedPrefix, publishedRelPath, fileName), sourceFilename) } func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { diff --git a/jfrog/public_test.go b/jfrog/public_test.go index d92b6471..5600983f 100644 --- a/jfrog/public_test.go +++ b/jfrog/public_test.go @@ -102,6 +102,92 @@ type resultFixture struct { Results []jfrogutils.ResultItem `json:"results"` } +type fakeLocalPool struct{} + +func (p *fakeLocalPool) Verify(string, string, *aptly_utils.ChecksumInfo, aptly.ChecksumStorage) (string, bool, error) { + return "", false, nil +} + +func (p *fakeLocalPool) Import(string, string, *aptly_utils.ChecksumInfo, bool, aptly.ChecksumStorage) (string, error) { + return "", nil +} + +func (p *fakeLocalPool) LegacyPath(string, *aptly_utils.ChecksumInfo) (string, error) { + return "", nil +} + +func (p *fakeLocalPool) Size(string) (int64, error) { + return 0, nil +} + +func (p *fakeLocalPool) Open(string) (aptly.ReadSeekerCloser, error) { + return nil, errors.New("not implemented") +} + +func (p *fakeLocalPool) FilepathList(aptly.Progress) ([]string, error) { + return nil, nil +} + +func (p *fakeLocalPool) Remove(string) (int64, error) { + return 0, nil +} + +func (p *fakeLocalPool) Stat(string) (os.FileInfo, error) { + return nil, errors.New("not implemented") +} + +func (p *fakeLocalPool) GenerateTempPath(string) (string, error) { + return "", nil +} + +func (p *fakeLocalPool) Link(string, string) error { + return nil +} + +func (p *fakeLocalPool) Symlink(string, string) error { + return nil +} + +func (p *fakeLocalPool) FullPath(path string) string { + return filepath.Join("/var/lib/aptly/pool", path) +} + +type fakeRemotePool struct { + openPath string + openErr error +} + +func (p *fakeRemotePool) Verify(string, string, *aptly_utils.ChecksumInfo, aptly.ChecksumStorage) (string, bool, error) { + return "", false, nil +} + +func (p *fakeRemotePool) Import(string, string, *aptly_utils.ChecksumInfo, bool, aptly.ChecksumStorage) (string, error) { + return "", nil +} + +func (p *fakeRemotePool) LegacyPath(string, *aptly_utils.ChecksumInfo) (string, error) { + return "", nil +} + +func (p *fakeRemotePool) Size(string) (int64, error) { + return 0, nil +} + +func (p *fakeRemotePool) Open(string) (aptly.ReadSeekerCloser, error) { + if p.openErr != nil { + return nil, p.openErr + } + return os.Open(p.openPath) +} + +func (p *fakeRemotePool) FilepathList(aptly.Progress) ([]string, error) { + return nil, nil +} + +func (p *fakeRemotePool) Remove(string) (int64, error) { + return 0, nil +} + func createReader(c *C, results []jfrogutils.ResultItem) *content.ContentReader { filePath := filepath.Join(c.MkDir(), "results.json") data, err := json.Marshal(resultFixture{Results: results}) @@ -192,6 +278,29 @@ func (s *PublishedStorageSuite) TestLinkFromPoolDelegatesToPutFile(c *C) { c.Assert(s.manager.uploadParams[0].Target, Equals, filepath.Join("repo", "prefix", "pool/main/p", "pkg.deb")) } +func (s *PublishedStorageSuite) TestLinkFromPoolUsesLocalPoolFullPath(c *C) { + pool := &fakeLocalPool{} + poolPath := "e3/48/84d71bb98002bf0c775479aa31ee_accountsservice_0.6.55-0ubuntu11_amd64.deb" + + err := s.storage.LinkFromPool("", "pool/main/p", "pkg.deb", pool, poolPath, aptly_utils.ChecksumInfo{}, false) + c.Assert(err, IsNil) + c.Assert(s.manager.uploadParams[0].Pattern, Equals, filepath.Join("/var/lib/aptly/pool", poolPath)) +} + +func (s *PublishedStorageSuite) TestLinkFromPoolCopiesFromRemotePool(c *C) { + tmpFile := filepath.Join(c.MkDir(), "source.deb") + c.Assert(os.WriteFile(tmpFile, []byte("package-bytes"), 0o644), IsNil) + + pool := &fakeRemotePool{openPath: tmpFile} + err := s.storage.LinkFromPool("", "pool/main/p", "pkg.deb", pool, "hash/path/pkg.deb", aptly_utils.ChecksumInfo{}, false) + c.Assert(err, IsNil) + + uploadPath := s.manager.uploadParams[0].Pattern + c.Assert(uploadPath, Not(Equals), "hash/path/pkg.deb") + _, statErr := os.Stat(uploadPath) + c.Assert(os.IsNotExist(statErr), Equals, true) +} + 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"}, From 4655f10048c2f49c19e672c859f1e09c142d4a91 Mon Sep 17 00:00:00 2001 From: iofq Date: Thu, 30 Apr 2026 14:07:36 -0500 Subject: [PATCH 13/14] feat: Allow Jfrog Artifactory authentication to be configured via env var --- jfrog/public.go | 77 +++++++++++++++++++++++++++----------------- jfrog/public_test.go | 40 ++++++++++++++++++++++- 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/jfrog/public.go b/jfrog/public.go index ec12decc..740e1af9 100644 --- a/jfrog/public.go +++ b/jfrog/public.go @@ -30,28 +30,45 @@ var ( _ aptly.PublishedStorage = (*PublishedStorage)(nil) ) +func createPublishedStorageConfig(url, user, password, apiKey, accessToken string) (config.Config, error) { + artDetails := auth.NewArtifactoryDetails() + artDetails.SetUrl(url) + + if user == "" { + user = os.Getenv("JFROG_USERNAME"); + } + if password == "" { + password = os.Getenv("JFROG_PASSWORD"); + } + if apiKey == "" { + apiKey = os.Getenv("JFROG_APIKEY"); + } + if accessToken == "" { + accessToken = os.Getenv("JFROG_ACCESSTOKEN"); + } + + if user != "" && password != "" { + artDetails.SetUser(user) + artDetails.SetPassword(password) + } else if apiKey != "" { + artDetails.SetApiKey(apiKey) + } else if accessToken != "" { + artDetails.SetAccessToken(accessToken) + } + + return config.NewConfigBuilder(). + SetServiceDetails(artDetails). + SetDryRun(false). + Build() +} + // 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() - + serviceConfig, err := createPublishedStorageConfig(url, user, password, apiKey, accessToken) if err != nil { return nil, errors.Wrap(err, "error building jfrog client config") } @@ -91,7 +108,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err if storage.plusWorkaround { targetPath = strings.Replace(targetPath, "+", "%2B", -1) } - + params := services.NewUploadParams() params.Pattern = sourceFilename params.Target = targetPath @@ -106,7 +123,7 @@ func (storage *PublishedStorage) Remove(path string) error { if storage.plusWorkaround { targetPath = strings.Replace(targetPath, "+", "%2B", -1) } - + deleteParams := services.NewDeleteParams() deleteParams.SetPattern(targetPath) @@ -175,7 +192,7 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { return nil, err } defer reader.Close() - + var paths []string for element := new(utils.ResultItem); reader.NextRecord(element) == nil; element = new(utils.ResultItem) { @@ -186,19 +203,19 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { } 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 @@ -211,17 +228,17 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error { 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) @@ -239,7 +256,7 @@ func (storage *PublishedStorage) FileExists(path string) (bool, error) { if storage.plusWorkaround { targetPath = strings.Replace(targetPath, "+", "%2B", -1) } - + params := services.NewSearchParams() params.Pattern = targetPath @@ -248,7 +265,7 @@ func (storage *PublishedStorage) FileExists(path string) (bool, error) { return false, err } defer reader.Close() - + length, err := reader.Length() isEmpty := length == 0 return !isEmpty, err @@ -259,17 +276,17 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) { 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 index 5600983f..d6f7ff3a 100644 --- a/jfrog/public_test.go +++ b/jfrog/public_test.go @@ -18,7 +18,10 @@ import ( . "gopkg.in/check.v1" ) -func Test(t *testing.T) { TestingT(t) } +func Test(t *testing.T) { + t.Setenv("JFROG_USERNAME", "userfromenv") + TestingT(t) +} type fakeJFrogManager struct { artifactory.EmptyArtifactoryServicesManager @@ -425,6 +428,41 @@ func (s *PublishedStorageSuite) TestReadLinkPlusWorkaround(c *C) { c.Assert(s.manager.itemPropsErr, IsNil) } +func (s *PublishedStorageSuite) TestCreatePublishedStorageConfig(c *C) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + withUserPassword, err := createPublishedStorageConfig(server.URL, "user", "password", "", "") + c.Assert(err, IsNil) + + withUserPasswordDetails := withUserPassword.GetServiceDetails() + c.Assert(withUserPasswordDetails, NotNil) + c.Assert(withUserPasswordDetails.GetUser(), Equals, "user") + + withAPIKey, err := createPublishedStorageConfig(server.URL, "", "", "api-123", "") + c.Assert(err, IsNil) + + withAPIKeyDetails := withAPIKey.GetServiceDetails() + c.Assert(withAPIKeyDetails, NotNil) + c.Assert(withAPIKeyDetails.GetApiKey(), Equals, "api-123") + + withAccessToken, err := createPublishedStorageConfig(server.URL, "", "", "", "token") + c.Assert(err, IsNil) + + withAccessTokenDetails := withAccessToken.GetServiceDetails() + c.Assert(withAccessTokenDetails, NotNil) + c.Assert(withAccessTokenDetails.GetAccessToken(), Equals, "token") + + withUserPasswordFromEnv, err := createPublishedStorageConfig(server.URL, "", "password", "", "") + c.Assert(err, IsNil) + + withUserPasswordFromEnvDetails := withUserPasswordFromEnv.GetServiceDetails() + c.Assert(withUserPasswordFromEnvDetails, NotNil) + c.Assert(withUserPasswordFromEnvDetails.GetUser(), Equals, "userfromenv") +} + func (s *PublishedStorageSuite) TestNewPublishedStorageRaw(c *C) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) From 160d16d96c492891da92ad6678cdf2166fc747ee Mon Sep 17 00:00:00 2001 From: iofq Date: Thu, 30 Apr 2026 16:50:11 -0500 Subject: [PATCH 14/14] docs: Update JFrog Artifactory documentation in config --- debian/aptly.conf | 10 ++++++++-- man/aptly.1.ronn.tmpl | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/debian/aptly.conf b/debian/aptly.conf index 98e75bce..043b46eb 100644 --- a/debian/aptly.conf +++ b/debian/aptly.conf @@ -202,6 +202,8 @@ filesystem_publish_endpoints: # aptly can be configured to publish repositories directly to JFrog Artifactory. First, # publishing endpoints should be described in the aptly configuration file. # +# The destination Artifactory repo should be of the "generic" type, not "debian". +# # In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before # publishing prefix on the command line, e.g.: # @@ -214,10 +216,14 @@ jfrog_publish_endpoints: # url: "https://artifactory.example.com/artifactory/" # # Repository # repository: apt-local - # # Username + # # Jfrog credentials to authenticate to Artifactory. If not supplied, the + # # environment variables `JFROG_USERNAME`, `JFROG_PASSWORD`, `JFROG_APIKEY`, + # # and `JFROG_ACCESSTOKEN` can be used + # # Authentication requires one of: user+pass, api key, or access token # username: admin - # # Password # password: password + # api_key: api_key + # access_token: access_token s3_publish_endpoints: # # Endpoint Name diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl index acfcc612..2119e40a 100644 --- a/man/aptly.1.ronn.tmpl +++ b/man/aptly.1.ronn.tmpl @@ -360,6 +360,9 @@ The legacy json configuration is still supported (and also supports comments): // aptly could be configured to publish repository directly to JFrog Artifactory. First, // endpoints should be described in aptly.conf: // + // The destination Artifactory repo should be of the "generic" type, not "debian". + // Authentication requires one of: user+pass, api key, or access token + // // In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before // publishing prefix on the command line, e.g.: // @@ -370,7 +373,9 @@ The legacy json configuration is still supported (and also supports comments): "url": "https://artifactory.example.com/artifactory/", "repository": "apt-local", "username": "admin", - "password": "password" + "password": "password", + "api_key": "api_key", + "access_token": "access_token" } }