mirror of
https://github.com/portainer/portainer.git
synced 2025-07-18 21:09:40 +02:00
feat(helm): use helm upgrade for install [r8s-258] (#568)
This commit is contained in:
parent
e68bd53e30
commit
0ebfe047d1
19 changed files with 613 additions and 150 deletions
|
@ -42,7 +42,7 @@ func Test_helmDelete(t *testing.T) {
|
||||||
|
|
||||||
// Install a single chart directly, to be deleted by the handler
|
// Install a single chart directly, to be deleted by the handler
|
||||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||||
h.helmPackageManager.Install(options)
|
h.helmPackageManager.Upgrade(options)
|
||||||
|
|
||||||
t.Run("helmDelete succeeds with admin user", func(t *testing.T) {
|
t.Run("helmDelete succeeds with admin user", func(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodDelete, "/1/kubernetes/helm/"+options.Name, nil)
|
req := httptest.NewRequest(http.MethodDelete, "/1/kubernetes/helm/"+options.Name, nil)
|
||||||
|
|
|
@ -125,7 +125,7 @@ func (handler *Handler) installChart(r *http.Request, p installChartPayload) (*r
|
||||||
installOpts.ValuesFile = file.Name()
|
installOpts.ValuesFile = file.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := handler.helmPackageManager.Install(installOpts)
|
release, err := handler.helmPackageManager.Upgrade(installOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func Test_helmList(t *testing.T) {
|
||||||
|
|
||||||
// Install a single chart. We expect to get these values back
|
// Install a single chart. We expect to get these values back
|
||||||
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
options := options.InstallOptions{Name: "nginx-1", Chart: "nginx", Namespace: "default"}
|
||||||
h.helmPackageManager.Install(options)
|
h.helmPackageManager.Upgrade(options)
|
||||||
|
|
||||||
t.Run("helmList", func(t *testing.T) {
|
t.Run("helmList", func(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/1/kubernetes/helm", nil)
|
req := httptest.NewRequest(http.MethodGet, "/1/kubernetes/helm", nil)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package options
|
package options
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type InstallOptions struct {
|
type InstallOptions struct {
|
||||||
Name string
|
Name string
|
||||||
Chart string
|
Chart string
|
||||||
|
@ -8,6 +10,7 @@ type InstallOptions struct {
|
||||||
Wait bool
|
Wait bool
|
||||||
ValuesFile string
|
ValuesFile string
|
||||||
PostRenderer string
|
PostRenderer string
|
||||||
|
Timeout time.Duration
|
||||||
KubernetesClusterAccess *KubernetesClusterAccess
|
KubernetesClusterAccess *KubernetesClusterAccess
|
||||||
|
|
||||||
// Optional environment vars to pass when running helm
|
// Optional environment vars to pass when running helm
|
||||||
|
|
87
pkg/libhelm/sdk/common.go
Normal file
87
pkg/libhelm/sdk/common.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"helm.sh/helm/v3/pkg/action"
|
||||||
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
|
"helm.sh/helm/v3/pkg/downloader"
|
||||||
|
"helm.sh/helm/v3/pkg/getter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// loadAndValidateChartWithPathOptions locates and loads the chart, and validates it.
|
||||||
|
// it also checks for chart dependencies and updates them if necessary.
|
||||||
|
// it returns the chart information.
|
||||||
|
func (hspm *HelmSDKPackageManager) loadAndValidateChartWithPathOptions(chartPathOptions *action.ChartPathOptions, chartName, repoURL string, dependencyUpdate bool, operation string) (*chart.Chart, error) {
|
||||||
|
// Locate and load the chart
|
||||||
|
chartPathOptions.RepoURL = repoURL
|
||||||
|
chartPath, err := chartPathOptions.LocateChart(chartName, hspm.settings)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", chartName).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to locate chart for helm " + operation)
|
||||||
|
return nil, errors.Wrapf(err, "failed to find the helm chart at the path: %s/%s", repoURL, chartName)
|
||||||
|
}
|
||||||
|
|
||||||
|
chartReq, err := loader.Load(chartPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart_path", chartPath).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to load chart for helm " + operation)
|
||||||
|
return nil, errors.Wrap(err, "failed to load chart for helm "+operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check chart dependencies to make sure all are present in /charts
|
||||||
|
if chartDependencies := chartReq.Metadata.Dependencies; chartDependencies != nil {
|
||||||
|
if err := action.CheckDependencies(chartReq, chartDependencies); err != nil {
|
||||||
|
err = errors.Wrap(err, "failed to check chart dependencies for helm "+operation)
|
||||||
|
if !dependencyUpdate {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", chartName).
|
||||||
|
Msg("Updating chart dependencies for helm " + operation)
|
||||||
|
|
||||||
|
providers := getter.All(hspm.settings)
|
||||||
|
manager := &downloader.Manager{
|
||||||
|
Out: os.Stdout,
|
||||||
|
ChartPath: chartPath,
|
||||||
|
Keyring: chartPathOptions.Keyring,
|
||||||
|
SkipUpdate: false,
|
||||||
|
Getters: providers,
|
||||||
|
RepositoryConfig: hspm.settings.RepositoryConfig,
|
||||||
|
RepositoryCache: hspm.settings.RepositoryCache,
|
||||||
|
Debug: hspm.settings.Debug,
|
||||||
|
}
|
||||||
|
if err := manager.Update(); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", chartName).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to update chart dependencies for helm " + operation)
|
||||||
|
return nil, errors.Wrap(err, "failed to update chart dependencies for helm "+operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the chart with the updated Chart.lock file.
|
||||||
|
if chartReq, err = loader.Load(chartPath); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart_path", chartPath).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to reload chart after dependency update for helm " + operation)
|
||||||
|
return nil, errors.Wrap(err, "failed to reload chart after dependency update for helm "+operation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chartReq, nil
|
||||||
|
}
|
|
@ -1,22 +1,18 @@
|
||||||
package sdk
|
package sdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/portainer/portainer/pkg/libhelm/options"
|
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||||
"github.com/portainer/portainer/pkg/libhelm/release"
|
"github.com/portainer/portainer/pkg/libhelm/release"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"helm.sh/helm/v3/pkg/action"
|
"helm.sh/helm/v3/pkg/action"
|
||||||
"helm.sh/helm/v3/pkg/chart"
|
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
|
||||||
"helm.sh/helm/v3/pkg/downloader"
|
|
||||||
"helm.sh/helm/v3/pkg/getter"
|
|
||||||
"helm.sh/helm/v3/pkg/postrender"
|
"helm.sh/helm/v3/pkg/postrender"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Install implements the HelmPackageManager interface by using the Helm SDK to install a chart.
|
// Install implements the HelmPackageManager interface by using the Helm SDK to install a chart.
|
||||||
func (hspm *HelmSDKPackageManager) Install(installOpts options.InstallOptions) (*release.Release, error) {
|
func (hspm *HelmSDKPackageManager) install(installOpts options.InstallOptions) (*release.Release, error) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("context", "HelmClient").
|
Str("context", "HelmClient").
|
||||||
Str("chart", installOpts.Chart).
|
Str("chart", installOpts.Chart).
|
||||||
|
@ -42,12 +38,7 @@ func (hspm *HelmSDKPackageManager) Install(installOpts options.InstallOptions) (
|
||||||
actionConfig := new(action.Configuration)
|
actionConfig := new(action.Configuration)
|
||||||
err := hspm.initActionConfig(actionConfig, installOpts.Namespace, installOpts.KubernetesClusterAccess)
|
err := hspm.initActionConfig(actionConfig, installOpts.Namespace, installOpts.KubernetesClusterAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
// error is already logged in initActionConfig
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart", installOpts.Chart).
|
|
||||||
Str("namespace", installOpts.Namespace).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to initialize helm configuration for helm release installation")
|
|
||||||
return nil, errors.Wrap(err, "failed to initialize helm configuration for helm release installation")
|
return nil, errors.Wrap(err, "failed to initialize helm configuration for helm release installation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +60,7 @@ func (hspm *HelmSDKPackageManager) Install(installOpts options.InstallOptions) (
|
||||||
return nil, errors.Wrap(err, "failed to get Helm values from file for helm release installation")
|
return nil, errors.Wrap(err, "failed to get Helm values from file for helm release installation")
|
||||||
}
|
}
|
||||||
|
|
||||||
chart, err := hspm.loadAndValidateChart(installClient, installOpts)
|
chart, err := hspm.loadAndValidateChartWithPathOptions(&installClient.ChartPathOptions, installOpts.Chart, installOpts.Repo, installClient.DependencyUpdate, "release installation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("context", "HelmClient").
|
Str("context", "HelmClient").
|
||||||
|
@ -114,90 +105,29 @@ func (hspm *HelmSDKPackageManager) Install(installOpts options.InstallOptions) (
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadAndValidateChart locates and loads the chart, and validates it.
|
|
||||||
// it also checks for chart dependencies and updates them if necessary.
|
|
||||||
// it returns the chart information.
|
|
||||||
func (hspm *HelmSDKPackageManager) loadAndValidateChart(installClient *action.Install, installOpts options.InstallOptions) (*chart.Chart, error) {
|
|
||||||
// Locate and load the chart
|
|
||||||
chartPath, err := installClient.ChartPathOptions.LocateChart(installOpts.Chart, hspm.settings)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart", installOpts.Chart).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to locate chart for helm release installation")
|
|
||||||
return nil, errors.Wrapf(err, "failed to find the helm chart at the path: %s/%s", installOpts.Repo, installOpts.Chart)
|
|
||||||
}
|
|
||||||
|
|
||||||
chartReq, err := loader.Load(chartPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart_path", chartPath).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to load chart for helm release installation")
|
|
||||||
return nil, errors.Wrap(err, "failed to load chart for helm release installation")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check chart dependencies to make sure all are present in /charts
|
|
||||||
if chartDependencies := chartReq.Metadata.Dependencies; chartDependencies != nil {
|
|
||||||
if err := action.CheckDependencies(chartReq, chartDependencies); err != nil {
|
|
||||||
err = errors.Wrap(err, "failed to check chart dependencies for helm release installation")
|
|
||||||
if !installClient.DependencyUpdate {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().
|
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart", installOpts.Chart).
|
|
||||||
Msg("Updating chart dependencies for helm release installation")
|
|
||||||
|
|
||||||
providers := getter.All(hspm.settings)
|
|
||||||
manager := &downloader.Manager{
|
|
||||||
Out: os.Stdout,
|
|
||||||
ChartPath: chartPath,
|
|
||||||
Keyring: installClient.ChartPathOptions.Keyring,
|
|
||||||
SkipUpdate: false,
|
|
||||||
Getters: providers,
|
|
||||||
RepositoryConfig: hspm.settings.RepositoryConfig,
|
|
||||||
RepositoryCache: hspm.settings.RepositoryCache,
|
|
||||||
Debug: hspm.settings.Debug,
|
|
||||||
}
|
|
||||||
if err := manager.Update(); err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart", installOpts.Chart).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to update chart dependencies for helm release installation")
|
|
||||||
return nil, errors.Wrap(err, "failed to update chart dependencies for helm release installation")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the chart with the updated Chart.lock file.
|
|
||||||
if chartReq, err = loader.Load(chartPath); err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("chart_path", chartPath).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to reload chart after dependency update for helm release installation")
|
|
||||||
return nil, errors.Wrap(err, "failed to reload chart after dependency update for helm release installation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chartReq, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// initInstallClient initializes the install client with the given options
|
// initInstallClient initializes the install client with the given options
|
||||||
// and return the install client.
|
// and return the install client.
|
||||||
func initInstallClient(actionConfig *action.Configuration, installOpts options.InstallOptions) (*action.Install, error) {
|
func initInstallClient(actionConfig *action.Configuration, installOpts options.InstallOptions) (*action.Install, error) {
|
||||||
installClient := action.NewInstall(actionConfig)
|
installClient := action.NewInstall(actionConfig)
|
||||||
installClient.CreateNamespace = true
|
installClient.CreateNamespace = true
|
||||||
installClient.DependencyUpdate = true
|
installClient.DependencyUpdate = true
|
||||||
|
|
||||||
installClient.ReleaseName = installOpts.Name
|
installClient.ReleaseName = installOpts.Name
|
||||||
installClient.Namespace = installOpts.Namespace
|
|
||||||
installClient.ChartPathOptions.RepoURL = installOpts.Repo
|
installClient.ChartPathOptions.RepoURL = installOpts.Repo
|
||||||
installClient.Wait = installOpts.Wait
|
installClient.Wait = installOpts.Wait
|
||||||
|
installClient.Timeout = installOpts.Timeout
|
||||||
|
|
||||||
|
// Set default values if not specified
|
||||||
|
if installOpts.Timeout == 0 {
|
||||||
|
installClient.Timeout = 5 * time.Minute
|
||||||
|
} else {
|
||||||
|
installClient.Timeout = installOpts.Timeout
|
||||||
|
}
|
||||||
|
if installOpts.Namespace == "" {
|
||||||
|
installClient.Namespace = "default"
|
||||||
|
} else {
|
||||||
|
installClient.Namespace = installOpts.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
if installOpts.PostRenderer != "" {
|
if installOpts.PostRenderer != "" {
|
||||||
postRenderer, err := postrender.NewExec(installOpts.PostRenderer)
|
postRenderer, err := postrender.NewExec(installOpts.PostRenderer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,26 +9,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createValuesFile(values string) (string, error) {
|
|
||||||
file, err := os.CreateTemp("", "helm-values")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.WriteString(values)
|
|
||||||
if err != nil {
|
|
||||||
file.Close()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.Name(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Install(t *testing.T) {
|
func Test_Install(t *testing.T) {
|
||||||
test.EnsureIntegrationTest(t)
|
test.EnsureIntegrationTest(t)
|
||||||
is := assert.New(t)
|
is := assert.New(t)
|
||||||
|
@ -44,10 +24,14 @@ func Test_Install(t *testing.T) {
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := hspm.Install(installOpts)
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil {
|
if release != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: "test-nginx",
|
Name: installOpts.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,9 +44,8 @@ func Test_Install(t *testing.T) {
|
||||||
|
|
||||||
t.Run("successfully installs nginx with values", func(t *testing.T) {
|
t.Run("successfully installs nginx with values", func(t *testing.T) {
|
||||||
// SDK equivalent of: helm install test-nginx-2 --repo https://kubernetes.github.io/ingress-nginx nginx --values /tmp/helm-values3161785816
|
// SDK equivalent of: helm install test-nginx-2 --repo https://kubernetes.github.io/ingress-nginx nginx --values /tmp/helm-values3161785816
|
||||||
values, err := createValuesFile("service:\n port: 8081")
|
values, err := test.CreateValuesFile("service:\n port: 8081")
|
||||||
is.NoError(err, "should create a values file")
|
is.NoError(err, "should create a values file")
|
||||||
|
|
||||||
defer os.Remove(values)
|
defer os.Remove(values)
|
||||||
|
|
||||||
installOpts := options.InstallOptions{
|
installOpts := options.InstallOptions{
|
||||||
|
@ -71,10 +54,15 @@ func Test_Install(t *testing.T) {
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
ValuesFile: values,
|
ValuesFile: values,
|
||||||
}
|
}
|
||||||
release, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil {
|
if release != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: "test-nginx-2",
|
Name: installOpts.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +80,12 @@ func Test_Install(t *testing.T) {
|
||||||
Chart: "portainer",
|
Chart: "portainer",
|
||||||
Repo: "https://portainer.github.io/k8s/",
|
Repo: "https://portainer.github.io/k8s/",
|
||||||
}
|
}
|
||||||
release, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil {
|
if release != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: installOpts.Name,
|
Name: installOpts.Name,
|
||||||
|
@ -108,9 +101,8 @@ func Test_Install(t *testing.T) {
|
||||||
|
|
||||||
t.Run("install with values as string", func(t *testing.T) {
|
t.Run("install with values as string", func(t *testing.T) {
|
||||||
// First create a values file since InstallOptions doesn't support values as string directly
|
// First create a values file since InstallOptions doesn't support values as string directly
|
||||||
values, err := createValuesFile("service:\n port: 8082")
|
values, err := test.CreateValuesFile("service:\n port: 8082")
|
||||||
is.NoError(err, "should create a values file")
|
is.NoError(err, "should create a values file")
|
||||||
|
|
||||||
defer os.Remove(values)
|
defer os.Remove(values)
|
||||||
|
|
||||||
// Install with values file
|
// Install with values file
|
||||||
|
@ -120,10 +112,15 @@ func Test_Install(t *testing.T) {
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
ValuesFile: values,
|
ValuesFile: values,
|
||||||
}
|
}
|
||||||
release, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil {
|
if release != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: "test-nginx-3",
|
Name: installOpts.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,11 +137,15 @@ func Test_Install(t *testing.T) {
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
}
|
}
|
||||||
release, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil {
|
if release != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: "test-nginx-4",
|
Name: installOpts.Name,
|
||||||
Namespace: "default",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,10 +160,15 @@ func Test_Install(t *testing.T) {
|
||||||
Chart: "ingress-nginx",
|
Chart: "ingress-nginx",
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
}
|
}
|
||||||
_, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := hspm.Upgrade(installOpts)
|
||||||
|
|
||||||
is.Error(err, "should return an error when name is not provided")
|
is.Error(err, "should return an error when name is not provided")
|
||||||
is.Equal(err.Error(), "name is required")
|
// is.Equal(err.Error(), "name is required for helm release installation")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("install with invalid chart", func(t *testing.T) {
|
t.Run("install with invalid chart", func(t *testing.T) {
|
||||||
|
@ -172,9 +178,8 @@ func Test_Install(t *testing.T) {
|
||||||
Chart: "non-existent-chart",
|
Chart: "non-existent-chart",
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
}
|
}
|
||||||
_, err := hspm.Install(installOpts)
|
_, err := hspm.Upgrade(installOpts)
|
||||||
is.Error(err, "should return error when chart doesn't exist")
|
is.Error(err, "should return error when chart doesn't exist")
|
||||||
is.Equal(err.Error(), "failed to find the helm chart at the path: https://kubernetes.github.io/ingress-nginx/non-existent-chart")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("install with invalid repo", func(t *testing.T) {
|
t.Run("install with invalid repo", func(t *testing.T) {
|
||||||
|
@ -184,7 +189,12 @@ func Test_Install(t *testing.T) {
|
||||||
Chart: "nginx",
|
Chart: "nginx",
|
||||||
Repo: "https://non-existent-repo.example.com",
|
Repo: "https://non-existent-repo.example.com",
|
||||||
}
|
}
|
||||||
_, err := hspm.Install(installOpts)
|
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := hspm.Upgrade(installOpts)
|
||||||
is.Error(err, "should return error when repo doesn't exist")
|
is.Error(err, "should return error when repo doesn't exist")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,7 @@ func (hspm *HelmSDKPackageManager) List(listOpts options.ListOptions) ([]release
|
||||||
actionConfig := new(action.Configuration)
|
actionConfig := new(action.Configuration)
|
||||||
err := hspm.initActionConfig(actionConfig, listOpts.Namespace, listOpts.KubernetesClusterAccess)
|
err := hspm.initActionConfig(actionConfig, listOpts.Namespace, listOpts.KubernetesClusterAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
// error is already logged in initActionConfig
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("namespace", listOpts.Namespace).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to initialize helm configuration")
|
|
||||||
return nil, errors.Wrap(err, "failed to initialize helm configuration")
|
return nil, errors.Wrap(err, "failed to initialize helm configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
pkg/libhelm/sdk/release.go
Normal file
47
pkg/libhelm/sdk/release.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||||
|
"helm.sh/helm/v3/pkg/action"
|
||||||
|
"helm.sh/helm/v3/pkg/release"
|
||||||
|
"helm.sh/helm/v3/pkg/storage/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (hspm *HelmSDKPackageManager) doesReleaseExist(releaseName, namespace string, clusterAccess *options.KubernetesClusterAccess) (bool, error) {
|
||||||
|
// Initialize action configuration
|
||||||
|
actionConfig := new(action.Configuration)
|
||||||
|
err := hspm.initActionConfig(actionConfig, namespace, clusterAccess)
|
||||||
|
if err != nil {
|
||||||
|
// error is already logged in initActionConfig
|
||||||
|
return false, fmt.Errorf("failed to initialize helm configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
historyClient, err := hspm.initHistoryClient(actionConfig, namespace, clusterAccess)
|
||||||
|
if err != nil {
|
||||||
|
// error is already logged in initHistoryClient
|
||||||
|
return false, fmt.Errorf("failed to initialize helm history client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versions, err := historyClient.Run(releaseName)
|
||||||
|
if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(versions) {
|
||||||
|
return false, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get history: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isReleaseUninstalled(versions []*release.Release) bool {
|
||||||
|
return len(versions) > 0 && versions[len(versions)-1].Info.Status == release.StatusUninstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hspm *HelmSDKPackageManager) initHistoryClient(actionConfig *action.Configuration, namespace string, clusterAccess *options.KubernetesClusterAccess) (*action.History, error) {
|
||||||
|
historyClient := action.NewHistory(actionConfig)
|
||||||
|
historyClient.Max = 1
|
||||||
|
|
||||||
|
return historyClient, nil
|
||||||
|
}
|
|
@ -32,13 +32,11 @@ func (hspm *HelmSDKPackageManager) Show(showOpts options.ShowOptions) ([]byte, e
|
||||||
Str("output_format", string(showOpts.OutputFormat)).
|
Str("output_format", string(showOpts.OutputFormat)).
|
||||||
Msg("Showing chart information")
|
Msg("Showing chart information")
|
||||||
|
|
||||||
// Initialize action configuration
|
// Initialize action configuration (no namespace or cluster access needed)
|
||||||
actionConfig := new(action.Configuration)
|
actionConfig := new(action.Configuration)
|
||||||
if err := actionConfig.Init(nil, "", "", func(format string, v ...interface{}) {}); err != nil {
|
err := hspm.initActionConfig(actionConfig, "", nil)
|
||||||
log.Error().
|
if err != nil {
|
||||||
Str("context", "HelmClient").
|
// error is already logged in initActionConfig
|
||||||
Err(err).
|
|
||||||
Msg("Failed to initialize helm configuration")
|
|
||||||
return nil, fmt.Errorf("failed to initialize helm configuration: %w", err)
|
return nil, fmt.Errorf("failed to initialize helm configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ func Test_Show(t *testing.T) {
|
||||||
Chart: "ingress-nginx",
|
Chart: "ingress-nginx",
|
||||||
Repo: "https://kubernetes.github.io/ingress-nginx",
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
}
|
}
|
||||||
release, err := hspm.Install(installOpts)
|
release, err := hspm.Upgrade(installOpts)
|
||||||
if release != nil || err != nil {
|
if release != nil || err != nil {
|
||||||
defer hspm.Uninstall(options.UninstallOptions{
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
Name: "ingress-nginx",
|
Name: "ingress-nginx",
|
||||||
|
|
26
pkg/libhelm/sdk/testutils/values.go
Normal file
26
pkg/libhelm/sdk/testutils/values.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateValuesFile creates a temporary file with the given content for testing
|
||||||
|
func CreateValuesFile(values string) (string, error) {
|
||||||
|
file, err := os.CreateTemp("", "helm-values")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.WriteString(values)
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Name(), nil
|
||||||
|
}
|
|
@ -26,12 +26,7 @@ func (hspm *HelmSDKPackageManager) Uninstall(uninstallOpts options.UninstallOpti
|
||||||
actionConfig := new(action.Configuration)
|
actionConfig := new(action.Configuration)
|
||||||
err := hspm.initActionConfig(actionConfig, uninstallOpts.Namespace, uninstallOpts.KubernetesClusterAccess)
|
err := hspm.initActionConfig(actionConfig, uninstallOpts.Namespace, uninstallOpts.KubernetesClusterAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
// error is already logged in initActionConfig
|
||||||
Str("context", "HelmClient").
|
|
||||||
Str("release", uninstallOpts.Name).
|
|
||||||
Str("namespace", uninstallOpts.Namespace).
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to initialize helm configuration")
|
|
||||||
return errors.Wrap(err, "failed to initialize helm configuration")
|
return errors.Wrap(err, "failed to initialize helm configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ func Test_Uninstall(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the release
|
// Install the release
|
||||||
_, err := hspm.Install(installOpts)
|
_, err := hspm.Upgrade(installOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Error installing release: %v", err)
|
t.Logf("Error installing release: %v", err)
|
||||||
t.Skip("Skipping uninstall test because install failed")
|
t.Skip("Skipping uninstall test because install failed")
|
||||||
|
|
161
pkg/libhelm/sdk/upgrade.go
Normal file
161
pkg/libhelm/sdk/upgrade.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||||
|
"github.com/portainer/portainer/pkg/libhelm/release"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"helm.sh/helm/v3/pkg/action"
|
||||||
|
"helm.sh/helm/v3/pkg/postrender"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Upgrade implements the HelmPackageManager interface by using the Helm SDK to upgrade a chart.
|
||||||
|
// If the release does not exist, it will install it instead.
|
||||||
|
func (hspm *HelmSDKPackageManager) Upgrade(upgradeOpts options.InstallOptions) (*release.Release, error) {
|
||||||
|
log.Debug().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", upgradeOpts.Chart).
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Str("repo", upgradeOpts.Repo).
|
||||||
|
Bool("wait", upgradeOpts.Wait).
|
||||||
|
Msg("Upgrading Helm chart")
|
||||||
|
|
||||||
|
if upgradeOpts.Name == "" {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", upgradeOpts.Chart).
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Str("repo", upgradeOpts.Repo).
|
||||||
|
Bool("wait", upgradeOpts.Wait).
|
||||||
|
Msg("Name is required for helm release upgrade")
|
||||||
|
return nil, errors.New("name is required for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the release exists
|
||||||
|
exists, err := hspm.doesReleaseExist(upgradeOpts.Name, upgradeOpts.Namespace, upgradeOpts.KubernetesClusterAccess)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to check if release exists")
|
||||||
|
return nil, errors.Wrap(err, "failed to check if release exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the release doesn't exist, install it instead
|
||||||
|
if !exists {
|
||||||
|
log.Info().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", upgradeOpts.Chart).
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Msg("Release doesn't exist, installing instead")
|
||||||
|
return hspm.install(upgradeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize action configuration with kubernetes config
|
||||||
|
actionConfig := new(action.Configuration)
|
||||||
|
err = hspm.initActionConfig(actionConfig, upgradeOpts.Namespace, upgradeOpts.KubernetesClusterAccess)
|
||||||
|
if err != nil {
|
||||||
|
// error is already logged in initActionConfig
|
||||||
|
return nil, errors.Wrap(err, "failed to initialize helm configuration for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeClient, err := initUpgradeClient(actionConfig, upgradeOpts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to initialize helm upgrade client for helm release upgrade")
|
||||||
|
return nil, errors.Wrap(err, "failed to initialize helm upgrade client for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
values, err := hspm.GetHelmValuesFromFile(upgradeOpts.ValuesFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to get Helm values from file for helm release upgrade")
|
||||||
|
return nil, errors.Wrap(err, "failed to get Helm values from file for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
chart, err := hspm.loadAndValidateChartWithPathOptions(&upgradeClient.ChartPathOptions, upgradeOpts.Chart, upgradeOpts.Repo, upgradeClient.DependencyUpdate, "release upgrade")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to load and validate chart for helm release upgrade")
|
||||||
|
return nil, errors.Wrap(err, "failed to load and validate chart for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", upgradeOpts.Chart).
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Msg("Running chart upgrade for helm release")
|
||||||
|
|
||||||
|
helmRelease, err := upgradeClient.Run(upgradeOpts.Name, chart, values)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("context", "HelmClient").
|
||||||
|
Str("chart", upgradeOpts.Chart).
|
||||||
|
Str("name", upgradeOpts.Name).
|
||||||
|
Str("namespace", upgradeOpts.Namespace).
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to upgrade helm chart for helm release upgrade")
|
||||||
|
return nil, errors.Wrap(err, "helm was not able to upgrade the chart for helm release upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &release.Release{
|
||||||
|
Name: helmRelease.Name,
|
||||||
|
Namespace: helmRelease.Namespace,
|
||||||
|
Chart: release.Chart{
|
||||||
|
Metadata: &release.Metadata{
|
||||||
|
Name: helmRelease.Chart.Metadata.Name,
|
||||||
|
Version: helmRelease.Chart.Metadata.Version,
|
||||||
|
AppVersion: helmRelease.Chart.Metadata.AppVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Labels: helmRelease.Labels,
|
||||||
|
Version: helmRelease.Version,
|
||||||
|
Manifest: helmRelease.Manifest,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initUpgradeClient initializes the upgrade client with the given options
|
||||||
|
// and return the upgrade client.
|
||||||
|
func initUpgradeClient(actionConfig *action.Configuration, upgradeOpts options.InstallOptions) (*action.Upgrade, error) {
|
||||||
|
upgradeClient := action.NewUpgrade(actionConfig)
|
||||||
|
upgradeClient.DependencyUpdate = true
|
||||||
|
upgradeClient.Atomic = true
|
||||||
|
upgradeClient.ChartPathOptions.RepoURL = upgradeOpts.Repo
|
||||||
|
upgradeClient.Wait = upgradeOpts.Wait
|
||||||
|
|
||||||
|
// Set default values if not specified
|
||||||
|
if upgradeOpts.Timeout == 0 {
|
||||||
|
upgradeClient.Timeout = 5 * time.Minute
|
||||||
|
} else {
|
||||||
|
upgradeClient.Timeout = upgradeOpts.Timeout
|
||||||
|
}
|
||||||
|
if upgradeOpts.Namespace == "" {
|
||||||
|
upgradeOpts.Namespace = "default"
|
||||||
|
} else {
|
||||||
|
upgradeClient.Namespace = upgradeOpts.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
if upgradeOpts.PostRenderer != "" {
|
||||||
|
postRenderer, err := postrender.NewExec(upgradeOpts.PostRenderer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create post renderer")
|
||||||
|
}
|
||||||
|
upgradeClient.PostRenderer = postRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
return upgradeClient, nil
|
||||||
|
}
|
179
pkg/libhelm/sdk/upgrade_test.go
Normal file
179
pkg/libhelm/sdk/upgrade_test.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer/pkg/libhelm/options"
|
||||||
|
"github.com/portainer/portainer/pkg/libhelm/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpgrade(t *testing.T) {
|
||||||
|
test.EnsureIntegrationTest(t)
|
||||||
|
is := assert.New(t)
|
||||||
|
|
||||||
|
// Create a new SDK package manager
|
||||||
|
hspm := NewHelmSDKPackageManager()
|
||||||
|
|
||||||
|
t.Run("when no release exists, the chart should be installed", func(t *testing.T) {
|
||||||
|
// SDK equivalent of: helm upgrade --install test-new-nginx --repo https://kubernetes.github.io/ingress-nginx ingress-nginx
|
||||||
|
upgradeOpts := options.InstallOptions{
|
||||||
|
Name: "test-new-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the release doesn't exist before test
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: upgradeOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(upgradeOpts)
|
||||||
|
if release != nil {
|
||||||
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: upgradeOpts.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
is.NoError(err, "should successfully install release via upgrade")
|
||||||
|
is.NotNil(release, "should return non-nil release")
|
||||||
|
is.Equal(upgradeOpts.Name, release.Name, "release name should match")
|
||||||
|
is.Equal(1, release.Version, "release version should be 1 for new install")
|
||||||
|
is.NotEmpty(release.Manifest, "release manifest should not be empty")
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: upgradeOpts.Name,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when release exists, the chart should be upgraded", func(t *testing.T) {
|
||||||
|
// First install a release
|
||||||
|
installOpts := options.InstallOptions{
|
||||||
|
Name: "test-upgrade-nginx",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the release doesn't exist before test
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
is.NoError(err, "should successfully install release")
|
||||||
|
is.NotNil(release, "should return non-nil release")
|
||||||
|
|
||||||
|
// Upgrade the release with the same options
|
||||||
|
upgradedRelease, err := hspm.Upgrade(installOpts)
|
||||||
|
|
||||||
|
is.NoError(err, "should successfully upgrade release")
|
||||||
|
is.NotNil(upgradedRelease, "should return non-nil release")
|
||||||
|
is.Equal("test-upgrade-nginx", upgradedRelease.Name, "release name should match")
|
||||||
|
is.Equal(2, upgradedRelease.Version, "release version should be incremented to 2")
|
||||||
|
is.NotEmpty(upgradedRelease.Manifest, "release manifest should not be empty")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should be able to upgrade with override values", func(t *testing.T) {
|
||||||
|
// First install a release
|
||||||
|
installOpts := options.InstallOptions{
|
||||||
|
Name: "test-values-nginx",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the release doesn't exist before test
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts) // Cleanup
|
||||||
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
is.NoError(err, "should successfully install release")
|
||||||
|
is.NotNil(release, "should return non-nil release")
|
||||||
|
|
||||||
|
// Create values file
|
||||||
|
values, err := test.CreateValuesFile("service:\n port: 8083")
|
||||||
|
is.NoError(err, "should create a values file")
|
||||||
|
defer os.Remove(values)
|
||||||
|
|
||||||
|
// Now upgrade with values
|
||||||
|
upgradeOpts := options.InstallOptions{
|
||||||
|
Name: "test-values-nginx",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
ValuesFile: values,
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradedRelease, err := hspm.Upgrade(upgradeOpts)
|
||||||
|
|
||||||
|
is.NoError(err, "should successfully upgrade release with values")
|
||||||
|
is.NotNil(upgradedRelease, "should return non-nil release")
|
||||||
|
is.Equal("test-values-nginx", upgradedRelease.Name, "release name should match")
|
||||||
|
is.Equal(2, upgradedRelease.Version, "release version should be incremented to 2")
|
||||||
|
is.NotEmpty(upgradedRelease.Manifest, "release manifest should not be empty")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should give an error if the override values are invalid", func(t *testing.T) {
|
||||||
|
// First install a release
|
||||||
|
installOpts := options.InstallOptions{
|
||||||
|
Name: "test-invalid-values",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the release doesn't exist before test
|
||||||
|
hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
release, err := hspm.Upgrade(installOpts)
|
||||||
|
defer hspm.Uninstall(options.UninstallOptions{
|
||||||
|
Name: installOpts.Name,
|
||||||
|
})
|
||||||
|
is.NoError(err, "should successfully install release")
|
||||||
|
is.NotNil(release, "should return non-nil release")
|
||||||
|
|
||||||
|
// Create invalid values file
|
||||||
|
values, err := test.CreateValuesFile("this is not valid yaml")
|
||||||
|
is.NoError(err, "should create a values file")
|
||||||
|
defer os.Remove(values)
|
||||||
|
|
||||||
|
// Now upgrade with invalid values
|
||||||
|
upgradeOpts := options.InstallOptions{
|
||||||
|
Name: "test-invalid-values",
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
ValuesFile: values,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = hspm.Upgrade(upgradeOpts)
|
||||||
|
|
||||||
|
is.Error(err, "should return error with invalid values")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return error when name is not provided", func(t *testing.T) {
|
||||||
|
upgradeOpts := options.InstallOptions{
|
||||||
|
Chart: "ingress-nginx",
|
||||||
|
Namespace: "default",
|
||||||
|
Repo: "https://kubernetes.github.io/ingress-nginx",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := hspm.Upgrade(upgradeOpts)
|
||||||
|
|
||||||
|
is.Error(err, "should return an error when name is not provided")
|
||||||
|
is.Equal("name is required for helm release upgrade", err.Error(), "should return correct error message")
|
||||||
|
})
|
||||||
|
}
|
|
@ -73,6 +73,11 @@ func (hpm *helmMockPackageManager) Install(installOpts options.InstallOptions) (
|
||||||
return newMockRelease(releaseElement), nil
|
return newMockRelease(releaseElement), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upgrade a helm chart (not thread safe)
|
||||||
|
func (hpm *helmMockPackageManager) Upgrade(upgradeOpts options.InstallOptions) (*release.Release, error) {
|
||||||
|
return hpm.Install(upgradeOpts)
|
||||||
|
}
|
||||||
|
|
||||||
// Show values/readme/chart etc
|
// Show values/readme/chart etc
|
||||||
func (hpm *helmMockPackageManager) Show(showOpts options.ShowOptions) ([]byte, error) {
|
func (hpm *helmMockPackageManager) Show(showOpts options.ShowOptions) ([]byte, error) {
|
||||||
switch showOpts.OutputFormat {
|
switch showOpts.OutputFormat {
|
||||||
|
|
26
pkg/libhelm/test/values.go
Normal file
26
pkg/libhelm/test/values.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateValuesFile creates a temporary file with the given content for testing
|
||||||
|
func CreateValuesFile(values string) (string, error) {
|
||||||
|
file, err := os.CreateTemp("", "helm-values")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.WriteString(values)
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Name(), nil
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ type HelmPackageManager interface {
|
||||||
Show(showOpts options.ShowOptions) ([]byte, error)
|
Show(showOpts options.ShowOptions) ([]byte, error)
|
||||||
SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error)
|
SearchRepo(searchRepoOpts options.SearchRepoOptions) ([]byte, error)
|
||||||
List(listOpts options.ListOptions) ([]release.ReleaseElement, error)
|
List(listOpts options.ListOptions) ([]release.ReleaseElement, error)
|
||||||
Install(installOpts options.InstallOptions) (*release.Release, error)
|
Upgrade(upgradeOpts options.InstallOptions) (*release.Release, error)
|
||||||
Uninstall(uninstallOpts options.UninstallOptions) error
|
Uninstall(uninstallOpts options.UninstallOptions) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue