Skip to content

Commit 60ba913

Browse files
authored
Merge pull request #58 from frizzr/patch-1
Added support for legacy Helm chart repositories
2 parents e4398d1 + 70722ec commit 60ba913

File tree

2 files changed

+130
-29
lines changed

2 files changed

+130
-29
lines changed

helm/dagger.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "helm",
3+
"engineVersion": "v0.14.0",
34
"sdk": "go",
4-
"engineVersion": "v0.14.0"
5+
"source": "."
56
}

helm/main.go

Lines changed: 128 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@ type PushOpts struct {
2626
Password *dagger.Secret
2727
}
2828

29-
func (p PushOpts) getChartFqdn(name string) string {
29+
func (p PushOpts) getProtocol() string {
3030
if p.Oci {
31-
return fmt.Sprintf("oci://%s/%s/%s", p.Registry, p.Repository, name)
31+
return "oci"
32+
} else {
33+
return "https"
3234
}
33-
return fmt.Sprintf("%s/%s/%s", p.Registry, p.Repository, name)
3435
}
3536

3637
func (p PushOpts) getRepoFqdn() string {
37-
if p.Oci {
38-
return fmt.Sprintf("oci://%s/%s", p.Registry, p.Repository)
39-
}
40-
return fmt.Sprintf("%s/%s", p.Registry, p.Repository)
38+
return fmt.Sprintf("%s://%s/%s", p.getProtocol(), p.Registry, p.Repository)
39+
}
40+
41+
func (p PushOpts) getChartFqdn(name string) string {
42+
return fmt.Sprintf("%s/%s", p.getRepoFqdn(), name)
4143
}
4244

4345
// Get and display the version of the Helm Chart located inside the given directory.
@@ -58,7 +60,7 @@ func (h *Helm) Version(
5860
return strings.TrimSpace(version), nil
5961
}
6062

61-
// Packages and pushes a Helm chart to a specified OCI-compatible registry with authentication.
63+
// Packages and pushes a Helm chart to a specified OCI-compatible (by default) registry with authentication.
6264
//
6365
// Returns true if the chart was successfully pushed, or false if the chart already exists, with error handling for push failures.
6466
//
@@ -70,6 +72,19 @@ func (h *Helm) Version(
7072
// --username $REGISTRY_HELM_USER \
7173
// --password env:REGISTRY_HELM_PASSWORD \
7274
// --directory ./examples/testdata/mychart/
75+
//
76+
// Example usage for pushing to a legacy (non-OCI) Helm repository assuming the repo name is 'helm'. If your target URL
77+
// requires a vendor-specific path prefix (for example, JFrog Artifactory usually requires 'artifactory' before the repo
78+
// name) then add it before the repository name. If you want to put the chart in a subpath in
79+
// the repository, then append that to the end of the repository name.
80+
//
81+
// dagger call package-push \
82+
// --registry registry.puzzle.ch \
83+
// --repository vendor-specific-prefix/helm/optional/subpath/in/repository \
84+
// --username $REGISTRY_HELM_USER \
85+
// --password env:REGISTRY_HELM_PASSWORD \
86+
// --directory ./examples/testdata/mychart/ \
87+
// --use-non-oci-helm-repo=true
7388
func (h *Helm) PackagePush(
7489
// method call context
7590
ctx context.Context,
@@ -83,11 +98,15 @@ func (h *Helm) PackagePush(
8398
username string,
8499
// registry login password
85100
password *dagger.Secret,
101+
// use a non-OCI (legacy) Helm repository
102+
// +optional
103+
// +default=false
104+
useNonOciHelmRepo bool, // Dev note: We are forced to use default=false due to https://github.com/dagger/dagger/issues/8810
86105
) (bool, error) {
87106
opts := PushOpts{
88107
Registry: registry,
89108
Repository: repository,
90-
Oci: true,
109+
Oci: !useNonOciHelmRepo,
91110
Username: username,
92111
Password: password,
93112
}
@@ -110,38 +129,47 @@ func (h *Helm) PackagePush(
110129
}
111130

112131
name = strings.TrimSpace(name)
132+
pkgFile := fmt.Sprintf("%s-%s.tgz", name, version)
113133

114-
c, err = c.
115-
WithEnvVariable("REGISTRY_URL", opts.Registry).
116-
WithEnvVariable("REGISTRY_USERNAME", opts.Username).
117-
WithSecretVariable("REGISTRY_PASSWORD", opts.Password).
118-
WithExec([]string{"sh", "-c", `echo ${REGISTRY_PASSWORD} | helm registry login ${REGISTRY_URL} --username ${REGISTRY_USERNAME} --password-stdin`}).
119-
Sync(ctx)
134+
chartExists, err := h.doesChartExistOnRepo(ctx, c, &opts, name, version)
120135
if err != nil {
121136
return false, err
122137
}
123138

124-
//TODO: Refactor with return
125-
c, err = c.WithExec([]string{"sh", "-c", fmt.Sprintf("helm show chart %s --version %s; echo -n $? > /ec", opts.getChartFqdn(name), version)}).Sync(ctx)
126-
if err != nil {
127-
return false, err
139+
if chartExists {
140+
return false, nil
128141
}
129142

130-
exc, err := c.File("/ec").Contents(ctx)
143+
c, err = c.WithExec([]string{"helm", "dependency", "update", "."}).
144+
WithExec([]string{"helm", "package", "."}).
145+
WithExec([]string{"sh", "-c", "ls"}).
146+
Sync(ctx)
147+
131148
if err != nil {
132149
return false, err
133150
}
134151

135-
if exc == "0" {
136-
//Chart exists
137-
return false, nil
152+
if useNonOciHelmRepo {
153+
curlCmd := []string{
154+
`curl --variable %REGISTRY_USERNAME`,
155+
`--variable %REGISTRY_PASSWORD`,
156+
`--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`,
157+
`-T`,
158+
pkgFile,
159+
opts.getRepoFqdn() + "/",
160+
}
161+
162+
c, err = c.
163+
WithEnvVariable("REGISTRY_USERNAME", opts.Username).
164+
WithSecretVariable("REGISTRY_PASSWORD", opts.Password).
165+
WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}).
166+
Sync(ctx)
167+
} else {
168+
c, err = c.
169+
WithExec([]string{"helm", "push", pkgFile, opts.getRepoFqdn()}).
170+
Sync(ctx)
138171
}
139172

140-
_, err = c.WithExec([]string{"helm", "dependency", "update", "."}).
141-
WithExec([]string{"helm", "package", "."}).
142-
WithExec([]string{"sh", "-c", "ls"}).
143-
WithExec([]string{"helm", "push", fmt.Sprintf("%s-%s.tgz", name, version), opts.getRepoFqdn()}).
144-
Sync(ctx)
145173
if err != nil {
146174
return false, err
147175
}
@@ -203,6 +231,78 @@ func (h *Helm) Lint(
203231
return out, nil
204232
}
205233

234+
func (h *Helm) doesChartExistOnRepo(
235+
ctx context.Context,
236+
c *dagger.Container,
237+
opts *PushOpts,
238+
name string,
239+
version string,
240+
) (bool, error) {
241+
if opts.Oci {
242+
c, err := c.
243+
WithEnvVariable("REGISTRY_URL", opts.Registry).
244+
WithEnvVariable("REGISTRY_USERNAME", opts.Username).
245+
WithSecretVariable("REGISTRY_PASSWORD", opts.Password).
246+
WithExec([]string{"sh", "-c", `echo ${REGISTRY_PASSWORD} | helm registry login ${REGISTRY_URL} --username ${REGISTRY_USERNAME} --password-stdin`}).
247+
Sync(ctx)
248+
if err != nil {
249+
return false, err
250+
}
251+
252+
//TODO: Refactor with return
253+
c, err = c.WithExec([]string{"sh", "-c", fmt.Sprintf("helm show chart %s --version %s; echo -n $? > /ec", opts.getChartFqdn(name), version)}).Sync(ctx)
254+
if err != nil {
255+
return false, err
256+
}
257+
258+
exc, err := c.File("/ec").Contents(ctx)
259+
if err != nil {
260+
return false, err
261+
}
262+
263+
if exc == "0" {
264+
//Chart exists
265+
return true, nil
266+
}
267+
268+
return false, nil
269+
}
270+
// else non-OCI
271+
pkgFile := fmt.Sprintf("%s-%s.tgz", name, version)
272+
// Do a GET of the chart but with response headers only so we do not download the chart
273+
curlCmd := []string{
274+
`curl --variable %REGISTRY_USERNAME`,
275+
`--variable %REGISTRY_PASSWORD`,
276+
`--expand-user "{{REGISTRY_USERNAME}}:{{REGISTRY_PASSWORD}}"`,
277+
opts.getChartFqdn(pkgFile),
278+
`--output /dev/null`,
279+
`--silent -Iw '%{http_code}'`,
280+
}
281+
282+
httpCode, err := c.
283+
WithEnvVariable("REGISTRY_USERNAME", opts.Username).
284+
WithSecretVariable("REGISTRY_PASSWORD", opts.Password).
285+
WithExec([]string{"sh", "-c", strings.Join(curlCmd, " ")}).
286+
Stdout(ctx)
287+
288+
if err != nil {
289+
return false, err
290+
}
291+
292+
httpCode = strings.TrimSpace(httpCode)
293+
294+
if httpCode == "200" {
295+
// Chart exists
296+
return true, nil
297+
}
298+
299+
if httpCode == "404" {
300+
return false, nil
301+
}
302+
303+
return false, fmt.Errorf("Server returned error code %s checking for chart existence on server.", httpCode)
304+
}
305+
206306
func (h *Helm) hasMissingDependencies(
207307
// method call context
208308
ctx context.Context,

0 commit comments

Comments
 (0)