Skip to content

Commit d7c2690

Browse files
committed
This branch adds support for nested helm dependencies
The previous implementation had a recursive function but it terminated after one level This branch adds support for nested repository urls - the previous implementation only supported a single level Resolves: vmware-labs#67
1 parent 7798d72 commit d7c2690

File tree

7 files changed

+113
-38
lines changed

7 files changed

+113
-38
lines changed

pkg/chartutils/values.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ func parseValuesImageElement(data map[string]interface{}) *ValuesImageElement {
181181
for _, k := range imageElementKeys {
182182
v, ok := data[k]
183183
if !ok {
184-
// digest is optional
185-
if k == "digest" {
184+
// digest and registry are optional
185+
if k == "digest" || k == "registry" {
186186
continue
187187
}
188188
return nil
@@ -193,9 +193,5 @@ func parseValuesImageElement(data map[string]interface{}) *ValuesImageElement {
193193
}
194194
elemData[k] = vStr
195195
}
196-
// An empty registry may be acceptable, but not an empty repository
197-
if elemData["repository"] == "" {
198-
return nil
199-
}
200196
return valuesImageElementFromMap(elemData)
201197
}

pkg/relocator/chart.go

+23-24
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ type RelocationResult struct {
2525
Count int
2626
}
2727

28-
func relocateChart(chart *cu.Chart, prefix string, cfg *RelocateConfig) error {
29-
valuesReplRes, err := relocateValues(chart, prefix)
28+
func relocateChart(chart *cu.Chart, newRegistry string, cfg *RelocateConfig) error {
29+
valuesReplRes, err := relocateValues(chart, newRegistry)
3030
if err != nil {
3131
return fmt.Errorf("failed to relocate chart: %v", err)
3232
}
@@ -42,7 +42,7 @@ func relocateChart(chart *cu.Chart, prefix string, cfg *RelocateConfig) error {
4242
var allErrors error
4343

4444
// TODO: Compare annotations with values replacements
45-
annotationsRelocResult, err := relocateAnnotations(chart, prefix)
45+
annotationsRelocResult, err := relocateAnnotations(chart, newRegistry)
4646
if err != nil {
4747
allErrors = errors.Join(allErrors, fmt.Errorf("failed to relocate Helm chart: %v", err))
4848
} else {
@@ -58,19 +58,27 @@ func relocateChart(chart *cu.Chart, prefix string, cfg *RelocateConfig) error {
5858

5959
lockFile := chart.LockFilePath()
6060
if utils.FileExists(lockFile) {
61-
err = RelocateLockFile(lockFile, prefix)
61+
err = RelocateLockFile(lockFile, newRegistry)
6262
if err != nil {
6363
allErrors = errors.Join(allErrors, fmt.Errorf("failed to relocate Images.lock file: %v", err))
6464
}
6565
}
6666

67+
if cfg.Recursive {
68+
for _, dep := range chart.Dependencies() {
69+
if err := relocateChart(dep, newRegistry, cfg); err != nil {
70+
allErrors = errors.Join(allErrors, fmt.Errorf("failed to relocate Helm SubChart %q: %v", dep.Chart().ChartFullPath(), err))
71+
}
72+
}
73+
}
74+
6775
return allErrors
6876
}
6977

7078
// RelocateChartDir relocates the chart (Chart.yaml annotations, Images.lock and values.yaml) specified
7179
// by chartPath using the provided prefix
72-
func RelocateChartDir(chartPath string, prefix string, opts ...RelocateOption) error {
73-
prefix = normalizeRelocateURL(prefix)
80+
func RelocateChartDir(chartPath string, newRegistry string, opts ...RelocateOption) error {
81+
newRegistry = normalizeRelocateURL(newRegistry)
7482

7583
cfg := NewRelocateConfig(opts...)
7684

@@ -79,39 +87,30 @@ func RelocateChartDir(chartPath string, prefix string, opts ...RelocateOption) e
7987
return fmt.Errorf("failed to load Helm chart: %v", err)
8088
}
8189

82-
err = relocateChart(chart, prefix, cfg)
90+
err = relocateChart(chart, newRegistry, cfg)
8391
if err != nil {
8492
return err
8593
}
8694
if utils.FileExists(filepath.Join(chartPath, carvel.CarvelImagesFilePath)) {
87-
err = relocateCarvelBundle(chartPath, prefix)
95+
err = relocateCarvelBundle(chartPath, newRegistry)
8896

8997
if err != nil {
9098
return err
9199
}
92100
}
93101

94-
var allErrors error
95-
96-
if cfg.Recursive {
97-
for _, dep := range chart.Dependencies() {
98-
if err := relocateChart(dep, prefix, cfg); err != nil {
99-
allErrors = errors.Join(allErrors, fmt.Errorf("failed to reloacte Helm SubChart %q: %v", dep.Chart().ChartFullPath(), err))
100-
}
101-
}
102-
}
103-
return allErrors
102+
return err
104103
}
105104

106-
func relocateCarvelBundle(chartRoot string, prefix string) error {
105+
func relocateCarvelBundle(chartRoot string, newRegistry string) error {
107106

108107
//TODO: Do better detection here, imgpkg probably has something
109108
carvelImagesFile := filepath.Join(chartRoot, carvel.CarvelImagesFilePath)
110109
lock, err := lockconfig.NewImagesLockFromPath(carvelImagesFile)
111110
if err != nil {
112111
return fmt.Errorf("failed to load Carvel images lock: %v", err)
113112
}
114-
result, err := RelocateCarvelImagesLock(&lock, prefix)
113+
result, err := RelocateCarvelImagesLock(&lock, newRegistry)
115114
if err != nil {
116115
return err
117116
}
@@ -125,9 +124,9 @@ func relocateCarvelBundle(chartRoot string, prefix string) error {
125124
}
126125

127126
// RelocateCarvelImagesLock rewrites the images urls in the provided lock using prefix
128-
func RelocateCarvelImagesLock(lock *lockconfig.ImagesLock, prefix string) (*RelocationResult, error) {
127+
func RelocateCarvelImagesLock(lock *lockconfig.ImagesLock, newRegistry string) (*RelocationResult, error) {
129128

130-
count, err := relocateCarvelImages(lock.Images, prefix)
129+
count, err := relocateCarvelImages(lock.Images, newRegistry)
131130
if err != nil {
132131
return nil, fmt.Errorf("failed to relocate Carvel images lock file: %v", err)
133132
}
@@ -141,10 +140,10 @@ func RelocateCarvelImagesLock(lock *lockconfig.ImagesLock, prefix string) (*Relo
141140

142141
}
143142

144-
func relocateCarvelImages(images []lockconfig.ImageRef, prefix string) (count int, err error) {
143+
func relocateCarvelImages(images []lockconfig.ImageRef, newRegistry string) (count int, err error) {
145144
var allErrors error
146145
for i, img := range images {
147-
norm, err := utils.RelocateImageURL(img.Image, prefix, true)
146+
norm, err := utils.RelocateImageURL(img.Image, newRegistry, true)
148147
if err != nil {
149148
allErrors = errors.Join(allErrors, err)
150149
continue

pkg/relocator/imagelock.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"github.com/vmware-labs/distribution-tooling-for-helm/pkg/utils"
1010
)
1111

12-
func relocateImages(images imagelock.ImageList, prefix string) (count int, err error) {
12+
func relocateImages(images imagelock.ImageList, newRegistry string) (count int, err error) {
1313
var allErrors error
1414
for _, img := range images {
15-
norm, err := utils.RelocateImageURL(img.Image, prefix, true)
15+
norm, err := utils.RelocateImageRegistry(img.Image, newRegistry, true)
1616
if err != nil {
1717
allErrors = errors.Join(allErrors, err)
1818
continue
@@ -24,8 +24,8 @@ func relocateImages(images imagelock.ImageList, prefix string) (count int, err e
2424
}
2525

2626
// RelocateLock rewrites the images urls in the provided lock using prefix
27-
func RelocateLock(lock *imagelock.ImagesLock, prefix string) (*RelocationResult, error) {
28-
count, err := relocateImages(lock.Images, prefix)
27+
func RelocateLock(lock *imagelock.ImagesLock, newRegistry string) (*RelocationResult, error) {
28+
count, err := relocateImages(lock.Images, newRegistry)
2929
if err != nil {
3030
return nil, fmt.Errorf("failed to relocate Images.lock file: %v", err)
3131
}
@@ -37,12 +37,12 @@ func RelocateLock(lock *imagelock.ImagesLock, prefix string) (*RelocationResult,
3737
}
3838

3939
// RelocateLockFile relocates images urls in the provided Images.lock using prefix
40-
func RelocateLockFile(file string, prefix string) error {
40+
func RelocateLockFile(file string, newRegistry string) error {
4141
lock, err := imagelock.FromYAMLFile(file)
4242
if err != nil {
4343
return fmt.Errorf("failed to load Images.lock: %v", err)
4444
}
45-
result, err := RelocateLock(lock, prefix)
45+
result, err := RelocateLock(lock, newRegistry)
4646
if err != nil {
4747
return err
4848
}

pkg/utils/utils.go

+49
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,55 @@ func RelocateImageURL(url string, prefix string, includeIndentifier bool) (strin
147147
return newURL, nil
148148
}
149149

150+
// RelocateImageRegistry rewrites the provided image URL by replacing its
151+
// registry with the newRegistry. If includeIdentifier is true, the tag or
152+
// digest of the image is included in the returned URL. The function returns
153+
// an error if the URL cannot be parsed or the new repository cannot be created.
154+
func RelocateImageRegistry(url string, newRegistry string,
155+
includeIndentifier bool) (string, error) {
156+
ref, err := name.ParseReference(url)
157+
if err != nil {
158+
return "", fmt.Errorf("failed to relocate url: %v", err)
159+
}
160+
161+
// Create a new repository with the new registry and the repository path
162+
// from the parsed reference
163+
newRepo, err := name.NewRepository(fmt.Sprintf("%s/%s", newRegistry,
164+
ref.Context().RepositoryStr()))
165+
if err != nil {
166+
return "", fmt.Errorf("failed to create new repository: %v", err)
167+
}
168+
169+
var newRef name.Reference
170+
switch v := ref.(type) {
171+
case name.Tag:
172+
// If the parsed reference is a Tag, create a new Tag with the new
173+
// repository and the tag from the parsed reference
174+
newRef, err = name.NewTag(fmt.Sprintf("%s:%s", newRepo.Name(),
175+
v.TagStr()), name.WeakValidation)
176+
case name.Digest:
177+
// If the parsed reference is a Digest, create a new Digest with the
178+
// new repository and the digest from the parsed reference
179+
newRef, err = name.NewDigest(fmt.Sprintf("%s@%s", newRepo.Name(),
180+
v.DigestStr()), name.WeakValidation)
181+
}
182+
if err != nil {
183+
return "", fmt.Errorf("failed to create new reference: %v", err)
184+
}
185+
186+
newURL := newRef.Context().Name()
187+
if includeIndentifier && newRef.Identifier() != "" {
188+
separator := ":"
189+
if _, ok := newRef.(name.Digest); ok {
190+
separator = "@"
191+
}
192+
newURL = fmt.Sprintf("%s%s%s", newURL, separator, newRef.Identifier())
193+
}
194+
195+
// Return the full name of the new reference, which includes the tag or digest
196+
return newURL, nil
197+
}
198+
150199
// ExecuteWithRetry executes a function retrying until it succeeds or the number of retries is reached
151200
func ExecuteWithRetry(retries int, cb func(try int, prevErr error) error) error {
152201
retry := 0

pkg/utils/utils_test.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,21 @@ func TestRelocateImageURL(t *testing.T) {
194194
url: "example.com:80/foo/bar/bitnami/app",
195195
prefix: newReg,
196196
},
197-
want: fmt.Sprintf("%s/bitnami/app", newReg),
197+
want: fmt.Sprintf("%s/foo/bar/bitnami/app", newReg),
198+
},
199+
"Replaces registry with doubly nested repository prefex": {
200+
args: args{
201+
url: "www.example.com/docker/abc/imagename",
202+
prefix: newReg,
203+
},
204+
want: fmt.Sprintf("%s/docker/abc/imagename", newReg),
205+
},
206+
"Replaces registry with triply nested repository prefix": {
207+
args: args{
208+
url: "www.example.com/docker/abc/testimages/imagename",
209+
prefix: newReg,
210+
},
211+
want: fmt.Sprintf("%s/docker/abc/testimages/imagename", newReg),
198212
},
199213
"Replaces library repositoy": {
200214
args: args{
@@ -213,7 +227,7 @@ func TestRelocateImageURL(t *testing.T) {
213227
}
214228
for name, tt := range tests {
215229
t.Run(name, func(t *testing.T) {
216-
got, err := RelocateImageURL(tt.args.url, tt.args.prefix, tt.args.includeIndentifier)
230+
got, err := RelocateImageRegistry(tt.args.url, tt.args.prefix, tt.args.includeIndentifier)
217231
validateError(t, tt.expectedErr, err)
218232

219233
if got != tt.want {

testdata/scenarios/chart1/charts/common/Chart.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ icon: https://bitnami.com/downloads/logos/bitnami-mark.png
1010
name: common
1111
type: library
1212
version: 2.6.0
13+
dependencies:
14+
- name: common2
15+
repository: oci://registry-1.docker.io/bitnamicharts
16+
version: 2.x.x
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
annotations:
2+
category: Infrastructure
3+
licenses: Apache-2.0
4+
apiVersion: v2
5+
appVersion: 2.6.0
6+
description: A Library Helm chart for grouping common logic between bitnami charts.
7+
This chart is not deployable by itself.
8+
home: https://bitnami.com
9+
icon: https://bitnami.com/downloads/logos/bitnami-mark.png
10+
name: common
11+
type: library
12+
version: 2.6.0

0 commit comments

Comments
 (0)