Skip to content

Commit

Permalink
Install/Uninstall helm Releases using both helmReleases and workloads…
Browse files Browse the repository at this point in the history
… API in parallel

Signed-off-by: Artiom Diomin <[email protected]>
  • Loading branch information
kron4eg committed May 23, 2024
1 parent a1f7753 commit 33fac8b
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 87 deletions.
30 changes: 30 additions & 0 deletions pkg/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

func File(st *state.State) (*os.File, func(), error) {
konfigBuf, err := Download(st)
if err != nil {
return nil, nil, err
}

tmpKubeConf, err := os.CreateTemp("", "kubeone-kubeconfig-*")
if err != nil {
return nil, nil, fail.Runtime(err, "creating temp file for helm kubeconfig")
}

cleanupFn := func() {
name := tmpKubeConf.Name()
tmpKubeConf.Close()
os.Remove(name)
}

n, err := tmpKubeConf.Write(konfigBuf)
if err != nil {
cleanupFn()
return nil, nil, fail.Runtime(err, "wring temp file for helm kubeconfig")
}
if n != len(konfigBuf) {
cleanupFn()
return nil, nil, fail.NewRuntimeError("incorrect number of bytes written to temp kubeconfig", "")
}

return tmpKubeConf, cleanupFn, nil
}

// Download downloads Kubeconfig over SSH
func Download(s *state.State) ([]byte, error) {
// connect to host
Expand Down
9 changes: 7 additions & 2 deletions pkg/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8c.io/kubeone/pkg/templates/externalccm"
"k8c.io/kubeone/pkg/templates/machinecontroller"
"k8c.io/kubeone/pkg/templates/operatingsystemmanager"
"k8c.io/kubeone/pkg/workloads"
"k8c.io/kubeone/pkg/workloads/addons"
"k8c.io/kubeone/pkg/workloads/localhelm"
)
Expand Down Expand Up @@ -265,7 +266,7 @@ func WithResources(t Tasks) Tasks {
Description: "ensure embedded addons",
},
{
Fn: nil,
Fn: workloads.Ensure,
Operation: "reconcile addons and helm releases",
},
{
Expand All @@ -276,7 +277,11 @@ func WithResources(t Tasks) Tasks {
},
{
Fn: localhelm.Deploy,
Operation: "releasing core helm charts",
Operation: "releasing helm charts",
},
{
Fn: localhelm.Uninstall,
Operation: "uninstall helm releases",
},
{
Fn: ensureCABundleConfigMap,
Expand Down
41 changes: 41 additions & 0 deletions pkg/workloads/ensure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package workloads

import (
"k8c.io/kubeone/pkg/kubeconfig"
"k8c.io/kubeone/pkg/state"
"k8c.io/kubeone/pkg/workloads/addons"
"k8c.io/kubeone/pkg/workloads/localhelm"
)

func Ensure(st *state.State) error {
if len(st.Cluster.Workloads) == 0 {
return nil
}

tmpKubeConf, cleanupFn, err := kubeconfig.File(st)
if err != nil {
return err
}
defer cleanupFn()

for _, wk := range st.Cluster.Workloads {
switch {
case wk.Addon != nil:
if err := addons.EnsureAddonByName(st, wk.Addon.Name); err != nil {
return err
}
case wk.HelmRelease != nil:
helmSettings := localhelm.NewHelmSettings(st.Verbose)
helmCfg, err := localhelm.NewActionConfiguration(helmSettings.Debug)
if err != nil {
return err
}

if err := localhelm.DeployRelease(st, *wk.HelmRelease, helmSettings, tmpKubeConf, helmCfg); err != nil {
return err
}
}
}

return nil
}
183 changes: 98 additions & 85 deletions pkg/workloads/localhelm/helm3.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"
"io"
"os"
"slices"
"sort"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -59,111 +60,88 @@ const (
)

func Deploy(st *state.State) error {
konfigBuf, err := kubeconfig.Download(st)
tmpKubeConf, cleanupFn, err := kubeconfig.File(st)
if err != nil {
return err
}
defer cleanupFn()

tmpKubeConf, err := os.CreateTemp("", "kubeone-kubeconfig-*")
if err != nil {
return fail.Runtime(err, "creating temp file for helm kubeconfig")
}
defer func() {
name := tmpKubeConf.Name()
tmpKubeConf.Close()
os.Remove(name)
}()

n, err := tmpKubeConf.Write(konfigBuf)
if err != nil {
return fail.Runtime(err, "wring temp file for helm kubeconfig")
}
if n != len(konfigBuf) {
return fail.NewRuntimeError("incorrect number of bytes written to temp kubeconfig", "")
}

helmSettings := newHelmSettings(st.Verbose)
helmCfg, err := newActionConfiguration(helmSettings.Debug)
if err != nil {
return err
}

kubeClient, err := kubernetes.NewForConfig(st.RESTConfig)
if err != nil {
return fail.Config(err, "init new kubernetes client")
}

// all namespaces
noNamespaceSecretsClient := kubeClient.CoreV1().Secrets("")
releasesToUninstall, err := driver.
NewSecrets(noNamespaceSecretsClient).
List(releasesFilterFn(st.Cluster.HelmReleases, st.Logger))
helmSettings := NewHelmSettings(st.Verbose)
helmCfg, err := NewActionConfiguration(helmSettings.Debug)
if err != nil {
return err
}

for _, release := range st.Cluster.HelmReleases {
var valueFiles []string
for _, value := range release.Values {
if value.ValuesFile != "" {
valueFiles = append(valueFiles, value.ValuesFile)
}
if err := DeployRelease(st, release, helmSettings, tmpKubeConf, helmCfg); err != nil {
return err
}
}

if value.Inline != nil {
inlineValues, errTmp := os.CreateTemp("", "inline-helm-values-*")
if errTmp != nil {
return fail.Runtime(errTmp, "creating temp file for helm inline values")
}
return nil
}

inlineValuesName := inlineValues.Name()
defer os.Remove(inlineValuesName)
func DeployRelease(st *state.State, release kubeoneapi.HelmRelease, helmSettings *helmcli.EnvSettings, kubeconfig *os.File, helmCfg *helmaction.Configuration) error {
var valueFiles []string
for _, value := range release.Values {
if value.ValuesFile != "" {
valueFiles = append(valueFiles, value.ValuesFile)
}

valuesBuf := bytes.NewBuffer(value.Inline)
_, err = io.Copy(inlineValues, valuesBuf)
if err != nil {
inlineValues.Close()
if value.Inline != nil {
inlineValues, errTmp := os.CreateTemp("", "inline-helm-values-*")
if errTmp != nil {
return fail.Runtime(errTmp, "creating temp file for helm inline values")
}

return fail.Runtime(err, "copying helm inline values to the temp file")
}
inlineValuesName := inlineValues.Name()
defer os.Remove(inlineValuesName)

valuesBuf := bytes.NewBuffer(value.Inline)
_, err := io.Copy(inlineValues, valuesBuf)
if err != nil {
inlineValues.Close()
valueFiles = append(valueFiles, inlineValuesName)

return fail.Runtime(err, "copying helm inline values to the temp file")
}
}

valueOpts := &values.Options{
ValueFiles: valueFiles,
}
providers := getter.All(helmSettings)
vals, errMerge := valueOpts.MergeValues(providers)
if errMerge != nil {
return fail.Runtime(errMerge, "merging helm values")
inlineValues.Close()
valueFiles = append(valueFiles, inlineValuesName)
}
}

restClientGetter := newRestClientGetter(tmpKubeConf.Name(), release.Namespace, st)
if err = helmCfg.Init(restClientGetter, release.Namespace, helmStorageDriver, st.Logger.Debugf); err != nil {
return fail.Runtime(err, "initializing helm action configuration")
}
valueOpts := &values.Options{
ValueFiles: valueFiles,
}
providers := getter.All(helmSettings)
vals, errMerge := valueOpts.MergeValues(providers)
if errMerge != nil {
return fail.Runtime(errMerge, "merging helm values")
}

histClient := helmaction.NewHistory(helmCfg)
histClient.Max = 1
existingReleases, err := histClient.Run(release.ReleaseName)
restClientGetter := newRestClientGetter(kubeconfig.Name(), release.Namespace, st)
if err := helmCfg.Init(restClientGetter, release.Namespace, helmStorageDriver, st.Logger.Debugf); err != nil {
return fail.Runtime(err, "initializing helm action configuration")
}

switch {
case errors.Is(err, driver.ErrReleaseNotFound):
if err = installRelease(st.Context, helmCfg, release, helmSettings, providers, st.DynamicClient, vals, st.Logger); err != nil {
return err
}
case err == nil:
if err = upgradeRelease(st.Context, helmCfg, release, helmSettings, providers, st.DynamicClient, vals, existingReleases, st.Logger); err != nil {
return err
}
default:
return fail.Runtime(err, "helm releases history")
histClient := helmaction.NewHistory(helmCfg)
histClient.Max = 1
existingReleases, err := histClient.Run(release.ReleaseName)

switch {
case errors.Is(err, driver.ErrReleaseNotFound):
if err = installRelease(st.Context, helmCfg, release, helmSettings, providers, st.DynamicClient, vals, st.Logger); err != nil {
return err
}
case err == nil:
if err = upgradeRelease(st.Context, helmCfg, release, helmSettings, providers, st.DynamicClient, vals, existingReleases, st.Logger); err != nil {
return err
}
default:
return fail.Runtime(err, "helm releases history")
}

return uninstallReleases(releasesToUninstall, helmCfg, tmpKubeConf.Name(), st)
return nil
}

func releasesFilterFn(helmReleases []kubeoneapi.HelmRelease, logger logrus.FieldLogger) func(rel *helmrelease.Release) bool {
Expand All @@ -183,7 +161,7 @@ func releasesFilterFn(helmReleases []kubeoneapi.HelmRelease, logger logrus.Field
}
}

func newHelmSettings(verbose bool) *helmcli.EnvSettings {
func NewHelmSettings(verbose bool) *helmcli.EnvSettings {
helmSettings := helmcli.New()
helmSettings.Debug = verbose

Expand Down Expand Up @@ -344,13 +322,48 @@ func runInstallRelease(
return rel, nil
}

func Uninstall(st *state.State) error {
tmpKubeConf, cleanupFn, err := kubeconfig.File(st)
if err != nil {
return err
}
defer cleanupFn()

helmCfg, err := NewActionConfiguration(st.Verbose)
if err != nil {
return err
}

return uninstallReleases(st, helmCfg, tmpKubeConf.Name())
}

func uninstallReleases(
toUninstall []*helmrelease.Release,
st *state.State,
helmCfg *helmaction.Configuration,
kubeconfPath string,
st *state.State,
) error {
logger := st.Logger

kubeClient, err := kubernetes.NewForConfig(st.RESTConfig)
if err != nil {
return fail.Config(err, "init new kubernetes client")
}

releasesToKeep := slices.Clone(st.Cluster.HelmReleases)

for _, wk := range st.Cluster.Workloads {
if wk.HelmRelease != nil {
releasesToKeep = append(releasesToKeep, *wk.HelmRelease)
}
}

// all namespaces
noNamespaceSecretsClient := kubeClient.CoreV1().Secrets("")
toUninstall, err := driver.NewSecrets(noNamespaceSecretsClient).List(releasesFilterFn(releasesToKeep, st.Logger))
if err != nil {
return err
}

for _, release := range toUninstall {
restClientGetter := newRestClientGetter(kubeconfPath, release.Namespace, st)
if err := helmCfg.Init(restClientGetter, release.Namespace, helmStorageDriver, logger.Debugf); err != nil {
Expand Down Expand Up @@ -456,7 +469,7 @@ func dependencyUpdate(chartPath string, helmSettings *helmcli.EnvSettings, provi
return chartRequested, nil
}

func newActionConfiguration(debug bool) (*helmaction.Configuration, error) {
func NewActionConfiguration(debug bool) (*helmaction.Configuration, error) {
registryClient, err := registry.NewClient(
registry.ClientOptDebug(debug),
registry.ClientOptEnableCache(true),
Expand Down

0 comments on commit 33fac8b

Please sign in to comment.