mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-21 20:54:24 -04:00
cli: unify chart value setup (#2153)
This commit is contained in:
parent
5119d843f1
commit
70ce195a5f
10 changed files with 310 additions and 297 deletions
|
@ -37,6 +37,7 @@ import (
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||||
|
@ -78,6 +79,11 @@ type initCmd struct {
|
||||||
masterSecret uri.MasterSecret
|
masterSecret uri.MasterSecret
|
||||||
fh *file.Handler
|
fh *file.Handler
|
||||||
helmInstaller helm.Initializer
|
helmInstaller helm.Initializer
|
||||||
|
tfClient showClusterer
|
||||||
|
}
|
||||||
|
|
||||||
|
type showClusterer interface {
|
||||||
|
ShowCluster(ctx context.Context, provider cloudprovider.Provider) (terraform.ApplyOutput, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runInitialize runs the initialize command.
|
// runInitialize runs the initialize command.
|
||||||
|
@ -105,7 +111,11 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating Helm installer: %w", err)
|
return fmt.Errorf("creating Helm installer: %w", err)
|
||||||
}
|
}
|
||||||
i := &initCmd{log: log, spinner: spinner, merger: &kubeconfigMerger{log: log}, fh: &fileHandler, helmInstaller: helmInstaller}
|
tfClient, err := terraform.New(ctx, constants.TerraformWorkingDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating Terraform client: %w", err)
|
||||||
|
}
|
||||||
|
i := &initCmd{log: log, spinner: spinner, merger: &kubeconfigMerger{log: log}, fh: &fileHandler, helmInstaller: helmInstaller, tfClient: tfClient}
|
||||||
fetcher := attestationconfigapi.NewFetcher()
|
fetcher := attestationconfigapi.NewFetcher()
|
||||||
return i.initialize(cmd, newDialer, fileHandler, license.NewClient(), fetcher)
|
return i.initialize(cmd, newDialer, fileHandler, license.NewClient(), fetcher)
|
||||||
}
|
}
|
||||||
|
@ -183,17 +193,6 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V
|
||||||
clusterName := clusterid.GetClusterName(conf, idFile)
|
clusterName := clusterid.GetClusterName(conf, idFile)
|
||||||
i.log.Debugf("Setting cluster name to %s", clusterName)
|
i.log.Debugf("Setting cluster name to %s", clusterName)
|
||||||
|
|
||||||
helmLoader := helm.NewLoader(provider, k8sVersion, clusterName)
|
|
||||||
i.log.Debugf("Created new Helm loader")
|
|
||||||
releases, err := helmLoader.LoadReleases(conf, flags.conformance, flags.helmWaitMode, masterSecret.Key, masterSecret.Salt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading Helm charts: %w", err)
|
|
||||||
}
|
|
||||||
i.log.Debugf("Loaded Helm deployments")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading Helm charts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.PrintErrln("Note: If you just created the cluster, it can take a few minutes to connect.")
|
cmd.PrintErrln("Note: If you just created the cluster, it can take a few minutes to connect.")
|
||||||
i.spinner.Start("Connecting ", false)
|
i.spinner.Start("Connecting ", false)
|
||||||
req := &initproto.InitRequest{
|
req := &initproto.InitRequest{
|
||||||
|
@ -230,7 +229,22 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := i.helmInstaller.Install(cmd.Context(), provider, masterSecret, idFile, serviceAccURI, releases); err != nil {
|
|
||||||
|
helmLoader := helm.NewLoader(provider, k8sVersion, clusterName)
|
||||||
|
i.log.Debugf("Created new Helm loader")
|
||||||
|
output, err := i.tfClient.ShowCluster(cmd.Context(), conf.GetProvider())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting Terraform output: %w", err)
|
||||||
|
}
|
||||||
|
releases, err := helmLoader.LoadReleases(conf, flags.conformance, flags.helmWaitMode, masterSecret.Key, masterSecret.Salt, serviceAccURI, idFile, output)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading Helm charts: %w", err)
|
||||||
|
}
|
||||||
|
i.log.Debugf("Loaded Helm deployments")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading Helm charts: %w", err)
|
||||||
|
}
|
||||||
|
if err := i.helmInstaller.Install(cmd.Context(), releases); err != nil {
|
||||||
return fmt.Errorf("installing Helm charts: %w", err)
|
return fmt.Errorf("installing Helm charts: %w", err)
|
||||||
}
|
}
|
||||||
cmd.Println(bufferedOutput.String())
|
cmd.Println(bufferedOutput.String())
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
|
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||||
|
@ -54,7 +55,16 @@ func TestInitArgumentValidation(t *testing.T) {
|
||||||
|
|
||||||
func TestInitialize(t *testing.T) {
|
func TestInitialize(t *testing.T) {
|
||||||
gcpServiceAccKey := &gcpshared.ServiceAccountKey{
|
gcpServiceAccKey := &gcpshared.ServiceAccountKey{
|
||||||
Type: "service_account",
|
Type: "service_account",
|
||||||
|
ProjectID: "project_id",
|
||||||
|
PrivateKeyID: "key_id",
|
||||||
|
PrivateKey: "key",
|
||||||
|
ClientEmail: "client_email",
|
||||||
|
ClientID: "client_id",
|
||||||
|
AuthURI: "auth_uri",
|
||||||
|
TokenURI: "token_uri",
|
||||||
|
AuthProviderX509CertURL: "cert",
|
||||||
|
ClientX509CertURL: "client_cert",
|
||||||
}
|
}
|
||||||
testInitResp := &initproto.InitSuccessResponse{
|
testInitResp := &initproto.InitSuccessResponse{
|
||||||
Kubeconfig: []byte("kubeconfig"),
|
Kubeconfig: []byte("kubeconfig"),
|
||||||
|
@ -175,7 +185,7 @@ func TestInitialize(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}, helmInstaller: &stubHelmInstaller{}}
|
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}, helmInstaller: &stubHelmInstaller{}, tfClient: &stubShowCluster{}}
|
||||||
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, stubAttestationFetcher{})
|
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, stubAttestationFetcher{})
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
|
@ -670,9 +680,19 @@ func (c stubInitClient) Recv() (*initproto.InitResponse, error) {
|
||||||
|
|
||||||
type stubHelmInstaller struct{}
|
type stubHelmInstaller struct{}
|
||||||
|
|
||||||
func (i *stubHelmInstaller) Install(_ context.Context, _ cloudprovider.Provider, _ uri.MasterSecret,
|
func (i *stubHelmInstaller) Install(_ context.Context, _ *helm.Releases) error {
|
||||||
_ clusterid.File,
|
|
||||||
_ string, _ *helm.Releases,
|
|
||||||
) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubShowCluster struct{}
|
||||||
|
|
||||||
|
func (s *stubShowCluster) ShowCluster(_ context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) {
|
||||||
|
res := terraform.ApplyOutput{}
|
||||||
|
switch csp {
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
res.Azure = &terraform.AzureApplyOutput{}
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
res.GCP = &terraform.GCPApplyOutput{}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ go_library(
|
||||||
"init.go",
|
"init.go",
|
||||||
"install.go",
|
"install.go",
|
||||||
"loader.go",
|
"loader.go",
|
||||||
|
"overrides.go",
|
||||||
"release.go",
|
"release.go",
|
||||||
"serviceversion.go",
|
"serviceversion.go",
|
||||||
"upgrade.go",
|
"upgrade.go",
|
||||||
|
@ -430,7 +431,6 @@ go_library(
|
||||||
"//internal/config",
|
"//internal/config",
|
||||||
"//internal/constants",
|
"//internal/constants",
|
||||||
"//internal/file",
|
"//internal/file",
|
||||||
"//internal/kms/uri",
|
|
||||||
"//internal/retry",
|
"//internal/retry",
|
||||||
"//internal/semver",
|
"//internal/semver",
|
||||||
"//internal/versions",
|
"//internal/versions",
|
||||||
|
@ -449,7 +449,6 @@ go_library(
|
||||||
"@sh_helm_helm_v3//pkg/action",
|
"@sh_helm_helm_v3//pkg/action",
|
||||||
"@sh_helm_helm_v3//pkg/chart",
|
"@sh_helm_helm_v3//pkg/chart",
|
||||||
"@sh_helm_helm_v3//pkg/chart/loader",
|
"@sh_helm_helm_v3//pkg/chart/loader",
|
||||||
"@sh_helm_helm_v3//pkg/chartutil",
|
|
||||||
"@sh_helm_helm_v3//pkg/cli",
|
"@sh_helm_helm_v3//pkg/cli",
|
||||||
"@sh_helm_helm_v3//pkg/release",
|
"@sh_helm_helm_v3//pkg/release",
|
||||||
],
|
],
|
||||||
|
@ -467,9 +466,12 @@ go_test(
|
||||||
embed = [":helm"],
|
embed = [":helm"],
|
||||||
deps = [
|
deps = [
|
||||||
"//cli/internal/clusterid",
|
"//cli/internal/clusterid",
|
||||||
|
"//cli/internal/terraform",
|
||||||
"//internal/attestation/idkeydigest",
|
"//internal/attestation/idkeydigest",
|
||||||
"//internal/attestation/measurements",
|
"//internal/attestation/measurements",
|
||||||
|
"//internal/cloud/azureshared",
|
||||||
"//internal/cloud/cloudprovider",
|
"//internal/cloud/cloudprovider",
|
||||||
|
"//internal/cloud/gcpshared",
|
||||||
"//internal/compatibility",
|
"//internal/compatibility",
|
||||||
"//internal/config",
|
"//internal/config",
|
||||||
"//internal/file",
|
"//internal/file",
|
||||||
|
@ -485,7 +487,6 @@ go_test(
|
||||||
"@io_k8s_apimachinery//pkg/runtime/schema",
|
"@io_k8s_apimachinery//pkg/runtime/schema",
|
||||||
"@io_k8s_sigs_yaml//:yaml",
|
"@io_k8s_sigs_yaml//:yaml",
|
||||||
"@sh_helm_helm_v3//pkg/chart",
|
"@sh_helm_helm_v3//pkg/chart",
|
||||||
"@sh_helm_helm_v3//pkg/chart/loader",
|
|
||||||
"@sh_helm_helm_v3//pkg/chartutil",
|
"@sh_helm_helm_v3//pkg/chartutil",
|
||||||
"@sh_helm_helm_v3//pkg/engine",
|
"@sh_helm_helm_v3//pkg/engine",
|
||||||
"@sh_helm_helm_v3//pkg/release",
|
"@sh_helm_helm_v3//pkg/release",
|
||||||
|
|
|
@ -7,27 +7,15 @@ package helm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initializer installs all Helm charts required for a constellation cluster.
|
// Initializer installs all Helm charts required for a constellation cluster.
|
||||||
type Initializer interface {
|
type Initializer interface {
|
||||||
Install(ctx context.Context, provider cloudprovider.Provider, masterSecret uri.MasterSecret,
|
Install(ctx context.Context, releases *Releases) error
|
||||||
idFile clusterid.File,
|
|
||||||
serviceAccURI string, releases *Releases,
|
|
||||||
) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type initializationClient struct {
|
type initializationClient struct {
|
||||||
|
@ -45,20 +33,9 @@ func NewInitializer(log debugLog) (Initializer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install installs all Helm charts required for a constellation cluster.
|
// Install installs all Helm charts required for a constellation cluster.
|
||||||
func (h initializationClient) Install(ctx context.Context, provider cloudprovider.Provider, masterSecret uri.MasterSecret,
|
func (h initializationClient) Install(ctx context.Context, releases *Releases,
|
||||||
idFile clusterid.File,
|
|
||||||
serviceAccURI string, releases *Releases,
|
|
||||||
) error {
|
) error {
|
||||||
tfClient, err := terraform.New(ctx, constants.TerraformWorkingDir)
|
if err := h.installer.InstallChart(ctx, releases.Cilium); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating Terraform client: %w", err)
|
|
||||||
}
|
|
||||||
output, err := tfClient.ShowCluster(ctx, provider)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting Terraform output: %w", err)
|
|
||||||
}
|
|
||||||
ciliumVals := setupCiliumVals(provider, output)
|
|
||||||
if err := h.installer.InstallChartWithValues(ctx, releases.Cilium, ciliumVals); err != nil {
|
|
||||||
return fmt.Errorf("installing Cilium: %w", err)
|
return fmt.Errorf("installing Cilium: %w", err)
|
||||||
}
|
}
|
||||||
h.log.Debugf("Waiting for Cilium to become ready")
|
h.log.Debugf("Waiting for Cilium to become ready")
|
||||||
|
@ -81,11 +58,7 @@ func (h initializationClient) Install(ctx context.Context, provider cloudprovide
|
||||||
}
|
}
|
||||||
|
|
||||||
h.log.Debugf("Installing microservices")
|
h.log.Debugf("Installing microservices")
|
||||||
serviceVals, err := setupMicroserviceVals(provider, masterSecret.Salt, idFile.UID, serviceAccURI, output)
|
if err := h.installer.InstallChart(ctx, releases.ConstellationServices); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting up microservice values: %w", err)
|
|
||||||
}
|
|
||||||
if err := h.installer.InstallChartWithValues(ctx, releases.ConstellationServices, serviceVals); err != nil {
|
|
||||||
return fmt.Errorf("installing microservices: %w", err)
|
return fmt.Errorf("installing microservices: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,22 +68,8 @@ func (h initializationClient) Install(ctx context.Context, provider cloudprovide
|
||||||
}
|
}
|
||||||
|
|
||||||
if releases.CSI != nil {
|
if releases.CSI != nil {
|
||||||
var csiVals map[string]any
|
|
||||||
if provider == cloudprovider.OpenStack {
|
|
||||||
creds, err := openstack.AccountKeyFromURI(serviceAccURI)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cinderIni := creds.CloudINI().CinderCSIConfiguration()
|
|
||||||
csiVals = map[string]any{
|
|
||||||
"cinder-config": map[string]any{
|
|
||||||
"secretData": cinderIni,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.log.Debugf("Installing CSI deployments")
|
h.log.Debugf("Installing CSI deployments")
|
||||||
if err := h.installer.InstallChartWithValues(ctx, *releases.CSI, csiVals); err != nil {
|
if err := h.installer.InstallChart(ctx, *releases.CSI); err != nil {
|
||||||
return fmt.Errorf("installing CSI snapshot CRDs: %w", err)
|
return fmt.Errorf("installing CSI snapshot CRDs: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,8 +82,7 @@ func (h initializationClient) Install(ctx context.Context, provider cloudprovide
|
||||||
}
|
}
|
||||||
|
|
||||||
h.log.Debugf("Installing constellation operators")
|
h.log.Debugf("Installing constellation operators")
|
||||||
operatorVals := setupOperatorVals(ctx, idFile.UID)
|
if err := h.installer.InstallChart(ctx, releases.ConstellationOperators); err != nil {
|
||||||
if err := h.installer.InstallChartWithValues(ctx, releases.ConstellationOperators, operatorVals); err != nil {
|
|
||||||
return fmt.Errorf("installing constellation operators: %w", err)
|
return fmt.Errorf("installing constellation operators: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -136,82 +94,6 @@ type installer interface {
|
||||||
InstallChartWithValues(ctx context.Context, release Release, extraValues map[string]any) error
|
InstallChartWithValues(ctx context.Context, release Release, extraValues map[string]any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(malt3): switch over to DNS name on AWS and Azure
|
|
||||||
// soon as every apiserver certificate of every control-plane node
|
|
||||||
// has the dns endpoint in its SAN list.
|
|
||||||
func setupCiliumVals(provider cloudprovider.Provider, output terraform.ApplyOutput) map[string]any {
|
|
||||||
vals := map[string]any{
|
|
||||||
"k8sServiceHost": output.IP,
|
|
||||||
"k8sServicePort": constants.KubernetesPort,
|
|
||||||
}
|
|
||||||
if provider == cloudprovider.GCP {
|
|
||||||
vals["ipv4NativeRoutingCIDR"] = output.GCP.IPCidrPod
|
|
||||||
vals["strictModeCIDR"] = output.GCP.IPCidrPod
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupMicroserviceVals returns the values for the microservice chart.
|
|
||||||
func setupMicroserviceVals(provider cloudprovider.Provider, measurementSalt []byte, uid, serviceAccURI string, output terraform.ApplyOutput) (map[string]any, error) {
|
|
||||||
extraVals := map[string]any{
|
|
||||||
"join-service": map[string]any{
|
|
||||||
"measurementSalt": base64.StdEncoding.EncodeToString(measurementSalt),
|
|
||||||
},
|
|
||||||
"verification-service": map[string]any{
|
|
||||||
"loadBalancerIP": output.IP,
|
|
||||||
},
|
|
||||||
"konnectivity": map[string]any{
|
|
||||||
"loadBalancerIP": output.IP,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
switch provider {
|
|
||||||
case cloudprovider.GCP:
|
|
||||||
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(serviceAccURI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting service account key: %w", err)
|
|
||||||
}
|
|
||||||
rawKey, err := json.Marshal(serviceAccountKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("marshaling service account key: %w", err)
|
|
||||||
}
|
|
||||||
if output.GCP == nil {
|
|
||||||
return nil, fmt.Errorf("no GCP output from Terraform")
|
|
||||||
}
|
|
||||||
extraVals["ccm"] = map[string]any{
|
|
||||||
"GCP": map[string]any{
|
|
||||||
"projectID": output.GCP.ProjectID,
|
|
||||||
"uid": uid,
|
|
||||||
"secretData": string(rawKey),
|
|
||||||
"subnetworkPodCIDR": output.GCP.IPCidrPod,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case cloudprovider.Azure:
|
|
||||||
if output.Azure == nil {
|
|
||||||
return nil, fmt.Errorf("no Azure output from Terraform")
|
|
||||||
}
|
|
||||||
ccmConfig, err := getCCMConfig(*output.Azure, serviceAccURI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting Azure CCM config: %w", err)
|
|
||||||
}
|
|
||||||
extraVals["ccm"] = map[string]any{
|
|
||||||
"Azure": map[string]any{
|
|
||||||
"azureConfig": string(ccmConfig),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return extraVals, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupOperatorVals returns the values for the constellation-operator chart.
|
|
||||||
func setupOperatorVals(_ context.Context, uid string) map[string]any {
|
|
||||||
return map[string]any{
|
|
||||||
"constellation-operator": map[string]any{
|
|
||||||
"constellationUID": uid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type cloudConfig struct {
|
type cloudConfig struct {
|
||||||
Cloud string `json:"cloud,omitempty"`
|
Cloud string `json:"cloud,omitempty"`
|
||||||
TenantID string `json:"tenantId,omitempty"`
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
@ -231,28 +113,3 @@ type cloudConfig struct {
|
||||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,omitempty"`
|
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,omitempty"`
|
||||||
UserAssignedIdentityID string `json:"userAssignedIdentityID,omitempty"`
|
UserAssignedIdentityID string `json:"userAssignedIdentityID,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCCMConfig returns the configuration needed for the Kubernetes Cloud Controller Manager on Azure.
|
|
||||||
func getCCMConfig(tfOutput terraform.AzureApplyOutput, serviceAccURI string) ([]byte, error) {
|
|
||||||
creds, err := azureshared.ApplicationCredentialsFromURI(serviceAccURI)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting service account key: %w", err)
|
|
||||||
}
|
|
||||||
useManagedIdentityExtension := creds.PreferredAuthMethod == azureshared.AuthMethodUserAssignedIdentity
|
|
||||||
config := cloudConfig{
|
|
||||||
Cloud: "AzurePublicCloud",
|
|
||||||
TenantID: creds.TenantID,
|
|
||||||
SubscriptionID: tfOutput.SubscriptionID,
|
|
||||||
ResourceGroup: tfOutput.ResourceGroup,
|
|
||||||
LoadBalancerSku: "standard",
|
|
||||||
SecurityGroupName: tfOutput.NetworkSecurityGroupName,
|
|
||||||
LoadBalancerName: tfOutput.LoadBalancerName,
|
|
||||||
UseInstanceMetadata: true,
|
|
||||||
VMType: "vmss",
|
|
||||||
Location: creds.Location,
|
|
||||||
UseManagedIdentityExtension: useManagedIdentityExtension,
|
|
||||||
UserAssignedIdentityID: tfOutput.UserAssignedIdentity,
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(config)
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
package helm
|
package helm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,7 +16,6 @@ import (
|
||||||
"github.com/edgelesssys/constellation/v2/internal/retry"
|
"github.com/edgelesssys/constellation/v2/internal/retry"
|
||||||
"helm.sh/helm/v3/pkg/action"
|
"helm.sh/helm/v3/pkg/action"
|
||||||
"helm.sh/helm/v3/pkg/chart"
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
|
||||||
"helm.sh/helm/v3/pkg/cli"
|
"helm.sh/helm/v3/pkg/cli"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
)
|
)
|
||||||
|
@ -79,7 +77,7 @@ func (h *Installer) InstallChartWithValues(ctx context.Context, release Release,
|
||||||
// install tries to install the given chart and aborts after ~5 tries.
|
// install tries to install the given chart and aborts after ~5 tries.
|
||||||
// The function will wait 30 seconds before retrying a failed installation attempt.
|
// The function will wait 30 seconds before retrying a failed installation attempt.
|
||||||
// After 3 tries, the retrier will be canceled and the function returns with an error.
|
// After 3 tries, the retrier will be canceled and the function returns with an error.
|
||||||
func (h *Installer) install(ctx context.Context, chartRaw []byte, values map[string]any) error {
|
func (h *Installer) install(ctx context.Context, chart *chart.Chart, values map[string]any) error {
|
||||||
var retries int
|
var retries int
|
||||||
retriable := func(err error) bool {
|
retriable := func(err error) bool {
|
||||||
// abort after maximumRetryAttempts tries.
|
// abort after maximumRetryAttempts tries.
|
||||||
|
@ -97,13 +95,6 @@ func (h *Installer) install(ctx context.Context, chartRaw []byte, values map[str
|
||||||
return wait.Interrupted(err) ||
|
return wait.Interrupted(err) ||
|
||||||
strings.Contains(err.Error(), "connection refused")
|
strings.Contains(err.Error(), "connection refused")
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bytes.NewReader(chartRaw)
|
|
||||||
chart, err := loader.LoadArchive(reader)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("helm load archive: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
doer := installDoer{
|
doer := installDoer{
|
||||||
h,
|
h,
|
||||||
chart,
|
chart,
|
||||||
|
|
|
@ -9,11 +9,8 @@ package helm
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -21,9 +18,10 @@ import (
|
||||||
"helm.sh/helm/pkg/ignore"
|
"helm.sh/helm/pkg/ignore"
|
||||||
"helm.sh/helm/v3/pkg/chart"
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
"helm.sh/helm/v3/pkg/chartutil"
|
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/helm/imageversion"
|
"github.com/edgelesssys/constellation/v2/cli/internal/helm/imageversion"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
@ -108,12 +106,13 @@ func NewLoader(csp cloudprovider.Provider, k8sVersion versions.ValidK8sVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadReleases loads the embedded helm charts and returns them as a HelmReleases object.
|
// LoadReleases loads the embedded helm charts and returns them as a HelmReleases object.
|
||||||
func (i *ChartLoader) LoadReleases(config *config.Config, conformanceMode bool, helmWaitMode WaitMode, masterSecret, salt []byte) (*Releases, error) {
|
func (i *ChartLoader) LoadReleases(config *config.Config, conformanceMode bool, helmWaitMode WaitMode, masterSecret, salt []byte, serviceAccURI string, idFile clusterid.File, output terraform.ApplyOutput) (*Releases, error) {
|
||||||
ciliumRelease, err := i.loadRelease(ciliumInfo, helmWaitMode)
|
ciliumRelease, err := i.loadRelease(ciliumInfo, helmWaitMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("loading cilium: %w", err)
|
return nil, fmt.Errorf("loading cilium: %w", err)
|
||||||
}
|
}
|
||||||
extendCiliumValues(ciliumRelease.Values, conformanceMode)
|
ciliumVals := extraCiliumValues(config.GetProvider(), conformanceMode, output)
|
||||||
|
ciliumRelease.Values = mergeMaps(ciliumRelease.Values, ciliumVals)
|
||||||
|
|
||||||
certManagerRelease, err := i.loadRelease(certManagerInfo, helmWaitMode)
|
certManagerRelease, err := i.loadRelease(certManagerInfo, helmWaitMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -124,14 +123,17 @@ func (i *ChartLoader) LoadReleases(config *config.Config, conformanceMode bool,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("loading operators: %w", err)
|
return nil, fmt.Errorf("loading operators: %w", err)
|
||||||
}
|
}
|
||||||
|
operatorRelease.Values = mergeMaps(operatorRelease.Values, extraOperatorValues(idFile.UID))
|
||||||
|
|
||||||
conServicesRelease, err := i.loadRelease(constellationServicesInfo, helmWaitMode)
|
conServicesRelease, err := i.loadRelease(constellationServicesInfo, helmWaitMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("loading constellation-services: %w", err)
|
return nil, fmt.Errorf("loading constellation-services: %w", err)
|
||||||
}
|
}
|
||||||
if err := extendConstellationServicesValues(conServicesRelease.Values, config, masterSecret, salt); err != nil {
|
svcVals, err := extraConstellationServicesValues(config, masterSecret, salt, idFile.UID, serviceAccURI, output)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("extending constellation-services values: %w", err)
|
return nil, fmt.Errorf("extending constellation-services values: %w", err)
|
||||||
}
|
}
|
||||||
|
conServicesRelease.Values = mergeMaps(conServicesRelease.Values, svcVals)
|
||||||
|
|
||||||
releases := Releases{Cilium: ciliumRelease, CertManager: certManagerRelease, ConstellationOperators: operatorRelease, ConstellationServices: conServicesRelease}
|
releases := Releases{Cilium: ciliumRelease, CertManager: certManagerRelease, ConstellationOperators: operatorRelease, ConstellationServices: conServicesRelease}
|
||||||
if config.HasProvider(cloudprovider.AWS) {
|
if config.HasProvider(cloudprovider.AWS) {
|
||||||
|
@ -143,11 +145,16 @@ func (i *ChartLoader) LoadReleases(config *config.Config, conformanceMode bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.DeployCSIDriver() {
|
if config.DeployCSIDriver() {
|
||||||
csi, err := i.loadRelease(csiInfo, helmWaitMode)
|
csiRelease, err := i.loadRelease(csiInfo, helmWaitMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("loading snapshot CRDs: %w", err)
|
return nil, fmt.Errorf("loading snapshot CRDs: %w", err)
|
||||||
}
|
}
|
||||||
releases.CSI = &csi
|
extraCSIvals, err := extraCSIValues(config.GetProvider(), serviceAccURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("extending CSI values: %w", err)
|
||||||
|
}
|
||||||
|
csiRelease.Values = mergeMaps(csiRelease.Values, extraCSIvals)
|
||||||
|
releases.CSI = &csiRelease
|
||||||
}
|
}
|
||||||
return &releases, nil
|
return &releases, nil
|
||||||
}
|
}
|
||||||
|
@ -183,13 +190,7 @@ func (i *ChartLoader) loadRelease(info chartInfo, helmWaitMode WaitMode) (Releas
|
||||||
updateVersions(chart, constants.BinaryVersion())
|
updateVersions(chart, constants.BinaryVersion())
|
||||||
values = i.loadCSIValues()
|
values = i.loadCSIValues()
|
||||||
}
|
}
|
||||||
|
return Release{Chart: chart, Values: values, ReleaseName: info.releaseName, WaitMode: helmWaitMode}, nil
|
||||||
chartRaw, err := i.marshalChart(chart)
|
|
||||||
if err != nil {
|
|
||||||
return Release{}, fmt.Errorf("packaging %s chart: %w", info.releaseName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Release{Chart: chartRaw, Values: values, ReleaseName: info.releaseName, WaitMode: helmWaitMode}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ChartLoader) loadAWSLBControllerValues() map[string]any {
|
func (i *ChartLoader) loadAWSLBControllerValues() map[string]any {
|
||||||
|
@ -200,22 +201,6 @@ func (i *ChartLoader) loadAWSLBControllerValues() map[string]any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extendCiliumValues extends the given values map by some values depending on user input.
|
|
||||||
// This extra step of separating the application of user input is necessary since service upgrades should
|
|
||||||
// reuse user input from the init step. However, we can't rely on reuse-values, because
|
|
||||||
// during upgrades we all values need to be set locally as they might have changed.
|
|
||||||
// Also, the charts are not rendered correctly without all of these values.
|
|
||||||
func extendCiliumValues(in map[string]any, conformanceMode bool) {
|
|
||||||
if conformanceMode {
|
|
||||||
in["kubeProxyReplacementHealthzBindAddr"] = ""
|
|
||||||
in["kubeProxyReplacement"] = "partial"
|
|
||||||
in["sessionAffinity"] = true
|
|
||||||
in["cni"] = map[string]any{
|
|
||||||
"chainingMode": "portmap",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadCertManagerHelper is used to separate the marshalling step from the loading step.
|
// loadCertManagerHelper is used to separate the marshalling step from the loading step.
|
||||||
// This reduces the time unit tests take to execute.
|
// This reduces the time unit tests take to execute.
|
||||||
func (i *ChartLoader) loadCertManagerValues() map[string]any {
|
func (i *ChartLoader) loadCertManagerValues() map[string]any {
|
||||||
|
@ -321,59 +306,6 @@ func (i *ChartLoader) cspTags() map[string]any {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extendConstellationServicesValues extends the given values map by some values depending on user input.
|
|
||||||
// Values set inside this function are only applied during init, not during upgrade.
|
|
||||||
func extendConstellationServicesValues(
|
|
||||||
in map[string]any, cfg *config.Config, masterSecret, salt []byte,
|
|
||||||
) error {
|
|
||||||
keyServiceValues, ok := in["key-service"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("missing 'key-service' key")
|
|
||||||
}
|
|
||||||
keyServiceValues["masterSecret"] = base64.StdEncoding.EncodeToString(masterSecret)
|
|
||||||
keyServiceValues["salt"] = base64.StdEncoding.EncodeToString(salt)
|
|
||||||
|
|
||||||
joinServiceVals, ok := in["join-service"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("invalid join-service values")
|
|
||||||
}
|
|
||||||
joinServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
|
|
||||||
|
|
||||||
// attestation config is updated separately during upgrade,
|
|
||||||
// so we only set them in Helm during init.
|
|
||||||
attestationConfigJSON, err := json.Marshal(cfg.GetAttestationConfig())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("marshalling measurements: %w", err)
|
|
||||||
}
|
|
||||||
joinServiceVals["attestationConfig"] = string(attestationConfigJSON)
|
|
||||||
|
|
||||||
verifyServiceVals, ok := in["verification-service"].(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return errors.New("invalid verification-service values")
|
|
||||||
}
|
|
||||||
verifyServiceVals["attestationVariant"] = cfg.GetAttestationConfig().GetVariant().String()
|
|
||||||
|
|
||||||
csp := cfg.GetProvider()
|
|
||||||
switch csp {
|
|
||||||
case cloudprovider.OpenStack:
|
|
||||||
in["openstack"] = map[string]any{
|
|
||||||
"deployYawolLoadBalancer": cfg.DeployYawolLoadBalancer(),
|
|
||||||
}
|
|
||||||
if cfg.DeployYawolLoadBalancer() {
|
|
||||||
in["yawol-controller"] = map[string]any{
|
|
||||||
"yawolOSSecretName": "yawolkey",
|
|
||||||
// has to be larger than ~30s to account for slow OpenStack API calls.
|
|
||||||
"openstackTimeout": "1m",
|
|
||||||
"yawolFloatingID": cfg.Provider.OpenStack.FloatingIPPoolID,
|
|
||||||
"yawolFlavorID": cfg.Provider.OpenStack.YawolFlavorID,
|
|
||||||
"yawolImageID": cfg.Provider.OpenStack.YawolImageID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateVersions changes all versions of direct dependencies that are set to "0.0.0" to newVersion.
|
// updateVersions changes all versions of direct dependencies that are set to "0.0.0" to newVersion.
|
||||||
func updateVersions(chart *chart.Chart, newVersion semver.Semver) {
|
func updateVersions(chart *chart.Chart, newVersion semver.Semver) {
|
||||||
chart.Metadata.Version = newVersion.String()
|
chart.Metadata.Version = newVersion.String()
|
||||||
|
@ -392,29 +324,6 @@ func updateVersions(chart *chart.Chart, newVersion semver.Semver) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshalChart takes a Chart object, packages it to a temporary file and returns the content of that file.
|
|
||||||
// We currently need to take this approach of marshaling as dependencies are not marshaled correctly with json.Marshal.
|
|
||||||
// This stems from the fact that chart.Chart does not export the dependencies property.
|
|
||||||
func (i *ChartLoader) marshalChart(chart *chart.Chart) ([]byte, error) {
|
|
||||||
// A separate tmpdir path is necessary since during unit testing multiple go routines are accessing the same path, possibly deleting files for other routines.
|
|
||||||
tmpDirPath, err := os.MkdirTemp("", "*")
|
|
||||||
defer os.Remove(tmpDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating tmp dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := chartutil.Save(chart, tmpDirPath)
|
|
||||||
defer os.Remove(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("chartutil save: %w", err)
|
|
||||||
}
|
|
||||||
chartRaw, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("reading packaged chart: %w", err)
|
|
||||||
}
|
|
||||||
return chartRaw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// taken from loader.LoadDir from the helm go module
|
// taken from loader.LoadDir from the helm go module
|
||||||
// loadChartsDir loads from a directory.
|
// loadChartsDir loads from a directory.
|
||||||
//
|
//
|
||||||
|
|
|
@ -20,27 +20,56 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"helm.sh/helm/v3/pkg/chart/loader"
|
|
||||||
"helm.sh/helm/v3/pkg/chartutil"
|
"helm.sh/helm/v3/pkg/chartutil"
|
||||||
"helm.sh/helm/v3/pkg/engine"
|
"helm.sh/helm/v3/pkg/engine"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func fakeServiceAccURI(provider cloudprovider.Provider) string {
|
||||||
|
switch provider {
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
cred := gcpshared.ServiceAccountKey{
|
||||||
|
Type: "service_account",
|
||||||
|
ProjectID: "project_id",
|
||||||
|
PrivateKeyID: "key_id",
|
||||||
|
PrivateKey: "key",
|
||||||
|
ClientEmail: "client_email",
|
||||||
|
ClientID: "client_id",
|
||||||
|
AuthURI: "auth_uri",
|
||||||
|
TokenURI: "token_uri",
|
||||||
|
AuthProviderX509CertURL: "cert",
|
||||||
|
ClientX509CertURL: "client_cert",
|
||||||
|
}
|
||||||
|
return cred.ToCloudServiceAccountURI()
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
creds := azureshared.ApplicationCredentials{
|
||||||
|
TenantID: "TenantID",
|
||||||
|
Location: "Location",
|
||||||
|
PreferredAuthMethod: azureshared.AuthMethodUserAssignedIdentity,
|
||||||
|
UamiResourceID: "uid",
|
||||||
|
}
|
||||||
|
return creds.ToCloudServiceAccountURI()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadReleases(t *testing.T) {
|
func TestLoadReleases(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}}
|
config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}}
|
||||||
chartLoader := ChartLoader{csp: config.GetProvider()}
|
chartLoader := ChartLoader{csp: config.GetProvider()}
|
||||||
helmReleases, err := chartLoader.LoadReleases(config, true, WaitModeAtomic, []byte("secret"), []byte("salt"))
|
helmReleases, err := chartLoader.LoadReleases(config, true, WaitModeAtomic, []byte("secret"), []byte("salt"), fakeServiceAccURI(cloudprovider.GCP), clusterid.File{UID: "testuid"}, terraform.ApplyOutput{GCP: &terraform.GCPApplyOutput{}})
|
||||||
require.NoError(err)
|
|
||||||
reader := bytes.NewReader(helmReleases.ConstellationServices.Chart)
|
|
||||||
chart, err := loader.LoadArchive(reader)
|
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
chart := helmReleases.ConstellationServices.Chart
|
||||||
assert.NotNil(chart.Dependencies())
|
assert.NotNil(chart.Dependencies())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,8 +175,13 @@ func TestConstellationServices(t *testing.T) {
|
||||||
chart, err := loadChartsDir(helmFS, constellationServicesInfo.path)
|
chart, err := loadChartsDir(helmFS, constellationServicesInfo.path)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
values := chartLoader.loadConstellationServicesValues()
|
values := chartLoader.loadConstellationServicesValues()
|
||||||
err = extendConstellationServicesValues(values, tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
|
serviceAccURI := fakeServiceAccURI(tc.config.GetProvider())
|
||||||
|
extraVals, err := extraConstellationServicesValues(tc.config, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), "uid", serviceAccURI, terraform.ApplyOutput{
|
||||||
|
Azure: &terraform.AzureApplyOutput{},
|
||||||
|
GCP: &terraform.GCPApplyOutput{},
|
||||||
|
})
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
values = mergeMaps(values, extraVals)
|
||||||
|
|
||||||
options := chartutil.ReleaseOptions{
|
options := chartutil.ReleaseOptions{
|
||||||
Name: "testRelease",
|
Name: "testRelease",
|
||||||
|
|
182
cli/internal/helm/overrides.go
Normal file
182
cli/internal/helm/overrides.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Overrides contains helm values that are dynamically injected into the helm charts.
|
||||||
|
*/
|
||||||
|
package helm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(malt3): switch over to DNS name on AWS and Azure
|
||||||
|
// soon as every apiserver certificate of every control-plane node
|
||||||
|
// has the dns endpoint in its SAN list.
|
||||||
|
// extraCiliumValues extends the given values map by some values depending on user input.
|
||||||
|
// This extra step of separating the application of user input is necessary since service upgrades should
|
||||||
|
// reuse user input from the init step. However, we can't rely on reuse-values, because
|
||||||
|
// during upgrades we all values need to be set locally as they might have changed.
|
||||||
|
// Also, the charts are not rendered correctly without all of these values.
|
||||||
|
func extraCiliumValues(provider cloudprovider.Provider, conformanceMode bool, output terraform.ApplyOutput) map[string]any {
|
||||||
|
extraVals := map[string]any{}
|
||||||
|
if conformanceMode {
|
||||||
|
extraVals["kubeProxyReplacementHealthzBindAddr"] = ""
|
||||||
|
extraVals["kubeProxyReplacement"] = "partial"
|
||||||
|
extraVals["sessionAffinity"] = true
|
||||||
|
extraVals["cni"] = map[string]any{
|
||||||
|
"chainingMode": "portmap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extraVals["k8sServiceHost"] = output.IP
|
||||||
|
extraVals["k8sServicePort"] = constants.KubernetesPort
|
||||||
|
if provider == cloudprovider.GCP {
|
||||||
|
extraVals["ipv4NativeRoutingCIDR"] = output.GCP.IPCidrPod
|
||||||
|
extraVals["strictModeCIDR"] = output.GCP.IPCidrPod
|
||||||
|
}
|
||||||
|
return extraVals
|
||||||
|
}
|
||||||
|
|
||||||
|
// extraConstellationServicesValues extends the given values map by some values depending on user input.
|
||||||
|
// Values set inside this function are only applied during init, not during upgrade.
|
||||||
|
func extraConstellationServicesValues(cfg *config.Config, masterSecret, salt []byte, uid, serviceAccURI string, output terraform.ApplyOutput,
|
||||||
|
) (map[string]any, error) {
|
||||||
|
attestationConfigJSON, err := json.Marshal(cfg.GetAttestationConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshalling measurements: %w", err)
|
||||||
|
}
|
||||||
|
extraVals := map[string]any{}
|
||||||
|
extraVals["join-service"] = map[string]any{
|
||||||
|
"measurementSalt": base64.StdEncoding.EncodeToString(salt),
|
||||||
|
"attestationVariant": cfg.GetAttestationConfig().GetVariant().String(),
|
||||||
|
"attestationConfig": string(attestationConfigJSON),
|
||||||
|
}
|
||||||
|
extraVals["verification-service"] = map[string]any{
|
||||||
|
"attestationVariant": cfg.GetAttestationConfig().GetVariant().String(),
|
||||||
|
"loadBalancerIP": output.IP,
|
||||||
|
}
|
||||||
|
extraVals["konnectivity"] = map[string]any{
|
||||||
|
"loadBalancerIP": output.IP,
|
||||||
|
}
|
||||||
|
|
||||||
|
extraVals["key-service"] = map[string]any{
|
||||||
|
"masterSecret": base64.StdEncoding.EncodeToString(masterSecret),
|
||||||
|
"salt": base64.StdEncoding.EncodeToString(salt),
|
||||||
|
}
|
||||||
|
switch cfg.GetProvider() {
|
||||||
|
case cloudprovider.OpenStack:
|
||||||
|
extraVals["openstack"] = map[string]any{
|
||||||
|
"deployYawolLoadBalancer": cfg.DeployYawolLoadBalancer(),
|
||||||
|
}
|
||||||
|
if cfg.DeployYawolLoadBalancer() {
|
||||||
|
extraVals["yawol-controller"] = map[string]any{
|
||||||
|
"yawolOSSecretName": "yawolkey",
|
||||||
|
// has to be larger than ~30s to account for slow OpenStack API calls.
|
||||||
|
"openstackTimeout": "1m",
|
||||||
|
"yawolFloatingID": cfg.Provider.OpenStack.FloatingIPPoolID,
|
||||||
|
"yawolFlavorID": cfg.Provider.OpenStack.YawolFlavorID,
|
||||||
|
"yawolImageID": cfg.Provider.OpenStack.YawolImageID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case cloudprovider.GCP:
|
||||||
|
serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(serviceAccURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting service account key: %w", err)
|
||||||
|
}
|
||||||
|
rawKey, err := json.Marshal(serviceAccountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshaling service account key: %w", err)
|
||||||
|
}
|
||||||
|
if output.GCP == nil {
|
||||||
|
return nil, fmt.Errorf("no GCP output from Terraform")
|
||||||
|
}
|
||||||
|
extraVals["ccm"] = map[string]any{
|
||||||
|
"GCP": map[string]any{
|
||||||
|
"projectID": output.GCP.ProjectID,
|
||||||
|
"uid": uid,
|
||||||
|
"secretData": string(rawKey),
|
||||||
|
"subnetworkPodCIDR": output.GCP.IPCidrPod,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case cloudprovider.Azure:
|
||||||
|
if output.Azure == nil {
|
||||||
|
return nil, fmt.Errorf("no Azure output from Terraform")
|
||||||
|
}
|
||||||
|
ccmConfig, err := getCCMConfig(*output.Azure, serviceAccURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting Azure CCM config: %w", err)
|
||||||
|
}
|
||||||
|
extraVals["ccm"] = map[string]any{
|
||||||
|
"Azure": map[string]any{
|
||||||
|
"azureConfig": string(ccmConfig),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extraVals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCCMConfig returns the configuration needed for the Kubernetes Cloud Controller Manager on Azure.
|
||||||
|
func getCCMConfig(tfOutput terraform.AzureApplyOutput, serviceAccURI string) ([]byte, error) {
|
||||||
|
creds, err := azureshared.ApplicationCredentialsFromURI(serviceAccURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting service account key: %w", err)
|
||||||
|
}
|
||||||
|
useManagedIdentityExtension := creds.PreferredAuthMethod == azureshared.AuthMethodUserAssignedIdentity
|
||||||
|
config := cloudConfig{
|
||||||
|
Cloud: "AzurePublicCloud",
|
||||||
|
TenantID: creds.TenantID,
|
||||||
|
SubscriptionID: tfOutput.SubscriptionID,
|
||||||
|
ResourceGroup: tfOutput.ResourceGroup,
|
||||||
|
LoadBalancerSku: "standard",
|
||||||
|
SecurityGroupName: tfOutput.NetworkSecurityGroupName,
|
||||||
|
LoadBalancerName: tfOutput.LoadBalancerName,
|
||||||
|
UseInstanceMetadata: true,
|
||||||
|
VMType: "vmss",
|
||||||
|
Location: creds.Location,
|
||||||
|
UseManagedIdentityExtension: useManagedIdentityExtension,
|
||||||
|
UserAssignedIdentityID: tfOutput.UserAssignedIdentity,
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extraOperatorValues returns the values for the constellation-operator chart.
|
||||||
|
func extraOperatorValues(uid string) map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"constellation-operator": map[string]any{
|
||||||
|
"constellationUID": uid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extraCSIValues returns the values for the csi chart.
|
||||||
|
func extraCSIValues(provider cloudprovider.Provider, serviceAccURI string) (map[string]any, error) {
|
||||||
|
var csiVals map[string]any
|
||||||
|
if provider == cloudprovider.OpenStack {
|
||||||
|
creds, err := openstack.AccountKeyFromURI(serviceAccURI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cinderIni := creds.CloudINI().CinderCSIConfiguration()
|
||||||
|
csiVals = map[string]any{
|
||||||
|
"cinder-config": map[string]any{
|
||||||
|
"secretData": cinderIni,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return csiVals, nil
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
// Package helm provides types and functions shared across services.
|
// Package helm provides types and functions shared across services.
|
||||||
package helm
|
package helm
|
||||||
|
|
||||||
|
import "helm.sh/helm/v3/pkg/chart"
|
||||||
|
|
||||||
// Release bundles all information necessary to create a helm release.
|
// Release bundles all information necessary to create a helm release.
|
||||||
type Release struct {
|
type Release struct {
|
||||||
Chart []byte
|
Chart *chart.Chart
|
||||||
Values map[string]any
|
Values map[string]any
|
||||||
ReleaseName string
|
ReleaseName string
|
||||||
WaitMode WaitMode
|
WaitMode WaitMode
|
||||||
|
|
|
@ -166,6 +166,9 @@ func (c *Client) ShowCluster(ctx context.Context, provider cloudprovider.Provide
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ApplyOutput{}, fmt.Errorf("terraform show: %w", err)
|
return ApplyOutput{}, fmt.Errorf("terraform show: %w", err)
|
||||||
}
|
}
|
||||||
|
if tfState.Values == nil {
|
||||||
|
return ApplyOutput{}, errors.New("terraform show: no values returned")
|
||||||
|
}
|
||||||
|
|
||||||
ipOutput, ok := tfState.Values.Outputs["ip"]
|
ipOutput, ok := tfState.Values.Outputs["ip"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue