From 149fedb90fcdb0af3b56925bd24ba5dfefcf8781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= <66256922+daniel-weisse@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:59:13 +0200 Subject: [PATCH] cli: add `constellation apply` command to replace `init` and `upgrade apply` (#2484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add apply command * Mark init and upgrade apply as deprecated * Use apply command in CI * Add skippable phases for attestation config and cert SANs --------- Signed-off-by: Daniel Weiße --- .../actions/constellation_create/action.yml | 7 +- .github/workflows/e2e-windows.yml | 2 +- cli/cmd/root.go | 3 +- cli/internal/cmd/apply.go | 115 +++++++++++++++--- cli/internal/cmd/apply_test.go | 22 ++-- cli/internal/cmd/init.go | 1 + cli/internal/cmd/upgradeapply.go | 37 +----- docs/docs/reference/cli.md | 58 +++++++-- e2e/internal/upgrade/upgrade_test.go | 2 +- 9 files changed, 168 insertions(+), 79 deletions(-) diff --git a/.github/actions/constellation_create/action.yml b/.github/actions/constellation_create/action.yml index b6dab3450c..25815f6157 100644 --- a/.github/actions/constellation_create/action.yml +++ b/.github/actions/constellation_create/action.yml @@ -147,7 +147,12 @@ runs: id: constellation-init shell: bash run: | - constellation init --debug + # TODO(v2.14): Remove workaround for CLIs not supporting apply command + cmd=apply + if constellation --help | grep -q init; then + cmd=init + fi + constellation $cmd --debug echo "KUBECONFIG=$(pwd)/constellation-admin.conf" | tee -a $GITHUB_OUTPUT - name: Wait for nodes to join and become ready diff --git a/.github/workflows/e2e-windows.yml b/.github/workflows/e2e-windows.yml index 21bc39d159..50e161c10b 100644 --- a/.github/workflows/e2e-windows.yml +++ b/.github/workflows/e2e-windows.yml @@ -84,7 +84,7 @@ jobs: - name: Initialize cluster shell: pwsh run: | - .\constellation.exe init --debug + .\constellation.exe apply --debug - name: Liveness probe shell: pwsh diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 00478bbe16..c6e52f4f4d 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -51,7 +51,7 @@ func NewRootCmd() *cobra.Command { rootCmd.AddCommand(cmd.NewConfigCmd()) rootCmd.AddCommand(cmd.NewCreateCmd()) - rootCmd.AddCommand(cmd.NewInitCmd()) + rootCmd.AddCommand(cmd.NewApplyCmd()) rootCmd.AddCommand(cmd.NewMiniCmd()) rootCmd.AddCommand(cmd.NewStatusCmd()) rootCmd.AddCommand(cmd.NewVerifyCmd()) @@ -60,6 +60,7 @@ func NewRootCmd() *cobra.Command { rootCmd.AddCommand(cmd.NewTerminateCmd()) rootCmd.AddCommand(cmd.NewIAMCmd()) rootCmd.AddCommand(cmd.NewVersionCmd()) + rootCmd.AddCommand(cmd.NewInitCmd()) return rootCmd } diff --git a/cli/internal/cmd/apply.go b/cli/internal/cmd/apply.go index 937a5a1e1a..44a394d763 100644 --- a/cli/internal/cmd/apply.go +++ b/cli/internal/cmd/apply.go @@ -40,6 +40,85 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" ) +// phases that can be skipped during apply. +// New phases should also be added to [formatSkipPhases]. +const ( + // skipInfrastructurePhase skips the Terraform apply of the apply process. + skipInfrastructurePhase skipPhase = "infrastructure" + // skipInitPhase skips the init RPC of the apply process. + skipInitPhase skipPhase = "init" + // skipAttestationConfigPhase skips the attestation config upgrade of the apply process. + skipAttestationConfigPhase skipPhase = "attestationconfig" + // skipCertSANsPhase skips the cert SANs upgrade of the apply process. + skipCertSANsPhase skipPhase = "certsans" + // skipHelmPhase skips the helm upgrade of the apply process. + skipHelmPhase skipPhase = "helm" + // skipImagePhase skips the image upgrade of the apply process. + skipImagePhase skipPhase = "image" + // skipK8sPhase skips the Kubernetes version upgrade of the apply process. + skipK8sPhase skipPhase = "k8s" +) + +// formatSkipPhases returns a formatted string of all phases that can be skipped. +func formatSkipPhases() string { + return fmt.Sprintf("{ %s }", strings.Join([]string{ + string(skipInfrastructurePhase), + string(skipInitPhase), + string(skipAttestationConfigPhase), + string(skipCertSANsPhase), + string(skipHelmPhase), + string(skipImagePhase), + string(skipK8sPhase), + }, " | ")) +} + +// skipPhase is a phase of the upgrade process that can be skipped. +type skipPhase string + +// skipPhases is a list of phases that can be skipped during the upgrade process. +type skipPhases map[skipPhase]struct{} + +// contains returns true if the list of phases contains the given phase. +func (s skipPhases) contains(phase skipPhase) bool { + _, ok := s[skipPhase(strings.ToLower(string(phase)))] + return ok +} + +// add a phase to the list of phases. +func (s *skipPhases) add(phases ...skipPhase) { + if *s == nil { + *s = make(skipPhases) + } + for _, phase := range phases { + (*s)[skipPhase(strings.ToLower(string(phase)))] = struct{}{} + } +} + +// NewApplyCmd creates the apply command. +func NewApplyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "apply", + Short: "Apply a configuration to a Constellation cluster", + Long: "Apply a configuration to a Constellation cluster to initialize or upgrade the cluster.", + Args: cobra.NoArgs, + RunE: runApply, + } + + cmd.Flags().Bool("conformance", false, "enable conformance mode") + cmd.Flags().Bool("skip-helm-wait", false, "install helm charts without waiting for deployments to be ready") + cmd.Flags().Bool("merge-kubeconfig", false, "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config") + cmd.Flags().BoolP("yes", "y", false, "run command without further confirmation\n"+ + "WARNING: the command might delete or update existing resources without additional checks. Please read the docs.\n") + cmd.Flags().Duration("timeout", 5*time.Minute, "change helm upgrade timeout\n"+ + "Might be useful for slow connections or big clusters.") + cmd.Flags().StringSlice("skip-phases", nil, "comma-separated list of upgrade phases to skip\n"+ + fmt.Sprintf("one or multiple of %s", formatSkipPhases())) + + must(cmd.Flags().MarkHidden("timeout")) + + return cmd +} + // applyFlags defines the flags for the apply command. type applyFlags struct { rootFlags @@ -202,7 +281,7 @@ The control flow is as follows: │ │Not up to date │ │ │(Diff from Terraform plan)│ │ └────────────┐ │ - │ │ │Terraform + │ │ |Infrastructure │ ┌────────────▼──────────┐ │Phase │ │Apply Terraform updates│ │ │ └────────────┬──────────┘ │ @@ -228,14 +307,14 @@ The control flow is as follows: └──────────────┬───────────────┘ │ │ │ │ │ └───────────────┐ │ ───┘ - │ │ - ┌──────────▼──▼──────────┐ - │Apply Attestation Config│ - └─────────────┬──────────┘ - │ - ┌──────────────▼────────────┐ - │Extend API Server Cert SANs│ - └──────────────┬────────────┘ + │ │ ───┐ + ┌──────────▼──▼──────────┐ │AttestationConfig + │Apply Attestation Config│ │Phase + └─────────────┬──────────┘ ───┘ + │ ───┐ + ┌──────────────▼────────────┐ │CertSANs + │Extend API Server Cert SANs│ │Phase + └──────────────┬────────────┘ ───┘ │ ───┐ ┌──────────▼────────┐ │Helm │ Apply Helm Charts │ │Phase @@ -277,22 +356,26 @@ func (a *applyCmd) apply(cmd *cobra.Command, configFetcher attestationconfigapi. } // From now on we can assume a valid Kubernetes admin config file exists + a.log.Debugf("Creating Kubernetes client using %s", a.flags.pathPrefixer.PrefixPrintablePath(constants.AdminConfFilename)) kubeUpgrader, err := a.newKubeUpgrader(cmd.OutOrStdout(), constants.AdminConfFilename, a.log) if err != nil { return err } // Apply Attestation Config - a.log.Debugf("Creating Kubernetes client using %s", a.flags.pathPrefixer.PrefixPrintablePath(constants.AdminConfFilename)) - a.log.Debugf("Applying new attestation config to cluster") - if err := a.applyJoinConfig(cmd, kubeUpgrader, conf.GetAttestationConfig(), stateFile.ClusterValues.MeasurementSalt); err != nil { - return fmt.Errorf("applying attestation config: %w", err) + if !a.flags.skipPhases.contains(skipAttestationConfigPhase) { + a.log.Debugf("Applying new attestation config to cluster") + if err := a.applyJoinConfig(cmd, kubeUpgrader, conf.GetAttestationConfig(), stateFile.ClusterValues.MeasurementSalt); err != nil { + return fmt.Errorf("applying attestation config: %w", err) + } } // Extend API Server Cert SANs - sans := append([]string{stateFile.Infrastructure.ClusterEndpoint, conf.CustomEndpoint}, stateFile.Infrastructure.APIServerCertSANs...) - if err := kubeUpgrader.ExtendClusterConfigCertSANs(cmd.Context(), sans); err != nil { - return fmt.Errorf("extending cert SANs: %w", err) + if !a.flags.skipPhases.contains(skipCertSANsPhase) { + sans := append([]string{stateFile.Infrastructure.ClusterEndpoint, conf.CustomEndpoint}, stateFile.Infrastructure.APIServerCertSANs...) + if err := kubeUpgrader.ExtendClusterConfigCertSANs(cmd.Context(), sans); err != nil { + return fmt.Errorf("extending cert SANs: %w", err) + } } // Apply Helm Charts diff --git a/cli/internal/cmd/apply_test.go b/cli/internal/cmd/apply_test.go index 9218cd3cae..3942ad5a99 100644 --- a/cli/internal/cmd/apply_test.go +++ b/cli/internal/cmd/apply_test.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/internal/file" @@ -22,19 +23,13 @@ import ( func TestParseApplyFlags(t *testing.T) { require := require.New(t) - // TODO: Use flags := applyCmd().Flags() once we have a separate apply command defaultFlags := func() *pflag.FlagSet { - flags := pflag.NewFlagSet("test", pflag.ContinueOnError) + flags := NewApplyCmd().Flags() + // Register persistent flags flags.String("workspace", "", "") flags.String("tf-log", "NONE", "") flags.Bool("force", false, "") flags.Bool("debug", false, "") - flags.Bool("merge-kubeconfig", false, "") - flags.Bool("conformance", false, "") - flags.Bool("skip-helm-wait", false, "") - flags.Bool("yes", false, "") - flags.StringSlice("skip-phases", []string{}, "") - flags.Duration("timeout", 0, "") return flags } @@ -46,7 +41,8 @@ func TestParseApplyFlags(t *testing.T) { "default flags": { flags: defaultFlags(), wantFlags: applyFlags{ - helmWaitMode: helm.WaitModeAtomic, + helmWaitMode: helm.WaitModeAtomic, + upgradeTimeout: 5 * time.Minute, }, }, "skip phases": { @@ -56,8 +52,9 @@ func TestParseApplyFlags(t *testing.T) { return flags }(), wantFlags: applyFlags{ - skipPhases: skipPhases{skipHelmPhase: struct{}{}, skipK8sPhase: struct{}{}}, - helmWaitMode: helm.WaitModeAtomic, + skipPhases: skipPhases{skipHelmPhase: struct{}{}, skipK8sPhase: struct{}{}}, + helmWaitMode: helm.WaitModeAtomic, + upgradeTimeout: 5 * time.Minute, }, }, "skip helm wait": { @@ -67,7 +64,8 @@ func TestParseApplyFlags(t *testing.T) { return flags }(), wantFlags: applyFlags{ - helmWaitMode: helm.WaitModeNone, + helmWaitMode: helm.WaitModeNone, + upgradeTimeout: 5 * time.Minute, }, }, } diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index 44424f9181..67ca29c4ef 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -47,6 +47,7 @@ func NewInitCmd() *cobra.Command { cmd.Flags().Duration("timeout", time.Hour, "") return runApply(cmd, args) }, + Deprecated: "use 'constellation apply' instead.", } cmd.Flags().Bool("conformance", false, "enable conformance mode") cmd.Flags().Bool("skip-helm-wait", false, "install helm charts without waiting for deployments to be ready") diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index b9e9faf21c..8221e03691 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "io" - "strings" "time" "github.com/edgelesssys/constellation/v2/cli/internal/state" @@ -24,22 +23,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) -const ( - // skipInitPhase skips the init RPC of the apply process. - skipInitPhase skipPhase = "init" - // skipInfrastructurePhase skips the terraform apply of the upgrade process. - skipInfrastructurePhase skipPhase = "infrastructure" - // skipHelmPhase skips the helm upgrade of the upgrade process. - skipHelmPhase skipPhase = "helm" - // skipImagePhase skips the image upgrade of the upgrade process. - skipImagePhase skipPhase = "image" - // skipK8sPhase skips the k8s upgrade of the upgrade process. - skipK8sPhase skipPhase = "k8s" -) - -// skipPhase is a phase of the upgrade process that can be skipped. -type skipPhase string - func newUpgradeApplyCmd() *cobra.Command { cmd := &cobra.Command{ Use: "apply", @@ -51,6 +34,7 @@ func newUpgradeApplyCmd() *cobra.Command { cmd.Flags().Bool("merge-kubeconfig", false, "") return runApply(cmd, args) }, + Deprecated: "use 'constellation apply' instead.", } cmd.Flags().BoolP("yes", "y", false, "run upgrades without further confirmation\n"+ @@ -81,25 +65,6 @@ func diffAttestationCfg(currentAttestationCfg config.AttestationCfg, newAttestat return diff, nil } -// skipPhases is a list of phases that can be skipped during the upgrade process. -type skipPhases map[skipPhase]struct{} - -// contains returns true if the list of phases contains the given phase. -func (s skipPhases) contains(phase skipPhase) bool { - _, ok := s[skipPhase(strings.ToLower(string(phase)))] - return ok -} - -// add a phase to the list of phases. -func (s *skipPhases) add(phases ...skipPhase) { - if *s == nil { - *s = make(skipPhases) - } - for _, phase := range phases { - (*s)[skipPhase(strings.ToLower(string(phase)))] = struct{}{} - } -} - type kubernetesUpgrader interface { UpgradeNodeVersion(ctx context.Context, conf *config.Config, force, skipImage, skipK8s bool) error ExtendClusterConfigCertSANs(ctx context.Context, alternativeNames []string) error diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index a8556fc28b..6a3e7c4298 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -18,7 +18,7 @@ Commands: * [kubernetes-versions](#constellation-config-kubernetes-versions): Print the Kubernetes versions supported by this CLI * [migrate](#constellation-config-migrate): Migrate a configuration file to a new version * [create](#constellation-create): Create instances on a cloud platform for your Constellation cluster -* [init](#constellation-init): Initialize the Constellation cluster +* [apply](#constellation-apply): Apply a configuration to a Constellation cluster * [mini](#constellation-mini): Manage MiniConstellation clusters * [up](#constellation-mini-up): Create and initialize a new MiniConstellation cluster * [down](#constellation-mini-down): Destroy a MiniConstellation cluster @@ -38,6 +38,7 @@ Commands: * [upgrade](#constellation-iam-upgrade): Find and apply upgrades to your IAM profile * [apply](#constellation-iam-upgrade-apply): Apply an upgrade to an IAM profile * [version](#constellation-version): Display version of this CLI +* [init](#constellation-init): Initialize the Constellation cluster ## constellation config @@ -231,27 +232,30 @@ constellation create [flags] -C, --workspace string path to the Constellation workspace ``` -## constellation init +## constellation apply -Initialize the Constellation cluster +Apply a configuration to a Constellation cluster ### Synopsis -Initialize the Constellation cluster. - -Start your confidential Kubernetes. +Apply a configuration to a Constellation cluster to initialize or upgrade the cluster. ``` -constellation init [flags] +constellation apply [flags] ``` ### Options ``` - --conformance enable conformance mode - -h, --help help for init - --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config - --skip-helm-wait install helm charts without waiting for deployments to be ready + --conformance enable conformance mode + -h, --help help for apply + --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config + --skip-helm-wait install helm charts without waiting for deployments to be ready + --skip-phases strings comma-separated list of upgrade phases to skip + one or multiple of { infrastructure | init | attestationconfig | certsans | helm | image | k8s } + -y, --yes run command without further confirmation + WARNING: the command might delete or update existing resources without additional checks. Please read the docs. + ``` ### Options inherited from parent commands @@ -804,3 +808,35 @@ constellation version [flags] -C, --workspace string path to the Constellation workspace ``` +## constellation init + +Initialize the Constellation cluster + +### Synopsis + +Initialize the Constellation cluster. + +Start your confidential Kubernetes. + +``` +constellation init [flags] +``` + +### Options + +``` + --conformance enable conformance mode + -h, --help help for init + --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config + --skip-helm-wait install helm charts without waiting for deployments to be ready +``` + +### Options inherited from parent commands + +``` + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace +``` + diff --git a/e2e/internal/upgrade/upgrade_test.go b/e2e/internal/upgrade/upgrade_test.go index b72d252f83..d6cb9f4d8c 100644 --- a/e2e/internal/upgrade/upgrade_test.go +++ b/e2e/internal/upgrade/upgrade_test.go @@ -343,7 +343,7 @@ func runUpgradeApply(require *require.Assertions, cli string) { tfLogFlag = "--tf-log=DEBUG" } - cmd = exec.CommandContext(context.Background(), cli, "upgrade", "apply", "--debug", "--yes", tfLogFlag) + cmd = exec.CommandContext(context.Background(), cli, "apply", "--debug", "--yes", tfLogFlag) stdout, stderr, err = runCommandWithSeparateOutputs(cmd) require.NoError(err, "Stdout: %s\nStderr: %s", string(stdout), string(stderr)) require.NoError(containsUnexepectedMsg(string(stdout)))