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

cli: add microservice upgrades behind hidden flags #729

Merged
merged 11 commits into from
Dec 19, 2022
Merged
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
25 changes: 2 additions & 23 deletions bootstrapper/internal/helm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (h *Client) InstallConstellationServices(ctx context.Context, release helm.
h.ReleaseName = release.ReleaseName
h.Wait = release.Wait

mergedVals := mergeMaps(release.Values, extraVals)
mergedVals := helm.MergeMaps(release.Values, extraVals)

if err := h.install(ctx, release.Chart, mergedVals); err != nil {
return err
Expand All @@ -87,7 +87,7 @@ func (h *Client) InstallOperators(ctx context.Context, release helm.Release, ext
h.ReleaseName = release.ReleaseName
h.Wait = release.Wait

mergedVals := mergeMaps(release.Values, extraVals)
mergedVals := helm.MergeMaps(release.Values, extraVals)

if err := h.install(ctx, release.Chart, mergedVals); err != nil {
return err
Expand Down Expand Up @@ -190,24 +190,3 @@ func (h *Client) install(ctx context.Context, chartRaw []byte, values map[string
}
return nil
}

// mergeMaps returns a new map that is the merger of it's inputs.
// Taken from: https://github.com/helm/helm/blob/dbc6d8e20fe1d58d50e6ed30f09a04a77e4c68db/pkg/cli/values/options.go#L91-L108.
func mergeMaps(a, b map[string]any) map[string]any {
out := make(map[string]any, len(a))
for k, v := range a {
out[k] = v
}
for k, v := range b {
if v, ok := v.(map[string]any); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]any); ok {
out[k] = mergeMaps(bv, v)
continue
}
}
}
out[k] = v
}
return out
}
85 changes: 0 additions & 85 deletions bootstrapper/internal/helm/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,3 @@ SPDX-License-Identifier: AGPL-3.0-only
*/

package helm

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMe(t *testing.T) {
testCases := map[string]struct {
vals map[string]any
extraVals map[string]any
expected map[string]any
}{
"equal": {
vals: map[string]any{
"join-service": map[string]any{
"key1": "foo",
"key2": "bar",
},
},
extraVals: map[string]any{
"join-service": map[string]any{
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
},
expected: map[string]any{
"join-service": map[string]any{
"key1": "foo",
"key2": "bar",
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
},
},
"missing join-service extraVals": {
vals: map[string]any{
"join-service": map[string]any{
"key1": "foo",
"key2": "bar",
},
},
extraVals: map[string]any{
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
expected: map[string]any{
"join-service": map[string]any{
"key1": "foo",
"key2": "bar",
},
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
},
"missing join-service vals": {
vals: map[string]any{
"key1": "foo",
"key2": "bar",
},
extraVals: map[string]any{
"join-service": map[string]any{
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
},
expected: map[string]any{
"key1": "foo",
"key2": "bar",
"join-service": map[string]any{
"extraKey1": "extraFoo",
"extraKey2": "extraBar",
},
},
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
newVals := mergeMaps(tc.vals, tc.extraVals)
assert.Equal(tc.expected, newVals)
})
}
}
23 changes: 21 additions & 2 deletions cli/internal/cloudcmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import (
"errors"
"fmt"
"io"
"time"

"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
corev1 "k8s.io/api/core/v1"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -35,7 +38,7 @@ type Upgrader struct {
}

// NewUpgrader returns a new Upgrader.
func NewUpgrader(outWriter io.Writer) (*Upgrader, error) {
func NewUpgrader(outWriter io.Writer, log debugLog) (*Upgrader, error) {
kubeConfig, err := clientcmd.BuildConfigFromFlags("", constants.AdminConfFilename)
if err != nil {
return nil, fmt.Errorf("building kubernetes config: %w", err)
Expand All @@ -52,7 +55,12 @@ func NewUpgrader(outWriter io.Writer) (*Upgrader, error) {
return nil, fmt.Errorf("setting up custom resource client: %w", err)
}

helmClient, err := helm.NewClient(constants.AdminConfFilename, constants.HelmNamespace)
client, err := apiextensionsclient.NewForConfig(kubeConfig)
if err != nil {
return nil, err
}

helmClient, err := helm.NewClient(constants.AdminConfFilename, constants.HelmNamespace, client, log)
if err != nil {
return nil, fmt.Errorf("setting up helm client: %w", err)
}
Expand Down Expand Up @@ -105,6 +113,11 @@ func (u *Upgrader) GetCurrentImage(ctx context.Context) (*unstructured.Unstructu
return imageStruct, imageVersion, nil
}

// UpgradeHelmServices upgrade helm services.
func (u *Upgrader) UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration) error {
return u.helmClient.Upgrade(ctx, config, timeout)
}

// CurrentHelmVersion returns the version of the currently installed helm release.
func (u *Upgrader) CurrentHelmVersion(release string) (string, error) {
return u.helmClient.CurrentVersion(release)
Expand Down Expand Up @@ -232,4 +245,10 @@ func (u *stableClient) kubernetesVersion() (string, error) {

type helmInterface interface {
CurrentVersion(release string) (string, error)
Upgrade(ctx context.Context, config *config.Config, timeout time.Duration) error
}

type debugLog interface {
Debugf(format string, args ...any)
Sync()
}
35 changes: 34 additions & 1 deletion cli/internal/cmd/upgradeexecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package cmd

import (
"context"
"fmt"
"time"

"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
Expand All @@ -27,13 +29,28 @@ func newUpgradeExecuteCmd() *cobra.Command {
RunE: runUpgradeExecute,
}

cmd.Flags().Bool("helm", false, "Execute helm upgrade. This feature is still in development an may change without anounncement. Upgrades all helm charts deployed during constellation-init.")
cmd.Flags().Duration("timeout", 3*time.Minute, "Change helm upgrade timeout. This feature is still in development an may change without anounncement. Might be useful for slow connections or big clusters.")
if err := cmd.Flags().MarkHidden("helm"); err != nil {
panic(err)
}
if err := cmd.Flags().MarkHidden("timeout"); err != nil {
panic(err)
}

return cmd
}

func runUpgradeExecute(cmd *cobra.Command, args []string) error {
log, err := newCLILogger(cmd)
if err != nil {
return fmt.Errorf("creating logger: %w", err)
}
defer log.Sync()

fileHandler := file.NewHandler(afero.NewOsFs())
imageFetcher := image.New()
upgrader, err := cloudcmd.NewUpgrader(cmd.OutOrStdout())
upgrader, err := cloudcmd.NewUpgrader(cmd.OutOrStdout(), log)
if err != nil {
return err
}
Expand All @@ -51,6 +68,21 @@ func upgradeExecute(cmd *cobra.Command, imageFetcher imageFetcher, upgrader clou
return displayConfigValidationErrors(cmd.ErrOrStderr(), err)
}

helm, err := cmd.Flags().GetBool("helm")
if err != nil {
return err
}
if helm {
timeout, err := cmd.Flags().GetDuration("timeout")
if err != nil {
return err
}
if err := upgrader.UpgradeHelmServices(cmd.Context(), conf, timeout); err != nil {
return fmt.Errorf("upgrading helm: %w", err)
}
return nil
}

// TODO: validate upgrade config? Should be basic things like checking image is not an empty string
// More sophisticated validation, like making sure we don't downgrade the cluster, should be done by `constellation upgrade plan`

Expand All @@ -66,6 +98,7 @@ func upgradeExecute(cmd *cobra.Command, imageFetcher imageFetcher, upgrader clou

type cloudUpgrader interface {
Upgrade(ctx context.Context, imageReference, imageVersion string, measurements measurements.M) error
UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration) error
}

type imageFetcher interface {
Expand Down
8 changes: 7 additions & 1 deletion cli/internal/cmd/upgradeexecute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"errors"
"testing"
"time"

"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
Expand Down Expand Up @@ -66,13 +67,18 @@ func TestUpgradeExecute(t *testing.T) {
}

type stubUpgrader struct {
err error
err error
helmErr error
}

func (u stubUpgrader) Upgrade(context.Context, string, string, measurements.M) error {
return u.err
}

func (u stubUpgrader) UpgradeHelmServices(ctx context.Context, config *config.Config, timeout time.Duration) error {
return u.helmErr
}

type stubImageFetcher struct {
reference string
fetchReferenceErr error
Expand Down
8 changes: 7 additions & 1 deletion cli/internal/cmd/upgradeplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ func newUpgradePlanCmd() *cobra.Command {
}

func runUpgradePlan(cmd *cobra.Command, args []string) error {
log, err := newCLILogger(cmd)
if err != nil {
return fmt.Errorf("creating logger: %w", err)
}
defer log.Sync()

fileHandler := file.NewHandler(afero.NewOsFs())
flags, err := parseUpgradePlanFlags(cmd)
if err != nil {
return err
}
planner, err := cloudcmd.NewUpgrader(cmd.OutOrStdout())
planner, err := cloudcmd.NewUpgrader(cmd.OutOrStdout(), log)
if err != nil {
return err
}
Expand Down
23 changes: 23 additions & 0 deletions cli/internal/helm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Chart upgrades

All services that are installed via helm-install are upgraded via helm-upgrade.
Two aspects are not full covered by running helm-upgrade: CRDs and values.
While helm-install can install CRDs if they are contained in a chart's `crds` folder, upgrade won't change any installed CRDs.
Furthermore, new values introduced with a new version of a chart will not be installed into the cluster if the `--reuse-values` flag is set.
Nevertheless, we have to rely on the values already present in the cluster because some of the values are set by the bootstrapper during installation.
Because upgrades should be a CLI-only operation and we want to avoid the behaviour of `--reuse-values`, we fetch the cluster values and merge them with any new values.

Here is how we manage CRD upgrades for each chart.

## Cilium
- CRDs are updated by cilium-operator.

## cert-manager
- installCRDs flag is set during upgrade. This flag is managed by cert-manager. cert-manager is in charge of correctly upgrading the CRDs.
- WARNING: upgrading cert-manager might break other installations of cert-manager in the cluster, if those other installation are not on the same version as the Constellation-manager installation. This is due to the cluster-wide CRDs.

## Operators
- Manually update CRDs before upgrading the chart. Update by running applying the CRDs found in the `operators/crds/` folder.

## Constellation-services
- There currently are no CRDs in this chart.
Loading