Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions pkg/helm/actions/get_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package actions

import (
"crypto/tls"
"fmt"
"net/http"

"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/registry"
)

func GetDefaultOCIRegistry(conf *action.Configuration) error {
return GetOCIRegistry(conf, false, false)
}

func GetOCIRegistry(conf *action.Configuration, insecure bool, plainHTTP bool) error {
if conf == nil {
return fmt.Errorf("action configuration cannot be nil")
}
opts := []registry.ClientOption{
registry.ClientOptDebug(false),
}
if plainHTTP {
opts = append(opts, registry.ClientOptPlainHTTP())
}
if insecure {
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
opts = append(opts, registry.ClientOptHTTPClient(httpClient))
}
registryClient, err := registry.NewClient(opts...)
if err != nil {
return fmt.Errorf("failed to create registry client: %w", err)
}
conf.RegistryClient = registryClient
return nil
}
52 changes: 52 additions & 0 deletions pkg/helm/actions/get_registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package actions

import (
"io"
"testing"

"github.com/stretchr/testify/require"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver"
)

func TestGetDefaultOCIRegistry_Success(t *testing.T) {
store := storage.Init(driver.NewMemory())
conf := &action.Configuration{
RESTClientGetter: FakeConfig{},
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities,
}
require.Nil(t, conf.RegistryClient, "Registry Client should be nil")

// Store original values
originalReleases := conf.Releases
originalKubeClient := conf.KubeClient
originalCapabilities := conf.Capabilities

err := GetDefaultOCIRegistry(conf)
require.NoError(t, err)
require.NotNil(t, conf.RegistryClient, "Registry Client should not be nil")

// Verify other configuration fields are not modified.
require.Equal(t, originalReleases, conf.Releases, "Releases should not be modified")
require.Equal(t, originalKubeClient, conf.KubeClient, "KubeClient should not be modified")
require.Equal(t, originalCapabilities, conf.Capabilities, "Capabilities should not be modified")

}

func TestGetDefaultOCIRegistry_NilConfig(t *testing.T) {
err := GetDefaultOCIRegistry(nil)
require.Error(t, err)
require.Contains(t, err.Error(), "action configuration cannot be nil")
}

func TestGetDefaultOCIRegistry_MinimumConfig(t *testing.T) {
conf := &action.Configuration{}
err := GetDefaultOCIRegistry(conf)
require.NoError(t, err)
require.NotNil(t, conf.RegistryClient, "Registry Client should not be nil")
}
46 changes: 46 additions & 0 deletions pkg/helm/actions/install_chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"fmt"
"os"
"strings"
"time"

"github.com/openshift/api/helm/v1beta1"
"github.com/openshift/console/pkg/helm/metrics"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release"
kv1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -195,3 +197,47 @@ func InstallChartAsync(ns, name, url string, vals map[string]interface{}, conf *
}
return &secret, nil
}

func InstallOCIChart(ns, name, url string, vals map[string]interface{}, conf *action.Configuration) (*release.Release, error) {

// Accept OCI URLs (oci://...) or direct HTTP/HTTPS chart URLs (*.tgz)
isOCI := registry.IsOCI(url)
isDirectChartURL := strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
isChartArchive := strings.HasSuffix(url, ".tgz") || strings.HasSuffix(url, ".tar.gz")

if !isOCI && !(isDirectChartURL && isChartArchive) {
return nil, fmt.Errorf("invalid chart URL: %s, must be oci:// URL or http(s)://*.tgz", url)
}

cmd := action.NewInstall(conf)
cmd.ReleaseName = name
cmd.Namespace = ns

cp, err := cmd.ChartPathOptions.LocateChart(url, settings)
if err != nil {
return nil, fmt.Errorf("error locating chart: %v", err)
}
ch, err := loader.Load(cp)
if err != nil {
return nil, err
}

// Add chart URL as an annotation before installation
if ch.Metadata == nil {
ch.Metadata = new(chart.Metadata)
}
if ch.Metadata.Annotations == nil {
ch.Metadata.Annotations = make(map[string]string)
}
ch.Metadata.Annotations["chart_url"] = url

release, err := cmd.Run(ch, vals)
if err != nil {
return nil, err
}
if ch.Metadata.Name != "" && ch.Metadata.Version != "" {
metrics.HandleconsoleHelmInstallsTotal(ch.Metadata.Name, ch.Metadata.Version)
}

return release, nil
}
62 changes: 62 additions & 0 deletions pkg/helm/actions/install_chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,65 @@ func TestInstallChartAsync(t *testing.T) {
})
}
}

func TestInstallOCIChart(t *testing.T) {
tests := []struct {
releaseName string
chartPath string
chartName string
chartVersion string
plainHTTP bool
insecureTLS bool
}{
{
releaseName: "valid-chart-path",
chartPath: "http://localhost:9181/charts/influxdb-3.0.2.tgz",
chartName: "influxdb",
chartVersion: "3.0.2",
},
{
releaseName: "valid-chart-path",
chartPath: "oci://localhost:5000/helm-charts/mychart:0.1.0",
chartName: "mychart",
chartVersion: "0.1.0",
plainHTTP: true,
},
{
releaseName: "valid-chart-path",
chartPath: "oci://localhost:5443/helm-charts/mariadb:7.3.5",
chartName: "mariadb",
chartVersion: "7.3.5",
insecureTLS: true,
},
{
releaseName: "invalid-chart-path",
chartPath: "http://localhost:9181/charts/influxdb/filename",
chartName: "influxdb",
chartVersion: "3.0.1",
},
}
for _, tt := range tests {
t.Run(tt.releaseName, func(t *testing.T) {
store := storage.Init(driver.NewMemory())
actionConfig := &action.Configuration{
RESTClientGetter: FakeConfig{},
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
}
GetOCIRegistry(actionConfig, tt.insecureTLS, tt.plainHTTP)

rel, err := InstallOCIChart("test-namespace", tt.releaseName, tt.chartPath, nil, actionConfig)
if tt.releaseName == "valid-chart-path" {
require.NoError(t, err)
require.Equal(t, tt.releaseName, rel.Name)
require.Equal(t, tt.chartVersion, rel.Chart.Metadata.Version)
require.Equal(t, tt.chartPath, rel.Chart.Metadata.Annotations["chart_url"])

} else if tt.releaseName == "invalid-chart-path" {
require.Error(t, err)
}
})
}
}
21 changes: 21 additions & 0 deletions pkg/helm/actions/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func TestMain(m *testing.M) {
if err := ExecuteScript("./testdata/chartmuseum-stop.sh", false); err != nil {
panic(err)
}
if err := ExecuteScript("./testdata/zot-stop.sh", false); err != nil {
panic(err)
}
if err := ExecuteScript("./testdata/cleanupNonTls.sh", false); err != nil {
panic(err)
}
Expand All @@ -52,24 +55,42 @@ func setupTestWithTls() error {
if err := ExecuteScript("./testdata/chartmuseum.sh", false); err != nil {
return err
}
if err := ExecuteScript("./testdata/downloadZot.sh", true); err != nil {
return err
}
if err := ExecuteScript("./testdata/downloadHelm.sh", false); err != nil {
return err
}
if err := ExecuteScript("./testdata/zot.sh", false); err != nil {
return err
}
time.Sleep(5 * time.Second)
if err := ExecuteScript("./testdata/cacertCreate.sh", true); err != nil {
return err
}
if err := ExecuteScript("./testdata/uploadCharts.sh", true); err != nil {
return err
}
if err := ExecuteScript("./testdata/uploadOciCharts.sh", true); err != nil {
return err
}
return nil
}

func setupTestWithoutTls() error {
if err := ExecuteScript("./testdata/chartmuseumWithoutTls.sh", false); err != nil {
return err
}
if err := ExecuteScript("./testdata/zotWithoutTls.sh", false); err != nil {
return err
}
time.Sleep(5 * time.Second)
if err := ExecuteScript("./testdata/uploadChartsWithoutTls.sh", true); err != nil {
return err
}
if err := ExecuteScript("./testdata/uploadOciChartsWithoutTls.sh", true); err != nil {
return err
}
return nil
}

Expand Down
5 changes: 4 additions & 1 deletion pkg/helm/actions/testdata/cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ rm -rf ./server.key
GOOS=${GOOS:-$(go env GOOS)}
GOARCH=${GOARCH:-$(go env GOARCH)}
rm -rf ./$GOOS-$GOARCH
rm -rf ./chartmuseum.tar.gz
rm -rf ./chartmuseum.tar.gz
rm -rf ./helm.tar.gz
# Zot cleanup
rm -rf ./zot-storage-*
20 changes: 20 additions & 0 deletions pkg/helm/actions/testdata/downloadHelm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# Download Helm CLI for pushing OCI charts

set -e
GOOS=${GOOS:-$(go env GOOS)}
GOARCH=${GOARCH:-$(go env GOARCH)}
HELM_VERSION=${HELM_VERSION:-3.19.0}
HELM_ARTIFACT_URL="https://get.helm.sh/helm-v${HELM_VERSION}-${GOOS}-${GOARCH}.tar.gz"

mkdir -p "$GOOS-$GOARCH"

if [ ! -f "$GOOS-$GOARCH/helm" ]; then
echo "Downloading Helm v${HELM_VERSION}..."
curl -L -o helm.tar.gz "$HELM_ARTIFACT_URL"
tar xzf helm.tar.gz --strip-components=1 -C "$GOOS-$GOARCH" "${GOOS}-${GOARCH}/helm"
chmod +x "$GOOS-$GOARCH/helm"
fi

exit 0

18 changes: 18 additions & 0 deletions pkg/helm/actions/testdata/downloadZot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

set -e
GOOS=${GOOS:-$(go env GOOS)}
GOARCH=${GOARCH:-$(go env GOARCH)}
ZOT_VERSION=${ZOT_VERSION:-v2.1.6}
ZOT_ARTIFACT_URL="https://github.com/project-zot/zot/releases/download/$ZOT_VERSION/zot-$GOOS-$GOARCH"

mkdir -p "$GOOS-$GOARCH"

if [ ! -f "$GOOS-$GOARCH/zot" ]; then
curl -L -o "$GOOS-$GOARCH/zot" "$ZOT_ARTIFACT_URL"
chmod +x "$GOOS-$GOARCH/zot"
fi

exit 0

# $GOOS-$GOARCH/zot is available from now on
32 changes: 32 additions & 0 deletions pkg/helm/actions/testdata/uploadOciCharts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
# Upload Helm charts as OCI artifacts to zot registry (with TLS)
set -e

# Change to the script's parent directory (pkg/helm/actions/)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/.."

REGISTRY="localhost:5443"
CACERT="./cacert.pem"
CHARTS_DIR="../testdata"
GOOS=${GOOS:-$(go env GOOS)}
GOARCH=${GOARCH:-$(go env GOARCH)}

# Use local helm binary if available, otherwise use system helm
if [ -x "./$GOOS-$GOARCH/helm" ]; then
HELM="./$GOOS-$GOARCH/helm"
elif command -v helm &> /dev/null; then
HELM="helm"
else
echo "Error: Helm not found. Run ./downloadHelm.sh first or install helm."
exit 1
fi

# Push charts to OCI registry using helm push
echo "Pushing mychart-0.1.0.tgz to oci://$REGISTRY/helm-charts..."
$HELM push $CHARTS_DIR/mychart-0.1.0.tgz oci://$REGISTRY/helm-charts --ca-file=$CACERT

echo "Pushing mariadb-7.3.5.tgz to oci://$REGISTRY/helm-charts..."
$HELM push $CHARTS_DIR/mariadb-7.3.5.tgz oci://$REGISTRY/helm-charts --ca-file=$CACERT

echo "Charts pushed successfully!"
34 changes: 34 additions & 0 deletions pkg/helm/actions/testdata/uploadOciChartsWithoutTls.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
# Upload Helm charts as OCI artifacts to zot registry (without TLS)
set -e

# Change to the script's parent directory (pkg/helm/actions/)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/.."

REGISTRY="localhost:5000"
CHARTS_DIR="../testdata"
GOOS=${GOOS:-$(go env GOOS)}
GOARCH=${GOARCH:-$(go env GOARCH)}

# Use local helm binary if available, otherwise use system helm
if [ -x "./$GOOS-$GOARCH/helm" ]; then
HELM="./$GOOS-$GOARCH/helm"
elif command -v helm &> /dev/null; then
HELM="helm"
else
echo "Error: Helm not found. Run ./downloadHelm.sh first or install helm."
exit 1
fi

# Push charts to OCI registry using helm push
# Using --plain-http for non-TLS registry

echo "Pushing mychart-0.1.0.tgz to oci://$REGISTRY/helm-charts..."
$HELM push $CHARTS_DIR/mychart-0.1.0.tgz oci://$REGISTRY/helm-charts --plain-http

echo "Pushing mariadb-7.3.5.tgz to oci://$REGISTRY/helm-charts..."
$HELM push $CHARTS_DIR/mariadb-7.3.5.tgz oci://$REGISTRY/helm-charts --plain-http

echo "Charts pushed successfully!"

Loading