Skip to content

Commit

Permalink
Add support for OCI tarball url (#8184)
Browse files Browse the repository at this point in the history
### Description of the change

This PR aims to fix #8153 by resolving this error:
```
E1219 15:25:15.632218       1 utils.go:1024] Failed to import files, ID=bitnami/nginx, version=17.3.3: Get "oci://registry-1.docker.io/bitnamicharts/nginx:17.3.3": unsupported protocol scheme "oci"
```

### Benefits

Currently, the kubeapps-asset-syncer is unable to fetch the information
when the chart URL is stored in OCI.

In this PR, the chart tarballUrl is transformed when the oci schema is
detected and replaced by its OCI blob URL, so the asset syncer will be
able to fetch the chart tarball the same way as it does for non-OCI
charts.

### Possible drawbacks

None known.

### Applicable issues

<!-- Enter any applicable Issues here (You can reference an issue using
#) -->

- fixes #8153

---------

Signed-off-by: Miguel Ruiz <miguel.ruiz@broadcom.com>
Co-authored-by: Beltran Rueda <beltranrueda@vmware.com>
  • Loading branch information
migruiz4 and beltran-rubo authored Dec 24, 2024
1 parent 3fb5e43 commit c0fabfb
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 25 deletions.
32 changes: 27 additions & 5 deletions cmd/asset-syncer/server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,33 @@ func (r *HelmRepo) FetchFiles(cv models.ChartVersion, userAgent string, passCred
authorizationHeader = r.AuthorizationHeader
}

return tarutil.FetchChartDetailFromTarballUrl(
chartTarballURL,
userAgent,
authorizationHeader,
r.netClient)
// If URL points to an OCI chart, we transform its URL to its tgz blob URL
if strings.HasPrefix(chartTarballURL, "oci://") {
return FetchChartDetailFromOciUrl(chartTarballURL, userAgent, authorizationHeader, r.netClient)
} else {
return tarutil.FetchChartDetailFromTarballUrl(chartTarballURL, userAgent, authorizationHeader, r.netClient)
}
}

// Fetches helm chart details from an OCI url
func FetchChartDetailFromOciUrl(chartTarballURL string, userAgent string, authz string, netClient *http.Client) (map[string]string, error) {
headers := http.Header{}
if len(userAgent) > 0 {
headers.Add("User-Agent", userAgent)
}
if len(authz) > 0 {
headers.Add("Authorization", authz)
}

puller := &helm.OCIPuller{Resolver: docker.NewResolver(docker.ResolverOptions{Headers: headers, Client: netClient})}

ref := strings.TrimPrefix(strings.TrimSpace(chartTarballURL), "oci://")
chartBuffer, _, err := puller.PullOCIChart(ref)
if err != nil {
return nil, err
}

return tarutil.FetchChartDetailFromTarball(chartBuffer)
}

// TagList represents a list of tags as specified at
Expand Down
2 changes: 1 addition & 1 deletion cmd/kubeapps-apis/plugins/helm/packages/v1alpha1/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ func (s *Server) fetchChartWithRegistrySecrets(ctx context.Context, headers http
},
appRepo,
caCertSecret, authSecret,
s.chartClientFactory.New(appRepo.Spec.Type, userAgentString),
s.chartClientFactory.New(tarballURL, userAgentString),
)
if err != nil {
return nil, nil, connect.NewError(connect.CodeInternal, fmt.Errorf("Unable to fetch the chart %s from the namespace %q: %w", chartDetails.ChartName, appRepo.Namespace, err))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,24 +101,24 @@ func (c *HelmRepoClient) GetChart(details *ChartDetails, repoURL string) (*chart
}

log.Infof("Downloading %s ...", chartURL)
chart, err := fetchChart(c.netClient, chartURL)
chart, err := fetchChart(c.netClient, chartURL.String())
if err != nil {
return nil, err
}

return chart, nil
}

func resolveChartURL(indexURL, chartURL string) (string, error) {
func resolveChartURL(indexURL, chartURL string) (*url.URL, error) {
parsedIndexURL, err := url.Parse(strings.TrimSpace(indexURL))
if err != nil {
return "", err
return nil, err
}
parsedChartURL, err := parsedIndexURL.Parse(strings.TrimSpace(chartURL))
if err != nil {
return "", err
return nil, err
}
return parsedChartURL.String(), nil
return parsedChartURL, nil
}

// fetchChart returns the Chart content given an URL
Expand Down Expand Up @@ -184,16 +184,15 @@ func (c *OCIRepoClient) GetChart(details *ChartDetails, repoURL string) (*chart.
if c.puller == nil {
return nil, fmt.Errorf("unable to retrieve chart, Init should be called first")
}
parsedURL, err := url.ParseRequestURI(strings.TrimSpace(repoURL))
if err != nil {
return nil, err
if details == nil || details.TarballURL == "" {
return nil, fmt.Errorf("unable to retrieve chart, missing chart details")
}
unescapedChartName, err := url.QueryUnescape(details.ChartName)
chartURL, err := resolveChartURL(repoURL, details.TarballURL)
if err != nil {
return nil, err
}

ref := path.Join(parsedURL.Host, parsedURL.Path, fmt.Sprintf("%s:%s", unescapedChartName, details.Version))
ref := path.Join(chartURL.Host, chartURL.Path)
chartBuffer, _, err := c.puller.PullOCIChart(ref)
if err != nil {
return nil, err
Expand All @@ -215,12 +214,11 @@ type ChartClientFactoryInterface interface {
type ChartClientFactory struct{}

// New for ClientResolver
func (c *ChartClientFactory) New(repoType, userAgent string) ChartClient {
func (c *ChartClientFactory) New(tarballUrl string, userAgent string) ChartClient {
var client ChartClient
switch repoType {
case "oci":
if strings.HasPrefix(tarballUrl, "oci://") {
client = NewOCIClient(userAgent)
default:
} else {
client = NewChartClient(userAgent)
}
return client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func Test_resolveChartURL(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
chartURL, err := resolveChartURL(tt.baseURL, tt.chartURL)
assert.NoError(t, err)
assert.Equal(t, chartURL, tt.wantedURL, "url")
assert.Equal(t, chartURL.String(), tt.wantedURL, "url")
})
}
}
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestOCIClient(t *testing.T) {
cli := NewOCIClient("foo")
cli.(*OCIRepoClient).puller = &helmfake.OCIPuller{}
_, err := cli.GetChart(nil, "foo")
if !strings.Contains(err.Error(), "invalid URI for request") {
if !strings.Contains(err.Error(), "missing chart details") {
t.Errorf("Unexpected error %v", err)
}
})
Expand All @@ -231,7 +231,7 @@ func TestOCIClient(t *testing.T) {
ExpectedName: "foo/bar/nginx:5.1.1",
Content: map[string]*bytes.Buffer{"5.1.1": bytes.NewBuffer(data)},
}
ch, err := cli.GetChart(&ChartDetails{ChartName: "nginx", Version: "5.1.1"}, "http://foo/bar")
ch, err := cli.GetChart(&ChartDetails{ChartName: "nginx", Version: "5.1.1", TarballURL: "oci://foo/bar/nginx:5.1.1"}, "http://foo/bar")
if ch == nil {
t.Errorf("Unexpected error: %s", err)
}
Expand All @@ -248,7 +248,7 @@ func TestOCIClient(t *testing.T) {
ExpectedName: "foo/bar/bar/nginx:5.1.1",
Content: map[string]*bytes.Buffer{"5.1.1": bytes.NewBuffer(data)},
}
ch, err := cli.GetChart(&ChartDetails{ChartName: "nginx", Version: "5.1.1"}, "http://foo/bar%2Fbar")
ch, err := cli.GetChart(&ChartDetails{ChartName: "nginx", Version: "5.1.1", TarballURL: "oci://foo/bar%2Fbar/nginx:5.1.1"}, "http://foo/bar%2Fbar")
if ch == nil {
t.Errorf("Unexpected error: %s", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ func (f *ChartClient) Init(appRepo *appRepov1.AppRepository, caCertSecret *corev
type ChartClientFactory struct{}

// New returns a fake ChartClient
func (c *ChartClientFactory) New(repoType, userAgent string) utils.ChartClient {
func (c *ChartClientFactory) New(tarballURL string, userAgent string) utils.ChartClient {
return &ChartClient{}
}

0 comments on commit c0fabfb

Please sign in to comment.