Skip to content

Commit b9d4aed

Browse files
authored
Merge pull request #1553 from linuxdataflow/feat/pls/jfrog-support
Add support for JFrog artifactory.
2 parents 8ebe80f + 160d16d commit b9d4aed

22 files changed

Lines changed: 1598 additions & 214 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ jobs:
100100
AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
101101
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
102102
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
103+
JFROG_URL: ${{ secrets.JFROG_URL }}
104+
JFROG_USERNAME: ${{ secrets.JFROG_USERNAME }}
105+
JFROG_PASSWORD: ${{ secrets.JFROG_PASSWORD }}
103106
run: |
104107
sudo mkdir -p /srv ; sudo chown runner /srv
105108
mkdir -p out/coverage

.github/workflows/golangci-lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ jobs:
4545
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
4646
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
4747
version: v1.64.5
48+
args: --timeout=10m
4849

4950
# Optional: working directory, useful for monorepos
5051
# working-directory: somedir

api/api_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ func createTestConfig() *os.File {
5353
"bucket": "bucket-gcs",
5454
},
5555
},
56+
"JFrogPublishEndpoints": map[string]map[string]string{
57+
"test-jfrog": {
58+
"url": "http://jfrog.example.com",
59+
},
60+
},
5661
})
5762
if err != nil {
5863
return nil
@@ -186,6 +191,18 @@ func (s *APISuite) TestTruthy(c *C) {
186191
c.Check(truthy(gin.H{}), Equals, true)
187192
}
188193

194+
func (s *APISuite) TestGetJFrogEndpoints(c *C) {
195+
response, err := s.HTTPRequest("GET", "/api/jfrog", nil)
196+
c.Assert(err, IsNil)
197+
c.Check(response.Code, Equals, 200)
198+
199+
var endpoints []string
200+
err = json.Unmarshal(response.Body.Bytes(), &endpoints)
201+
c.Assert(err, IsNil)
202+
sort.Strings(endpoints)
203+
c.Check(endpoints, DeepEquals, []string{"test-jfrog"})
204+
}
205+
189206
func (s *APISuite) TestGetS3Endpoints(c *C) {
190207
response, err := s.HTTPRequest("GET", "/api/s3", nil)
191208
c.Assert(err, IsNil)

api/jfrog.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package api
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
)
6+
7+
// @Summary JFrog repositories
8+
// @Description **Get list of JFrog repositories**
9+
// @Description
10+
// @Description List configured JFrog publish endpoints.
11+
// @Tags Status
12+
// @Produce json
13+
// @Success 200 {array} string "List of JFrog publish endpoints"
14+
// @Router /api/jfrog [get]
15+
func apiJFrogList(c *gin.Context) {
16+
keys := []string{}
17+
for k := range context.Config().JFrogPublishRoots {
18+
keys = append(keys, k)
19+
}
20+
c.JSON(200, keys)
21+
}

api/router.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
178178
{
179179
api.GET("/s3", apiS3List)
180180
api.GET("/gcs", apiGCSList)
181+
api.GET("/jfrog", apiJFrogList)
181182
}
182183

183184
{

context/context.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/aptly-dev/aptly/files"
2626
"github.com/aptly-dev/aptly/gcs"
2727
"github.com/aptly-dev/aptly/http"
28+
"github.com/aptly-dev/aptly/jfrog"
2829
"github.com/aptly-dev/aptly/pgp"
2930
"github.com/aptly-dev/aptly/s3"
3031
"github.com/aptly-dev/aptly/swift"
@@ -476,6 +477,18 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto
476477
if err != nil {
477478
Fatal(err)
478479
}
480+
} else if strings.HasPrefix(name, "jfrog:") {
481+
params, ok := context.config().JFrogPublishRoots[name[6:]]
482+
if !ok {
483+
Fatal(fmt.Errorf("published JFrog storage %v not configured", name[6:]))
484+
}
485+
486+
var err error
487+
publishedStorage, err = jfrog.NewPublishedStorage(
488+
name[6:], params)
489+
if err != nil {
490+
Fatal(err)
491+
}
479492
} else {
480493
Fatal(fmt.Errorf("unknown published storage format: %v", name))
481494
}

context/context_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"reflect"
77
"testing"
88

9+
"github.com/aptly-dev/aptly/utils"
910
"github.com/smira/flag"
1011

1112
. "gopkg.in/check.v1"
@@ -87,3 +88,64 @@ func (s *AptlyContextSuite) TestGetPublishedStorageBadFS(c *C) {
8788
&FatalError{ReturnCode: 1, Message: fmt.Sprintf("error loading config file %s/.aptly.conf: invalid yaml (EOF) or json (EOF)",
8889
os.Getenv("HOME"))})
8990
}
91+
92+
func (s *AptlyContextSuite) TestGetPublishedStorageJFrogConfigured(c *C) {
93+
prevConfig := utils.Config
94+
defer func() { utils.Config = prevConfig }()
95+
96+
s.context.configLoaded = true
97+
utils.Config.RootDir = c.MkDir()
98+
utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{
99+
"test": {
100+
Repository: "aptly-repo",
101+
Url: "https://example.jfrog.local/artifactory",
102+
AccessToken: "token",
103+
Prefix: "public",
104+
},
105+
}
106+
107+
storage := s.context.GetPublishedStorage("jfrog:test")
108+
c.Assert(storage, NotNil)
109+
c.Assert(fmt.Sprintf("%v", storage), Equals, "jfrog:aptly-repo:public")
110+
111+
// Ensure we get the cached object on repeated lookups.
112+
storageAgain := s.context.GetPublishedStorage("jfrog:test")
113+
c.Assert(storageAgain, Equals, storage)
114+
}
115+
116+
func (s *AptlyContextSuite) TestGetPublishedStorageJFrogMissing(c *C) {
117+
prevConfig := utils.Config
118+
defer func() { utils.Config = prevConfig }()
119+
120+
s.context.configLoaded = true
121+
utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{}
122+
123+
c.Assert(func() { s.context.GetPublishedStorage("jfrog:missing") },
124+
FatalErrorPanicMatches,
125+
&FatalError{ReturnCode: 1, Message: "published JFrog storage missing not configured"})
126+
}
127+
128+
func (s *AptlyContextSuite) TestGetPublishedStorageJFrogInitError(c *C) {
129+
prevConfig := utils.Config
130+
defer func() { utils.Config = prevConfig }()
131+
132+
s.context.configLoaded = true
133+
utils.Config.JFrogPublishRoots = map[string]utils.JFrogPublishRoot{
134+
"broken": {
135+
Repository: "aptly-repo",
136+
Url: "ssh://example.local/artifactory",
137+
},
138+
}
139+
140+
defer func() {
141+
obtained := recover()
142+
c.Assert(obtained, NotNil)
143+
144+
fatalErr, ok := obtained.(*FatalError)
145+
c.Assert(ok, Equals, true)
146+
c.Check(fatalErr.ReturnCode, Equals, 1)
147+
c.Check(fatalErr.Message, Matches, `error creating jfrog manager: .*`)
148+
}()
149+
150+
s.context.GetPublishedStorage("jfrog:broken")
151+
}

debian/aptly.conf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,35 @@ filesystem_publish_endpoints:
196196
#
197197
# `aptly publish snapshot wheezy-main s3:test:`
198198
#
199+
200+
# JFrog Artifactory Endpoint Support
201+
#
202+
# aptly can be configured to publish repositories directly to JFrog Artifactory. First,
203+
# publishing endpoints should be described in the aptly configuration file.
204+
#
205+
# The destination Artifactory repo should be of the "generic" type, not "debian".
206+
#
207+
# In order to publish to JFrog, specify endpoint as `jfrog:endpoint-name:` before
208+
# publishing prefix on the command line, e.g.:
209+
#
210+
# `aptly publish snapshot wheezy-main jfrog:test:`
211+
#
212+
jfrog_publish_endpoints:
213+
# # Endpoint Name
214+
# test:
215+
# # JFrog URL
216+
# url: "https://artifactory.example.com/artifactory/"
217+
# # Repository
218+
# repository: apt-local
219+
# # Jfrog credentials to authenticate to Artifactory. If not supplied, the
220+
# # environment variables `JFROG_USERNAME`, `JFROG_PASSWORD`, `JFROG_APIKEY`,
221+
# # and `JFROG_ACCESSTOKEN` can be used
222+
# # Authentication requires one of: user+pass, api key, or access token
223+
# username: admin
224+
# password: password
225+
# api_key: api_key
226+
# access_token: access_token
227+
199228
s3_publish_endpoints:
200229
# # Endpoint Name
201230
# test:

docs/Publish.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Publish snapshot or local repo as Debian repository to be used as APT source on
55

66
The published repository is signed with the user's GnuPG key.
77

8-
Repositories can be published to local directories, Amazon S3 buckets, Azure or Swift Storage.
8+
Repositories can be published to local directories, Amazon S3 buckets, Azure, Swift, or JFrog Artifactory Storage.
99

1010
#### GPG Keys
1111

go.mod

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/aptly-dev/aptly
22

3-
go 1.24.0
3+
go 1.24.6
44

55
require (
66
github.com/AlekSi/pointer v1.1.0
@@ -14,7 +14,7 @@ require (
1414
github.com/jlaffaye/ftp v0.2.0 // indirect
1515
github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2
1616
github.com/klauspost/compress v1.18.2
17-
github.com/klauspost/pgzip v1.2.5
17+
github.com/klauspost/pgzip v1.2.6
1818
github.com/mattn/go-colorable v0.1.13 // indirect
1919
github.com/mattn/go-runewidth v0.0.15 // indirect
2020
github.com/mattn/go-shellwords v1.0.12
@@ -49,13 +49,17 @@ require (
4949
cloud.google.com/go/iam v1.5.3 // indirect
5050
cloud.google.com/go/monitoring v1.24.3 // indirect
5151
cloud.google.com/go/pubsub/v2 v2.4.0 // indirect
52+
dario.cat/mergo v1.0.1 // indirect
5253
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
54+
github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect
5355
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
5456
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
5557
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
5658
github.com/KyleBanks/depth v1.2.1 // indirect
59+
github.com/Microsoft/go-winio v0.6.2 // indirect
5760
github.com/PuerkitoBio/purell v1.1.1 // indirect
5861
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
62+
github.com/andybalholm/brotli v1.1.1 // indirect
5963
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
6064
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
6165
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
@@ -77,12 +81,19 @@ require (
7781
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
7882
github.com/coreos/go-semver v0.3.0 // indirect
7983
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
84+
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
85+
github.com/dsnet/compress v0.0.1 // indirect
86+
github.com/emirpasic/gods v1.18.1 // indirect
8087
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
8188
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
8289
github.com/fatih/color v1.17.0 // indirect
8390
github.com/felixge/httpsnoop v1.0.4 // indirect
91+
github.com/forPelevin/gomoji v1.3.0 // indirect
8492
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
8593
github.com/gin-contrib/sse v0.1.0 // indirect
94+
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
95+
github.com/go-git/go-billy/v5 v5.6.2 // indirect
96+
github.com/go-git/go-git/v5 v5.14.0 // indirect
8697
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
8798
github.com/go-logr/logr v1.4.3 // indirect
8899
github.com/go-logr/stdr v1.2.2 // indirect
@@ -94,37 +105,55 @@ require (
94105
github.com/go-playground/universal-translator v0.18.1 // indirect
95106
github.com/goccy/go-json v0.10.2 // indirect
96107
github.com/gogo/protobuf v1.3.2 // indirect
108+
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
109+
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
97110
github.com/golang/protobuf v1.5.4 // indirect
98111
github.com/golang/snappy v0.0.4 // indirect
99112
github.com/google/renameio/v2 v2.0.0 // indirect
100113
github.com/google/s2a-go v0.1.9 // indirect
101114
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
102115
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
116+
github.com/gookit/color v1.5.4 // indirect
103117
github.com/gorilla/handlers v1.5.2 // indirect
104118
github.com/gorilla/mux v1.8.1 // indirect
105119
github.com/hashicorp/errwrap v1.1.0 // indirect
106120
github.com/hashicorp/go-multierror v1.1.1 // indirect
121+
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
122+
github.com/jfrog/archiver/v3 v3.6.1 // indirect
123+
github.com/jfrog/build-info-go v1.11.0 // indirect
124+
github.com/jfrog/gofrog v1.7.6 // indirect
107125
github.com/josharian/intern v1.0.0 // indirect
108126
github.com/json-iterator/go v1.1.12 // indirect
127+
github.com/kevinburke/ssh_config v1.2.0 // indirect
109128
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
110129
github.com/kr/pretty v0.3.1 // indirect
111130
github.com/kr/text v0.2.0 // indirect
112131
github.com/leodido/go-urn v1.2.4 // indirect
113132
github.com/mailru/easyjson v0.7.6 // indirect
114133
github.com/mattn/go-isatty v0.0.20 // indirect
134+
github.com/minio/sha256-simd v1.0.1 // indirect
115135
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
116136
github.com/modern-go/reflect2 v1.0.2 // indirect
117137
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
138+
github.com/nwaples/rardecode v1.1.3 // indirect
118139
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
140+
github.com/pierrec/lz4/v4 v4.1.22 // indirect
141+
github.com/pjbgf/sha1cd v0.3.2 // indirect
119142
github.com/pkg/xattr v0.4.12 // indirect
120143
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
121144
github.com/prometheus/client_model v0.6.2 // indirect
122145
github.com/prometheus/common v0.59.1 // indirect
123146
github.com/prometheus/procfs v0.15.1 // indirect
124147
github.com/rivo/uniseg v0.4.7 // indirect
125148
github.com/rogpeppe/go-internal v1.14.1 // indirect
149+
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
150+
github.com/skeema/knownhosts v1.3.1 // indirect
126151
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
127152
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
153+
github.com/ulikunitz/xz v0.5.15 // indirect
154+
github.com/xanzy/ssh-agent v0.3.3 // indirect
155+
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
156+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
128157
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
129158
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
130159
go.opencensus.io v0.24.0 // indirect
@@ -140,6 +169,7 @@ require (
140169
go.uber.org/multierr v1.10.0 // indirect
141170
go.uber.org/zap v1.26.0 // indirect
142171
golang.org/x/arch v0.3.0 // indirect
172+
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
143173
golang.org/x/net v0.49.0 // indirect
144174
golang.org/x/sync v0.19.0 // indirect
145175
golang.org/x/text v0.33.0 // indirect
@@ -149,6 +179,7 @@ require (
149179
google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect
150180
google.golang.org/grpc v1.79.3 // indirect
151181
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
182+
gopkg.in/warnings.v0 v0.1.2 // indirect
152183
gopkg.in/yaml.v2 v2.4.0 // indirect
153184
)
154185

@@ -164,6 +195,7 @@ require (
164195
github.com/aws/smithy-go v1.24.2
165196
github.com/fsouza/fake-gcs-server v1.53.1
166197
github.com/google/uuid v1.6.0
198+
github.com/jfrog/jfrog-client-go v1.55.0
167199
github.com/swaggo/files v1.0.1
168200
github.com/swaggo/gin-swagger v1.6.0
169201
github.com/swaggo/swag v1.16.3

0 commit comments

Comments
 (0)