Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ testbin/*
# gorleaser artifacts
dist/**

# Apple files
# Apple files
.DS_Store

# act artifacts needed for testing workflows
.secrets
.env
act_*.json

# handmade files for testing hooks locally
*.tmp.json
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23 as build
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23 AS build

WORKDIR /concourse-oci-helm-chart-resource
COPY . .
Expand All @@ -8,6 +8,7 @@ FROM --platform=${BUILDPLATFORM:-linux/amd64} alpine:3.21.2 AS run

# upgrade all installed packages to fix potential CVEs in advance
RUN apk upgrade --no-cache --no-progress \
&& apk add --no-cache --no-progress --upgrade ca-certificates \
&& apk del --no-cache --no-progress apk-tools alpine-keys

# Required by concourse resource. Copy explicitly.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ resources:
- name: my.chart
type: oci-registry
source:
registry: ghcr.io/cloudoperators
repository: all-my-helm-charts
registry: ghcr.io
repository: cloudoperators/all-my-helm-charts
chart_name: my-chart
```

Expand Down
4 changes: 2 additions & 2 deletions cmd/in/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func main() {
response, err := resource.Get(context.Background(), req, outputDir)
if err != nil {
fmt.Fprintf(os.Stderr, "get failed: %s\n", err)
}
if err := json.NewEncoder(os.Stdout).Encode(response); err != nil {
os.Exit(1)
} else if err := json.NewEncoder(os.Stdout).Encode(response); err != nil {
fmt.Fprintf(os.Stderr, "failed to marshal response: %s\n", err)
}
}
70 changes: 58 additions & 12 deletions pkg/resource/check.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at this file in detail. Everything else LGTM

Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ package resource
import (
"context"
"fmt"
"os"
"slices"

"github.com/Masterminds/semver/v3"
"github.com/pkg/errors"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
)

type (
Expand All @@ -28,33 +31,76 @@ func (cr *CheckRequest) Validate() error {
func Check(ctx context.Context, request CheckRequest) (*CheckResponse, error) {
repo, err := newRepositoryForSource(ctx, request.Source)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to create repository client")
}

// Fetching repository tags
// Fetch repository tags
allTags, err := registry.Tags(ctx, repo)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch tags")
}

// Sorting tags.
var latestTag *semver.Version
for _, tag := range allTags {
v, err := semver.NewVersion(tag)
// Sort tags by semver
sortedSemvers := sortBySemver(allTags)

// chop the list at the index of the requested version, if there was one
if request.Version != nil {
requestedVersion, err := semver.NewVersion(request.Version.Tag)
if err != nil {
return nil, err
return nil, errors.Wrap(err, fmt.Sprintf("failed to parse semver in requested version %q", request.Version.Tag))
}
for i, version := range sortedSemvers {
if version.GreaterThanEqual(requestedVersion) {
sortedSemvers = sortedSemvers[i:]
break
}
}
if latestTag == nil || v.GreaterThan(latestTag) {
latestTag = v
} else {
// if no version was requested, return the latest 10 versions
startIndex := len(sortedSemvers) - 10
if startIndex < 0 {
startIndex = 0
}
sortedSemvers = sortedSemvers[startIndex:]
}
if latestTag == nil {

if len(sortedSemvers) == 0 {
return nil, fmt.Errorf("no latest tag found for source %s", request.Source.String())
}

digest, err := getDigestForTag(ctx, repo, latestTag.String())
resolvedVersions, err := resolveImageDigests(ctx, sortedSemvers, repo)
if err != nil {
return nil, err
}
return &CheckResponse{{Tag: latestTag.String(), Digest: digest}}, nil
return &resolvedVersions, nil
}

func sortBySemver(allTags []string) []semver.Version {
allVersions := make([]semver.Version, len(allTags))
var j = 0
for _, tag := range allTags {
v, err := semver.NewVersion(tag)
if err != nil {
fmt.Fprintf(os.Stderr, "skipping tag %q because it does not look like a semver\n", tag)
} else {
allVersions[j] = *v
j++
}
}
slices.SortStableFunc(allVersions, func(i, j semver.Version) int {
return i.Compare(&j)
})
return allVersions
}

func resolveImageDigests(ctx context.Context, sortedSemvers []semver.Version, repo *remote.Repository) (CheckResponse, error) {
resolvedVersions := make(CheckResponse, len(sortedSemvers))
for i, version := range sortedSemvers {
digest, err := getDigestForTag(ctx, repo, version.Original())
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch digest for latest tag %q (parsed as %s)", version.Original(), version.String()))
}
resolvedVersions[i] = Version{Tag: version.Original(), Digest: digest}
}
return resolvedVersions, nil
}
Loading