Skip to content

Commit

Permalink
Merge pull request #121 from goober/feature/oci-charts
Browse files Browse the repository at this point in the history
Add support for OCI charts
  • Loading branch information
turkenh authored Mar 1, 2022
2 parents bbe9ce4 + ec820a1 commit d04ceff
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 17 deletions.
2 changes: 2 additions & 0 deletions apis/release/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ type ReleaseParameters struct {
ValuesSpec `json:",inline"`
// SkipCRDs skips installation of CRDs for the release.
SkipCRDs bool `json:"skipCRDs,omitempty"`
// InsecureSkipTLSVerify skips tls certificate checks for the chart download
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
}

// ReleaseObservation are the observable fields of a Release.
Expand Down
62 changes: 62 additions & 0 deletions examples/sample/release-oci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
apiVersion: helm.crossplane.io/v1beta1
kind: Release
metadata:
name: wordpress-example
spec:
# rollbackLimit: 3
forProvider:
chart:
name: wordpress
repository: "oci://localhost:5000/helm-charts"
version: 9.3.19
# pullSecretRef:
# name: oci-creds
# namespace: default
# url: "oci://localhost:5000/helm-charts/wordpress:9.3.19"
namespace: wordpress
# insecureSkipTLSVerify: true
# skipCreateNamespace: true
# wait: true
# skipCRDs: true
values:
service:
type: ClusterIP
set:
- name: param1
value: value2
# valuesFrom:
# - configMapKeyRef:
# key: values.yaml
# name: default-vals
# namespace: wordpress
# optional: false
# - secretKeyRef:
# key: svalues.yaml
# name: svals
# namespace: wordpress
# optional: false
# connectionDetails:
# - apiVersion: v1
# kind: Service
# name: wordpress-example
# namespace: wordpress
# fieldPath: spec.clusterIP
# #fieldPath: status.loadBalancer.ingress[0].ip
# toConnectionSecretKey: ip
# - apiVersion: v1
# kind: Service
# name: wordpress-example
# namespace: wordpress
# fieldPath: spec.ports[0].port
# toConnectionSecretKey: port
# - apiVersion: v1
# kind: Secret
# name: wordpress-example
# namespace: wordpress
# fieldPath: data.wordpress-password
# toConnectionSecretKey: password
# writeConnectionSecretToRef:
# name: wordpress-credentials
# namespace: crossplane-system
providerConfigRef:
name: helm-provider
1 change: 1 addition & 0 deletions examples/sample/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ spec:
# namespace: default
# url: "https://charts.bitnami.com/bitnami/wordpress-9.3.19.tgz"
namespace: wordpress
# insecureSkipTLSVerify: true
# skipCreateNamespace: true
# wait: true
# skipCRDs: true
Expand Down
4 changes: 4 additions & 0 deletions package/crds/helm.crossplane.io_releases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@ spec:
latest version if not set
type: string
type: object
insecureSkipTLSVerify:
description: InsecureSkipTLSVerify skips tls certificate checks
for the chart download
type: boolean
namespace:
description: Namespace to install the release into.
type: string
Expand Down
2 changes: 2 additions & 0 deletions pkg/clients/helm/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ type Args struct {
Timeout time.Duration
// SkipCRDs skips CRDs creation during Helm release install or upgrade.
SkipCRDs bool
// InsecureSkipTLSVerify skips tls certificate checks for the chart download
InsecureSkipTLSVerify bool
}
114 changes: 97 additions & 17 deletions pkg/clients/helm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const (
errFailedToLoadChart = "failed to load chart"
errUnexpectedDirContentTmpl = "expected 1 .tgz chart file, got [%s]"
errFailedToParseURL = "failed to parse URL"
errFailedToLogin = "failed to login to registry"
errUnexpectedOCIUrlTmpl = "url not prefixed with oci://, got [%s]"
)

// Client is the interface to interact with Helm
Expand All @@ -71,6 +73,7 @@ type client struct {
upgradeClient *action.Upgrade
rollbackClient *action.Rollback
uninstallClient *action.Uninstall
loginClient *action.RegistryLogin
}

// ArgsApplier defines helm client arguments helper
Expand Down Expand Up @@ -111,6 +114,7 @@ func NewClient(log logging.Logger, restConfig *rest.Config, argAppliers ...ArgsA

pc.DestDir = chartCache
pc.Settings = &cli.EnvSettings{}
pc.InsecureSkipTLSverify = args.InsecureSkipTLSVerify

gc := action.NewGet(actionConfig)

Expand All @@ -119,18 +123,22 @@ func NewClient(log logging.Logger, restConfig *rest.Config, argAppliers ...ArgsA
ic.Wait = args.Wait
ic.Timeout = args.Timeout
ic.SkipCRDs = args.SkipCRDs
ic.InsecureSkipTLSverify = args.InsecureSkipTLSVerify

uc := action.NewUpgrade(actionConfig)
uc.Wait = args.Wait
uc.Timeout = args.Timeout
uc.SkipCRDs = args.SkipCRDs
uc.InsecureSkipTLSverify = args.InsecureSkipTLSVerify

uic := action.NewUninstall(actionConfig)

rb := action.NewRollback(actionConfig)
rb.Wait = args.Wait
rb.Timeout = args.Timeout

lc := action.NewRegistryLogin(actionConfig)

return &client{
log: log,
pullClient: pc,
Expand All @@ -139,6 +147,7 @@ func NewClient(log logging.Logger, restConfig *rest.Config, argAppliers ...ArgsA
upgradeClient: uc,
rollbackClient: rb,
uninstallClient: uic,
loginClient: lc,
}, nil
}

Expand Down Expand Up @@ -190,16 +199,31 @@ func (hc *client) pullChart(spec *v1beta1.ChartSpec, creds *RepoCreds, chartDir

chartRef := spec.URL
if spec.URL == "" {
chartRef = spec.Name

pc.RepoURL = spec.Repository
if registry.IsOCI(spec.Repository) {
chartRef = resolveOCIChartRef(spec.Repository, spec.Name)
} else {
chartRef = spec.Name
pc.RepoURL = spec.Repository
}
pc.Version = spec.Version
} else if registry.IsOCI(spec.URL) {
ociURL, version, err := resolveOCIChartVersion(spec.URL)
if err != nil {
return err
}
pc.Version = version
chartRef = ociURL.String()
}
pc.Username = creds.Username
pc.Password = creds.Password

pc.DestDir = chartDir

err := hc.login(spec, creds, pc.InsecureSkipTLSverify)
if err != nil {
return err
}

o, err := pc.Run(chartRef)
hc.log.Debug(o)
if err != nil {
Expand All @@ -208,32 +232,63 @@ func (hc *client) pullChart(spec *v1beta1.ChartSpec, creds *RepoCreds, chartDir
return nil
}

func (hc *client) login(spec *v1beta1.ChartSpec, creds *RepoCreds, insecure bool) error {
ociURL := spec.URL
if spec.URL == "" {
ociURL = spec.Repository
}
if !registry.IsOCI(ociURL) {
return nil
}
parsedURL, err := url.Parse(ociURL)
if err != nil {
return errors.Wrap(err, errFailedToParseURL)
}
var out strings.Builder
err = hc.loginClient.Run(&out, parsedURL.Host, creds.Username, creds.Password, insecure)
hc.log.Debug(out.String())
return errors.Wrap(err, errFailedToLogin)
}

func (hc *client) PullAndLoadChart(spec *v1beta1.ChartSpec, creds *RepoCreds) (*chart.Chart, error) {
var chartFilePath string
var err error
if spec.URL == "" && spec.Version == "" {
switch {
case spec.URL == "" && spec.Version == "":
chartFilePath, err = hc.pullLatestChartVersion(spec, creds)
if err != nil {
return nil, err
}
} else {
filename := fmt.Sprintf("%s-%s.tgz", spec.Name, spec.Version)
if spec.URL != "" {
u, err := url.Parse(spec.URL)
if err != nil {
return nil, errors.Wrap(err, errFailedToParseURL)
}
filename = path.Base(u.Path)
case registry.IsOCI(spec.URL):
u, v, err := resolveOCIChartVersion(spec.URL)
if err != nil {
return nil, err
}
chartFilePath = filepath.Join(chartCache, filename)

if _, err := os.Stat(chartFilePath); os.IsNotExist(err) {
if err = hc.pullChart(spec, creds, chartCache); err != nil {
if v == "" {
chartFilePath, err = hc.pullLatestChartVersion(spec, creds)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, errors.Wrap(err, errFailedToCheckIfLocalChartExists)
} else {
chartFilePath = resolveChartFilePath(path.Base(u.Path), v)
}
case spec.URL != "":
u, err := url.Parse(spec.URL)
if err != nil {
return nil, errors.Wrap(err, errFailedToParseURL)
}
chartFilePath = filepath.Join(chartCache, path.Base(u.Path))
default:
chartFilePath = resolveChartFilePath(spec.Name, spec.Version)
}

if _, err := os.Stat(chartFilePath); os.IsNotExist(err) {
if err = hc.pullChart(spec, creds, chartCache); err != nil {
return nil, err
}
} else if err != nil {
return nil, errors.Wrap(err, errFailedToCheckIfLocalChartExists)
}

chart, err := loader.Load(chartFilePath)
Expand Down Expand Up @@ -283,3 +338,28 @@ func (hc *client) Uninstall(release string) error {
_, err := hc.uninstallClient.Run(release)
return err
}

func resolveOCIChartVersion(chartURL string) (*url.URL, string, error) {
if !registry.IsOCI(chartURL) {
return nil, "", errors.Errorf(errUnexpectedOCIUrlTmpl, chartURL)
}
ociURL, err := url.Parse(chartURL)
if err != nil {
return nil, "", errors.Wrap(err, errFailedToParseURL)
}
parts := strings.Split(ociURL.Path, ":")
if len(parts) > 1 {
ociURL.Path = parts[0]
return ociURL, parts[1], nil
}
return ociURL, "", nil
}

func resolveChartFilePath(name string, version string) string {
filename := fmt.Sprintf("%s-%s.tgz", name, version)
return filepath.Join(chartCache, filename)
}

func resolveOCIChartRef(repository string, name string) string {
return strings.Join([]string{strings.TrimSuffix(repository, "/"), name}, "/")
}
1 change: 1 addition & 0 deletions pkg/controller/release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func withRelease(cr *v1beta1.Release) helmClient.ArgsApplier {
config.Wait = cr.Spec.ForProvider.Wait
config.Timeout = waitTimeout(cr)
config.SkipCRDs = cr.Spec.ForProvider.SkipCRDs
config.InsecureSkipTLSVerify = cr.Spec.ForProvider.InsecureSkipTLSVerify
}
}

Expand Down

0 comments on commit d04ceff

Please sign in to comment.