Skip to content

Commit

Permalink
Switch Radius Helm chart pull from ACR to GHCR (radius-project#7455)
Browse files Browse the repository at this point in the history
# Description

* Switch Radius Helm chart pull from ACR to GHCR
* Add error handling for 403 from GHCR

Example outputs:

Expired credentials:
```sh
❯ go run ./cmd/rad/main.go install kubernetes --reinstall
# command-line-arguments
ld: warning: -bind_at_load is deprecated on macOS
Reinstalling Radius version edge to namespace: radius-system...
Error: failed to load Helm chart, err: recieved 403 unauthorized when downloading helm chart from the registry. you may want to perform a `docker logout ghcr.io` and re-try the command, Helm output: 

TraceId:  dff0bdbe3da3f80fc6e8a84a9b5d38ea

exit status 1
```

## Type of change

<!--

Please select **one** of the following options that describes your
change and delete the others. Clearly identifying the type of change you
are making will help us review your PR faster, and is used in authoring
release notes.

If you are making a bug fix or functionality change to Radius and do not
have an associated issue link please create one now.

-->

- This pull request adds or changes features of Radius and has an
approved issue (issue link required).

<!--

Please update the following to link the associated issue. This is
required for some kinds of changes (see above).

-->

Fixes: radius-project#6801

---------

Signed-off-by: willdavsmith <[email protected]>
  • Loading branch information
willdavsmith authored Jun 17, 2024
1 parent b2c8878 commit d26a411
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 4 deletions.
55 changes: 52 additions & 3 deletions pkg/cli/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ import (
_ "embed"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"

containerderrors "github.com/containerd/containerd/remotes/errors"
"github.com/radius-project/radius/pkg/cli/clierrors"
helm "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/registry"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

Expand Down Expand Up @@ -79,7 +83,6 @@ func locateChartFile(dirPath string) (string, error) {

func helmChartFromContainerRegistry(version string, config *helm.Configuration, repoUrl string, releaseName string) (*chart.Chart, error) {
pull := helm.NewPull()
pull.RepoURL = repoUrl
pull.Settings = &cli.EnvSettings{}
pullopt := helm.WithConfig(config)
pullopt(pull)
Expand All @@ -101,9 +104,42 @@ func helmChartFromContainerRegistry(version string, config *helm.Configuration,

pull.DestDir = dir

_, err = pull.Run(releaseName)
var chartRef string

if !registry.IsOCI(repoUrl) {
// For non-OCI registries (like contour), we need to set the repo URL
// to the registry URL. The chartRef is the release name.
// ex.
// pull.RepoURL = https://charts.bitnami.com/bitnami
// pull.Run("contour")
pull.RepoURL = repoUrl
chartRef = releaseName
} else {
// For OCI registries (like radius), we will use the
// repo URL + the releaseName as the chartRef.
// pull.Run("oci://ghcr.io/radius-project/helm-chart/radius")
chartRef = fmt.Sprintf("%s/%s", repoUrl, releaseName)

// Since we are using an OCI registry, we need to set the registry client
registryClient, err := registry.NewClient()
if err != nil {
return nil, err
}

pull.SetRegistryClient(registryClient)
}

_, err = pull.Run(chartRef)
if err != nil {
return nil, fmt.Errorf("error downloading helm chart from the registry for version: %s, release name: %s. Error: %w", version, releaseName, err)
// Error handling for a specific case where credentials are stale.
// This happens for ghcr in particular because ghcr does not use
// subdomains - the scope of a login is all of ghcr.io.
// https://github.com/helm/helm/issues/12584
if isHelm403Error(err) {
return nil, clierrors.Message("recieved 403 unauthorized when downloading helm chart from the registry. you may want to perform a `docker logout ghcr.io` and re-try the command")
}

return nil, clierrors.MessageWithCause(err, fmt.Sprintf("error downloading helm chart from the registry for version: %s, release name: %s", version, releaseName))
}

chartPath, err := locateChartFile(dir)
Expand Down Expand Up @@ -136,3 +172,16 @@ func runUpgrade(upgradeClient *helm.Upgrade, releaseName string, helmChart *char
}
return err
}

// isHelm403Error is a helper function to determine if an error is a specific helm error
// (403 unauthorized when downloading a helm chart from ghcr.io) from a chain of errors.
func isHelm403Error(err error) bool {
var errUnexpectedStatus containerderrors.ErrUnexpectedStatus
if errors.As(err, &errUnexpectedStatus) {
if errUnexpectedStatus.StatusCode == http.StatusForbidden && strings.Contains(errUnexpectedStatus.RequestURL, "ghcr.io") {
return true
}
}

return false
}
40 changes: 40 additions & 0 deletions pkg/cli/helm/helm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package helm

import (
"errors"
"fmt"
"net/http"
"testing"

containerderrors "github.com/containerd/containerd/remotes/errors"
"github.com/stretchr/testify/assert"
)

func Test_isHelm403Error(t *testing.T) {
var err error
var result bool

err = errors.New("error")
result = isHelm403Error(err)
assert.False(t, result)

err = fmt.Errorf("%w: wrapped error", errors.New("error"))
result = isHelm403Error(err)
assert.False(t, result)

err = fmt.Errorf("%w: wrapped error", containerderrors.ErrUnexpectedStatus{})
result = isHelm403Error(err)
assert.False(t, result)

err = fmt.Errorf("%w: wrapped error", containerderrors.ErrUnexpectedStatus{StatusCode: http.StatusForbidden, RequestURL: "ghcr.io/myregistry"})
result = isHelm403Error(err)
assert.True(t, result)

err = containerderrors.ErrUnexpectedStatus{StatusCode: http.StatusForbidden, RequestURL: "ghcr.io/myregistry"}
result = isHelm403Error(err)
assert.True(t, result)

err = containerderrors.ErrUnexpectedStatus{StatusCode: http.StatusUnauthorized, RequestURL: "ghcr.io/myregistry"}
result = isHelm403Error(err)
assert.False(t, result)
}
2 changes: 1 addition & 1 deletion pkg/cli/helm/radiusclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (

const (
radiusReleaseName = "radius"
radiusHelmRepo = "https://radius.azurecr.io/helm/v1/repo"
radiusHelmRepo = "oci://ghcr.io/radius-project/helm-chart"
RadiusSystemNamespace = "radius-system"
)

Expand Down

0 comments on commit d26a411

Please sign in to comment.