Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for OCI registry based charts #975

Merged
merged 5 commits into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
.idea
*.DS_Store
/fleet
/.vscode
3 changes: 2 additions & 1 deletion docs/gitrepo-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ kustomize:
dir: ./kustomize

helm:
# Use a custom location for the Helm chart. This can refer to any go-getter URL.
# Use a custom location for the Helm chart. This can refer to any go-getter URL or
# OCI registry based helm chart URL e.g. "oci://ghcr.io/fleetrepoci/guestbook".
# This allows one to download charts from most any location. Also know that
# go-getter URL supports adding a digest to validate the download. If repo
# is set below this field is the name of the chart to lookup
Expand Down
16 changes: 16 additions & 0 deletions e2e/assets/single-cluster/helm-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
kind: GitRepo
apiVersion: fleet.cattle.io/v1alpha1
metadata:
name: helm
spec:
repo: https://github.com/rancher/fleet-examples
branch: add-helm-oci-example
paths:
- single-cluster/helm-oci
targets:
- clusterSelector:
matchExpressions:
- key: provider.cattle.io
operator: NotIn
values:
- harvester
13 changes: 13 additions & 0 deletions e2e/single-cluster/single_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ var _ = Describe("Single Cluster Examples", func() {

})

Context("containing an oci based helm chart", func() {
BeforeEach(func() {
asset = "single-cluster/helm-oci.yaml"
})

It("deploys the helm chart", func() {
Eventually(func() string {
out, _ := k.Namespace("fleet-helm-oci-example").Get("pods")
return out
}, testenv.Timeout).Should(ContainSubstring("frontend-"))
})
})

When("creating a gitrepo resource", func() {
Context("containing a helm chart", func() {
BeforeEach(func() {
Expand Down
6 changes: 3 additions & 3 deletions pkg/bundle/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bund
}

if file == "-" {
return Read(ctx, name, baseDir, os.Stdin, opts)
return mayCompress(ctx, name, baseDir, os.Stdin, opts)
}

var (
Expand All @@ -65,7 +65,7 @@ func Open(ctx context.Context, name, baseDir, file string, opts *Options) (*Bund
in = f
}

return Read(ctx, name, baseDir, in, opts)
return mayCompress(ctx, name, baseDir, in, opts)
}

// Try accessing the documented, primary fleet.yaml extension first. If that returns an "IsNotExist" error, then we
Expand All @@ -89,7 +89,7 @@ func setupIOReader(baseDir string) (*os.File, error) {
return nil, nil
}

func Read(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*Bundle, error) {
func mayCompress(ctx context.Context, name, baseDir string, bundleSpecReader io.Reader, opts *Options) (*Bundle, error) {
if opts == nil {
opts = &Options{}
}
Expand Down
68 changes: 52 additions & 16 deletions pkg/bundle/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
Expand All @@ -21,6 +22,9 @@ import (
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader"
helmgetter "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/repo"

"github.com/rancher/fleet/modules/cli/pkg/progress"
Expand Down Expand Up @@ -83,7 +87,7 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b
return result, nil
}

func ChartPath(helm *fleet.HelmOptions) string {
func checksum(helm *fleet.HelmOptions) string {
if helm == nil {
return "none"
}
Expand Down Expand Up @@ -186,11 +190,12 @@ func addCharts(directories []directory, base string, charts []*fleet.HelmOptions
}

directories = append(directories, directory{
prefix: ChartPath(chart),
base: base,
path: chartURL,
key: ChartPath(chart),
auth: auth,
prefix: checksum(chart),
base: base,
path: chartURL,
key: checksum(chart),
auth: auth,
version: chart.Version,
})
}
}
Expand All @@ -216,11 +221,12 @@ func addDirectory(directories []directory, base, customDir, defaultDir string) (
}

type directory struct {
prefix string
base string
path string
key string
auth Auth
prefix string
base string
path string
key string
version string
auth Auth
}

func readDirectories(ctx context.Context, compress bool, directories ...directory) (map[string][]fleet.BundleResource, error) {
Expand All @@ -241,7 +247,7 @@ func readDirectories(ctx context.Context, compress bool, directories ...director
dir := dir
eg.Go(func() error {
defer sem.Release(1)
resources, err := readDirectory(ctx, compress, dir.prefix, dir.base, dir.path, dir.auth)
resources, err := readDirectory(ctx, compress, dir.prefix, dir.base, dir.path, dir.version, dir.auth)
if err != nil {
return err
}
Expand All @@ -261,10 +267,10 @@ func readDirectories(ctx context.Context, compress bool, directories ...director
return result, eg.Wait()
}

func readDirectory(ctx context.Context, compress bool, prefix, base, name string, auth Auth) ([]fleet.BundleResource, error) {
func readDirectory(ctx context.Context, compress bool, prefix, base, name, version string, auth Auth) ([]fleet.BundleResource, error) {
var resources []fleet.BundleResource

files, err := readContent(ctx, base, name, auth)
files, err := readContent(ctx, base, name, version, auth)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -295,21 +301,36 @@ func readDirectory(ctx context.Context, compress bool, prefix, base, name string
return resources, nil
}

func readContent(ctx context.Context, base, name string, auth Auth) (map[string][]byte, error) {
func readContent(ctx context.Context, base, name, version string, auth Auth) (map[string][]byte, error) {
temp, err := os.MkdirTemp("", "fleet")
if err != nil {
return nil, err
}
defer os.RemoveAll(temp)

src := name

// go-getter does not support downloading OCI registry based files yet
// until this is implemented we use Helm to download charts from OCI based registries
// and provide the downloaded file to go-getter locally
hasOCIURL, err := regexp.MatchString(`^oci:\/\/`, name)
if err != nil {
return nil, err
}
if hasOCIURL {
src, err = downloadOCIChart(name, version, temp)
if err != nil {
return nil, err
}
}

temp = filepath.Join(temp, "content")

base, err = filepath.Abs(base)
if err != nil {
return nil, err
}

src := name
if auth.SSHPrivateKey != nil {
if !strings.ContainsAny(src, "?") {
src += "?"
Expand Down Expand Up @@ -387,6 +408,21 @@ func readContent(ctx context.Context, base, name string, auth Auth) (map[string]
return files, nil
}

// downloadOciChart uses Helm to download charts from OCI based registries
func downloadOCIChart(name, version, path string) (string, error) {
c := downloader.ChartDownloader{
Verify: downloader.VerifyNever,
Getters: helmgetter.All(&cli.EnvSettings{}),
}

saved, _, err := c.DownloadTo(name, version, path)
if err != nil {
return "", err
}

return saved, nil
}

func newHttpGetter(auth Auth) *getter.HttpGetter {
httpGetter := &getter.HttpGetter{
Client: &http.Client{},
Expand Down
2 changes: 1 addition & 1 deletion pkg/bundle/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func chartPath(options fleet.BundleDeploymentOptions) (string, string) {
if options.Helm == nil || options.Helm.Chart == "" {
return chartYAML, ""
}
return joinAndClean(options.Helm.Chart, chartYAML), ChartPath(options.Helm) + "/"
return joinAndClean(options.Helm.Chart, chartYAML), checksum(options.Helm) + "/"
}

func kustomizePath(options fleet.BundleDeploymentOptions) string {
Expand Down