Skip to content

Commit

Permalink
chore: differentiate by media type
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobmoellerdev committed Jan 2, 2025
1 parent 5872c1a commit c84fbf4
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ media=
artifact=
hint=
creds=
digest=
args=( )

parseArgs() {
Expand All @@ -21,6 +22,9 @@ parseArgs() {
--mediaType|-m)
media="$2"
shift 2;;
--digest|-d)
digest="$2"
shift 2;;
--artifactType|-a)
artifact="$2"
shift 2;;
Expand Down
72 changes: 72 additions & 0 deletions cmds/jfrogplugin/uploaders/helm/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package helm

import (
"errors"
"fmt"
"io"

"ocm.software/ocm/api/oci"
"ocm.software/ocm/api/oci/extensions/repositories/artifactset"
"ocm.software/ocm/api/utils/accessio"
"ocm.software/ocm/api/utils/accessobj"
)

func ConvertArtifactSetHelmChartToPlainTGZChart(reader io.Reader) (_ io.ReadCloser, _ string, err error) {
set, err := artifactset.Open(accessobj.ACC_READONLY, "", 0, accessio.Reader(io.NopCloser(reader)))
if err != nil {
return nil, "", fmt.Errorf("failed to open helm OCI artifact as artifact set: %w", err)
}
defer func() {
err = errors.Join(err, set.Close())
}()

art, err := set.GetArtifact(set.GetMain().String())
if err != nil {
return nil, "", fmt.Errorf("failed to get artifact from set: %w", err)
}
defer func() {
err = errors.Join(err, art.Close())
}()

chartTgz, provenance, err := accessSingleLayerOCIHelmChart(art)
if err != nil {
return nil, "", fmt.Errorf("failed to access OCI artifact as a single layer helm OCI image: %w", err)
}
defer func() {
err = errors.Join(err, chartTgz.Close())
if provenance != nil {
err = errors.Join(err, provenance.Close())
}
}()

chartReader, err := chartTgz.Reader()
if err != nil {
return nil, "", fmt.Errorf("failed to get reader for chart tgz: %w", err)
}

digest := chartTgz.Digest().String()

return chartReader, digest, nil
}

func accessSingleLayerOCIHelmChart(art oci.ArtifactAccess) (chart oci.BlobAccess, prov oci.BlobAccess, err error) {
m := art.ManifestAccess()
if m == nil {
return nil, nil, errors.New("artifact is no image manifest")
}
if len(m.GetDescriptor().Layers) < 1 {
return nil, nil, errors.New("no layers found")
}

if chart, err = m.GetBlob(m.GetDescriptor().Layers[0].Digest); err != nil {
return nil, nil, err
}

if len(m.GetDescriptor().Layers) > 1 {
if prov, err = m.GetBlob(m.GetDescriptor().Layers[1].Digest); err != nil {
return nil, nil, err
}
}

return chart, prov, nil
}
33 changes: 32 additions & 1 deletion cmds/jfrogplugin/uploaders/helm/helm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package helm

import (
"bytes"
"context"
"fmt"
"io"
Expand All @@ -15,8 +16,12 @@ import (
"ocm.software/ocm/api/credentials"
"ocm.software/ocm/api/credentials/cpi"
"ocm.software/ocm/api/credentials/identity/hostpath"
"ocm.software/ocm/api/oci/artdesc"
"ocm.software/ocm/api/oci/extensions/repositories/artifactset"
"ocm.software/ocm/api/ocm/extensions/artifacttypes"
"ocm.software/ocm/api/ocm/plugin/ppi"
"ocm.software/ocm/api/tech/helm"
"ocm.software/ocm/api/tech/helm/loader"
"ocm.software/ocm/api/utils/runtime"
)

Expand Down Expand Up @@ -122,7 +127,7 @@ func (a *Uploader) ValidateSpecification(_ ppi.Plugin, spec ppi.UploadTargetSpec
// 3. creating a request respecting the passed credentials based on SetHeadersFromCredentials
// 4. uploading the passed blob as is (expected to be a tgz byte stream)
// 5. intepreting the JFrog API response, and converting it from ArtifactoryUploadResponse to ppi.AccessSpec
func (a *Uploader) Upload(_ ppi.Plugin, arttype, _, hint, digest string, targetSpec ppi.UploadTargetSpec, creds credentials.Credentials, reader io.Reader) (ppi.AccessSpecProvider, error) {
func (a *Uploader) Upload(_ ppi.Plugin, arttype, mediaType, hint, digest string, targetSpec ppi.UploadTargetSpec, creds credentials.Credentials, reader io.Reader) (ppi.AccessSpecProvider, error) {
if arttype != artifacttypes.HELM_CHART {
return nil, fmt.Errorf("unsupported artifact type %s", arttype)
}
Expand All @@ -132,6 +137,28 @@ func (a *Uploader) Upload(_ ppi.Plugin, arttype, _, hint, digest string, targetS
return nil, fmt.Errorf("the type %T is not a valid target spec type", spec)
}

switch mediaType {
case helm.ChartMediaType:
// if it is a native chart tgz we can pass it on as is
var buf bytes.Buffer
chart, err := loader.LoadArchive(io.TeeReader(reader, &buf))
if err != nil {
return nil, fmt.Errorf("failed to load chart: %w", err)
}
spec.Name = chart.Metadata.Name
spec.Version = chart.Metadata.Version
reader = &buf
case artifactset.MediaType(artdesc.MediaTypeImageManifest):
// if we have an artifact set (ocm custom version of index + locally colocated blobs as files, we need
// to translate it.
var err error
if reader, digest, err = ConvertArtifactSetHelmChartToPlainTGZChart(reader); err != nil {
return nil, fmt.Errorf("failed to convert OCI Helm Chart to plain TGZ: %w", err)
}
default:
return nil, fmt.Errorf("unsupported media type %s", mediaType)
}

if err := EnsureSpecWithHelpFromHint(spec, hint); err != nil {
return nil, fmt.Errorf("could not ensure spec to be ready for upload: %w", err)
}
Expand All @@ -149,6 +176,10 @@ func (a *Uploader) Upload(_ ppi.Plugin, arttype, _, hint, digest string, targetS
return nil, fmt.Errorf("failed to upload: %w", err)
}

if err := ReindexChart(ctx, a.Client, spec.URL, spec.Repository, creds); err != nil {
return nil, fmt.Errorf("failed to reindex chart: %w", err)
}

return func() ppi.AccessSpec {
return access
}, nil
Expand Down
57 changes: 57 additions & 0 deletions cmds/jfrogplugin/uploaders/helm/reindex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package helm

import (
"fmt"
"io"
"net/http"
"path"

"golang.org/x/net/context"

"ocm.software/ocm/api/credentials"
)

func ReindexChart(ctx context.Context, client *http.Client, artifactoryURL string,
repository string,
creds credentials.Credentials) (err error) {
reindexURL, err := convertToReindexURL(artifactoryURL, repository)
if err != nil {
return fmt.Errorf("failed to convert to reindex URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reindexURL, nil)
if err != nil {
return fmt.Errorf("failed to create reindex request: %w", err)
}
SetHeadersFromCredentials(req, creds)
req.Header = req.Header.Clone()

res, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to reindex chart: %w", err)
}

if res.StatusCode != http.StatusOK {
responseBytes, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("failed to read response body but server returned %v: %w", res.StatusCode, err)
}
var body string
if len(responseBytes) > 0 {
body = fmt.Sprintf(": %s", string(responseBytes))
}
return fmt.Errorf("invalid response (status %v) while reindexing at %q: %s", res.StatusCode, reindexURL, body)
}

return nil
}

// convertToReindexURL converts the base URL and repository to a reindex URL.
// see https://jfrog.com/help/r/jfrog-rest-apis/calculate-helm-chart-index for the reindex API
func convertToReindexURL(baseURL string, repository string) (string, error) {
u, err := parseURLAllowNoScheme(baseURL)
if err != nil {
return "", fmt.Errorf("failed to parse url: %w", err)
}
u.Path = path.Join(u.Path, "api", "helm", repository, "reindex")
return u.String(), nil
}
2 changes: 1 addition & 1 deletion cmds/jfrogplugin/uploaders/helm/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestUpload(t *testing.T) {
accessSpec, err := Upload(ctx, data, client, url, credentials.DirectCredentials{
credentials.ATTR_USERNAME: "foo",
credentials.ATTR_PASSWORD: "bar",
})
}, "")

if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down

0 comments on commit c84fbf4

Please sign in to comment.