Skip to content

Commit

Permalink
Fix deployment of OCI charts urls inside regular Helm index
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Ruiz <[email protected]>
  • Loading branch information
migruiz4 committed Dec 23, 2024
1 parent 3f6cb71 commit 5caa0a5
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 35 deletions.
65 changes: 42 additions & 23 deletions cmd/asset-syncer/server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,21 +311,37 @@ func (r *HelmRepo) FetchFiles(cv models.ChartVersion, userAgent string, passCred

// If URL points to an OCI chart, we transform its URL to its tgz blob URL
if chartTarballURL.Scheme == "oci" {
// Override the chart tarball with the oci blob tarball
chartTarballURL, err = getOciTarballUrl(chartTarballURL, userAgent, authorizationHeader, r.netClient)
if err != nil {
return nil, err
}
return FetchChartDetailFromOciUrl(chartTarballURL, userAgent, authorizationHeader, r.netClient)
} else {
return FetchChartDetailFromTarballUrl(chartTarballURL, userAgent, authorizationHeader, r.netClient)
}
}

// Fetches helm chart details from a gzipped tarball
//
// name is expected in format "foo/bar" or "foo%2Fbar" if url-escaped
func FetchChartDetailFromTarballUrl(chartTarballURL *url.URL, userAgent string, authz string, netClient *http.Client) (map[string]string, error) {
reqHeaders := make(map[string]string)
if len(userAgent) > 0 {
reqHeaders["User-Agent"] = userAgent
}
if len(authz) > 0 {
reqHeaders["Authorization"] = authz
}

// use our "standard" http-client library
reader, _, err := httpclient.GetStream(chartTarballURL.String(), netClient, reqHeaders)
if reader != nil {
defer reader.Close()
}

return tarutil.FetchChartDetailFromTarballUrl(
chartTarballURL.String(),
userAgent,
authorizationHeader,
r.netClient)
if err != nil {
return nil, err
}
return tarutil.FetchChartDetailFromTarball(reader)
}

func getOciTarballUrl(chartTarballURL *url.URL, userAgent string, authorizationHeader string, netClient *http.Client) (*url.URL, error) {
func FetchChartDetailFromOciUrl(chartTarballURL *url.URL, userAgent string, authorizationHeader string, netClient *http.Client) (map[string]string, error) {
// If URL points to an OCI chart, we transform its URL to its tgz blob URL
// Extract the tag from the chart Path
chartTag := "latest"
Expand All @@ -334,26 +350,27 @@ func getOciTarballUrl(chartTarballURL *url.URL, userAgent string, authorizationH
chartTag = chartTarballURL.Path[i+1:]
chartTarballURL.Path = chartTarballURL.Path[:i]
}
// Separate the appname from the oci Url
j := strings.LastIndex(chartTarballURL.Path, "/")
RegistryNamespaceUrl, err := url.Parse(chartTarballURL.Path[j+1:])
appName := chartTarballURL.Path[:j]
appName := chartTarballURL.Path[j+1:]
chartTarballURL.Path = chartTarballURL.Path[:j]
// TODO: I would like to refactor the OciAPIClient to be generic and allow generating an OCI client without all the oci-catalog specific code
// IMPORTANT: Currently, getOrasRepoClient(appname, userAgent) is too specific, I would need to be able to generate an OCI client for other general purposes by simply providing an url.
o := OciAPIClient{RegistryNamespaceUrl: RegistryNamespaceUrl, HttpClient: netClient, GrpcClient: nil}
o := OciAPIClient{RegistryNamespaceUrl: chartTarballURL, HttpClient: netClient, GrpcClient: nil}
orasRepoClient, err := o.getOrasRepoClient(appName, "")
if err != nil {
panic(err)
}
ctx := context.TODO()
// TODO: This code is too similar to the function 'IsHelmChart'.
// Again, I would like to move both functions into a common 'fetchOciManifest' function in order to simplify the code structure
descriptor, rc, err := orasRepoClient.Manifests().FetchReference(ctx, chartTag)
manifestDescriptor, rc, err := orasRepoClient.Manifests().FetchReference(ctx, chartTag)
if err != nil {
panic(err)
}
defer rc.Close()

manifestData, err := content.ReadAll(rc, descriptor)
manifestData, err := content.ReadAll(rc, manifestDescriptor)
if err != nil {
panic(err)
}
Expand All @@ -366,17 +383,19 @@ func getOciTarballUrl(chartTarballURL *url.URL, userAgent string, authorizationH
return nil, fmt.Errorf("unexpected layer in chart manifest")
}

ociTarballUrl, err := buildChartOciTarballUrl("https", chartTarballURL.Host, chartTarballURL.Path, manifest.Layers[0].Digest)
blobDescriptor, err := orasRepoClient.Blobs().Resolve(ctx, manifest.Layers[0].Digest)
if err != nil {
return nil, err
}
reader, err := orasRepoClient.Blobs().Fetch(ctx, blobDescriptor)
if reader != nil {
defer reader.Close()
}
if err != nil {
return nil, err
}

return ociTarballUrl, nil
}

func buildChartOciTarballUrl(schema string, registry string, repository string, blobDigest string) (*url.URL, error) {
ociTarballUrl := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", schema, registry, repository, blobDigest)
return url.Parse(ociTarballUrl)
return tarutil.FetchChartDetailFromTarball(reader)
}

// 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 @@ -184,16 +184,12 @@ 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
}
unescapedChartName, err := url.QueryUnescape(details.ChartName)
parsedURL, err := url.Parse(strings.TrimSpace(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(parsedURL.Host, parsedURL.Path)
chartBuffer, _, err := c.puller.PullOCIChart(ref)
if err != nil {
return nil, err
Expand All @@ -215,12 +211,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 @@ -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 5caa0a5

Please sign in to comment.