mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-03 14:54:53 -04:00
cli: install helm charts in cli instead of bootstrapper (#2136)
* init * fixup! init * gcp working? * fixup! fixup! init * azure cfg for microService installation * fixup! azure cfg for microService installation * fixup! azure cfg for microService installation * cleanup bootstrapper code * cleanup helminstall code * fixup! cleanup helminstall code * Update internal/deploy/helm/install.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * daniel feedback * TODO add provider (also to CreateCluster) so we can ensure that provider specific output * fixup! daniel feedback * use debugLog in helm installer * placeholderHelmInstaller * rename to stub --------- Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
parent
ef60d00a60
commit
26305e8f80
38 changed files with 775 additions and 970 deletions
|
@ -78,172 +78,8 @@ func (c *Client) WithManualStateMigration(migration StateMigration) *Client {
|
|||
return c
|
||||
}
|
||||
|
||||
// Show reads the default state path and outputs the state.
|
||||
func (c *Client) Show(ctx context.Context) (*tfjson.State, error) {
|
||||
return c.tf.Show(ctx)
|
||||
}
|
||||
|
||||
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
|
||||
func (c *Client) PrepareWorkspace(path string, vars Variables) error {
|
||||
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {
|
||||
return fmt.Errorf("prepare workspace: %w", err)
|
||||
}
|
||||
|
||||
return c.writeVars(vars)
|
||||
}
|
||||
|
||||
// PrepareUpgradeWorkspace prepares a Terraform workspace for a Constellation version upgrade.
|
||||
// It copies the Terraform state from the old working dir and the embedded Terraform files into the new working dir.
|
||||
func (c *Client) PrepareUpgradeWorkspace(path, oldWorkingDir, newWorkingDir, backupDir string, vars Variables) error {
|
||||
if err := prepareUpgradeWorkspace(path, c.file, oldWorkingDir, newWorkingDir, backupDir); err != nil {
|
||||
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
||||
}
|
||||
|
||||
return c.writeVars(vars)
|
||||
}
|
||||
|
||||
// PrepareIAMUpgradeWorkspace prepares a Terraform workspace for a Constellation IAM upgrade.
|
||||
func PrepareIAMUpgradeWorkspace(file file.Handler, path, oldWorkingDir, newWorkingDir, backupDir string) error {
|
||||
if err := prepareUpgradeWorkspace(path, file, oldWorkingDir, newWorkingDir, backupDir); err != nil {
|
||||
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
||||
}
|
||||
// copy the vars file from the old working dir to the new working dir
|
||||
if err := file.CopyFile(filepath.Join(oldWorkingDir, terraformVarsFile), filepath.Join(newWorkingDir, terraformVarsFile)); err != nil {
|
||||
return fmt.Errorf("copying vars file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCluster creates a Constellation cluster using Terraform.
|
||||
func (c *Client) CreateCluster(ctx context.Context, logLevel LogLevel) (ApplyOutput, error) {
|
||||
if err := c.setLogLevel(logLevel); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
|
||||
}
|
||||
|
||||
if err := c.tf.Init(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform init: %w", err)
|
||||
}
|
||||
|
||||
if err := c.applyManualStateMigrations(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("apply manual state migrations: %w", err)
|
||||
}
|
||||
|
||||
if err := c.tf.Apply(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform apply: %w", err)
|
||||
}
|
||||
|
||||
tfState, err := c.tf.Show(ctx)
|
||||
if err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform show: %w", err)
|
||||
}
|
||||
|
||||
ipOutput, ok := tfState.Values.Outputs["ip"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no IP output found")
|
||||
}
|
||||
ip, ok := ipOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in IP output: not a string")
|
||||
}
|
||||
|
||||
apiServerCertSANsOutput, ok := tfState.Values.Outputs["api_server_cert_sans"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no api_server_cert_sans output found")
|
||||
}
|
||||
apiServerCertSANsUntyped, ok := apiServerCertSANsOutput.Value.([]any)
|
||||
if !ok {
|
||||
return ApplyOutput{}, fmt.Errorf("invalid type in api_server_cert_sans output: %s is not a list of elements", apiServerCertSANsOutput.Type.FriendlyName())
|
||||
}
|
||||
apiServerCertSANs, err := toStringSlice(apiServerCertSANsUntyped)
|
||||
if err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("convert api_server_cert_sans output: %w", err)
|
||||
}
|
||||
|
||||
secretOutput, ok := tfState.Values.Outputs["initSecret"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no initSecret output found")
|
||||
}
|
||||
secret, ok := secretOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in initSecret output: not a string")
|
||||
}
|
||||
|
||||
uidOutput, ok := tfState.Values.Outputs["uid"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no uid output found")
|
||||
}
|
||||
uid, ok := uidOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in uid output: not a string")
|
||||
}
|
||||
|
||||
var attestationURL string
|
||||
if attestationURLOutput, ok := tfState.Values.Outputs["attestationURL"]; ok {
|
||||
if attestationURLString, ok := attestationURLOutput.Value.(string); ok {
|
||||
attestationURL = attestationURLString
|
||||
}
|
||||
}
|
||||
|
||||
return ApplyOutput{
|
||||
IP: ip,
|
||||
APIServerCertSANs: apiServerCertSANs,
|
||||
Secret: secret,
|
||||
UID: uid,
|
||||
AttestationURL: attestationURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyOutput contains the Terraform output values of a cluster creation
|
||||
// or apply operation.
|
||||
type ApplyOutput struct {
|
||||
IP string
|
||||
APIServerCertSANs []string
|
||||
Secret string
|
||||
UID string
|
||||
// AttestationURL is the URL of the attestation provider.
|
||||
// It is only set if the cluster is created on Azure.
|
||||
AttestationURL string
|
||||
}
|
||||
|
||||
// IAMOutput contains the output information of the Terraform IAM operations.
|
||||
type IAMOutput struct {
|
||||
GCP GCPIAMOutput
|
||||
Azure AzureIAMOutput
|
||||
AWS AWSIAMOutput
|
||||
}
|
||||
|
||||
// GCPIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||
type GCPIAMOutput struct {
|
||||
SaKey string
|
||||
}
|
||||
|
||||
// AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure.
|
||||
type AzureIAMOutput struct {
|
||||
SubscriptionID string
|
||||
TenantID string
|
||||
UAMIID string
|
||||
}
|
||||
|
||||
// AWSIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||
type AWSIAMOutput struct {
|
||||
ControlPlaneInstanceProfile string
|
||||
WorkerNodeInstanceProfile string
|
||||
}
|
||||
|
||||
// ApplyIAMConfig creates an IAM configuration using Terraform.
|
||||
func (c *Client) ApplyIAMConfig(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (IAMOutput, error) {
|
||||
if err := c.setLogLevel(logLevel); err != nil {
|
||||
return IAMOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
|
||||
}
|
||||
|
||||
if err := c.tf.Init(ctx); err != nil {
|
||||
return IAMOutput{}, err
|
||||
}
|
||||
|
||||
if err := c.tf.Apply(ctx); err != nil {
|
||||
return IAMOutput{}, err
|
||||
}
|
||||
|
||||
// ShowIAM reads the state of Constellation IAM resources from Terraform.
|
||||
func (c *Client) ShowIAM(ctx context.Context, provider cloudprovider.Provider) (IAMOutput, error) {
|
||||
tfState, err := c.tf.Show(ctx)
|
||||
if err != nil {
|
||||
return IAMOutput{}, err
|
||||
|
@ -324,6 +160,277 @@ func (c *Client) ApplyIAMConfig(ctx context.Context, provider cloudprovider.Prov
|
|||
}
|
||||
}
|
||||
|
||||
// ShowCluster reads the state of Constellation cluster resources from Terraform.
|
||||
func (c *Client) ShowCluster(ctx context.Context, provider cloudprovider.Provider) (ApplyOutput, error) {
|
||||
tfState, err := c.tf.Show(ctx)
|
||||
if err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform show: %w", err)
|
||||
}
|
||||
|
||||
ipOutput, ok := tfState.Values.Outputs["ip"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no IP output found")
|
||||
}
|
||||
ip, ok := ipOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in IP output: not a string")
|
||||
}
|
||||
|
||||
apiServerCertSANsOutput, ok := tfState.Values.Outputs["api_server_cert_sans"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no api_server_cert_sans output found")
|
||||
}
|
||||
apiServerCertSANsUntyped, ok := apiServerCertSANsOutput.Value.([]any)
|
||||
if !ok {
|
||||
return ApplyOutput{}, fmt.Errorf("invalid type in api_server_cert_sans output: %s is not a list of elements", apiServerCertSANsOutput.Type.FriendlyName())
|
||||
}
|
||||
apiServerCertSANs, err := toStringSlice(apiServerCertSANsUntyped)
|
||||
if err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("convert api_server_cert_sans output: %w", err)
|
||||
}
|
||||
|
||||
secretOutput, ok := tfState.Values.Outputs["initSecret"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no initSecret output found")
|
||||
}
|
||||
secret, ok := secretOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in initSecret output: not a string")
|
||||
}
|
||||
|
||||
uidOutput, ok := tfState.Values.Outputs["uid"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no uid output found")
|
||||
}
|
||||
uid, ok := uidOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in uid output: not a string")
|
||||
}
|
||||
|
||||
res := ApplyOutput{
|
||||
IP: ip,
|
||||
APIServerCertSANs: apiServerCertSANs,
|
||||
Secret: secret,
|
||||
UID: uid,
|
||||
}
|
||||
// TODO add provider
|
||||
switch provider {
|
||||
case cloudprovider.GCP:
|
||||
gcpProjectOutput, ok := tfState.Values.Outputs["project"]
|
||||
if ok {
|
||||
gcpProject, ok := gcpProjectOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in project output: not a string")
|
||||
}
|
||||
cidrNodesOutput, ok := tfState.Values.Outputs["ip_cidr_nodes"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no ip_cidr_nodes output found")
|
||||
}
|
||||
cidrNodes, ok := cidrNodesOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in ip_cidr_nodes output: not a string")
|
||||
}
|
||||
cidrPodsOutput, ok := tfState.Values.Outputs["ip_cidr_pods"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no ip_cidr_pods output found")
|
||||
}
|
||||
cidrPods, ok := cidrPodsOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in ip_cidr_pods output: not a string")
|
||||
}
|
||||
res.GCP = &GCPApplyOutput{
|
||||
ProjectID: gcpProject,
|
||||
IPCidrNode: cidrNodes,
|
||||
IPCidrPod: cidrPods,
|
||||
}
|
||||
}
|
||||
case cloudprovider.Azure:
|
||||
var attestationURL string
|
||||
if ok {
|
||||
if attestationURLOutput, ok := tfState.Values.Outputs["attestationURL"]; ok {
|
||||
if attestationURLString, ok := attestationURLOutput.Value.(string); ok {
|
||||
attestationURL = attestationURLString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
azureUAMIOutput, ok := tfState.Values.Outputs["user_assigned_identity"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no user_assigned_identity output found")
|
||||
}
|
||||
azureUAMI, ok := azureUAMIOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in user_assigned_identity output: not a string")
|
||||
}
|
||||
|
||||
rgOutput, ok := tfState.Values.Outputs["resource_group"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no resource_group output found")
|
||||
}
|
||||
rg, ok := rgOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in resource_group output: not a string")
|
||||
}
|
||||
|
||||
subscriptionOutput, ok := tfState.Values.Outputs["subscription_id"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no subscription_id output found")
|
||||
}
|
||||
subscriptionID, ok := subscriptionOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in subscription_id output: not a string")
|
||||
}
|
||||
|
||||
networkSGNameOutput, ok := tfState.Values.Outputs["network_security_group_name"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no network_security_group_name output found")
|
||||
}
|
||||
networkSGName, ok := networkSGNameOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in network_security_group_name output: not a string")
|
||||
}
|
||||
loadBalancerNameOutput, ok := tfState.Values.Outputs["loadbalancer_name"]
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("no loadbalancer_name output found")
|
||||
}
|
||||
loadBalancerName, ok := loadBalancerNameOutput.Value.(string)
|
||||
if !ok {
|
||||
return ApplyOutput{}, errors.New("invalid type in loadbalancer_name output: not a string")
|
||||
}
|
||||
res.Azure = &AzureApplyOutput{
|
||||
ResourceGroup: rg,
|
||||
SubscriptionID: subscriptionID,
|
||||
UserAssignedIdentity: azureUAMI,
|
||||
NetworkSecurityGroupName: networkSGName,
|
||||
LoadBalancerName: loadBalancerName,
|
||||
AttestationURL: attestationURL,
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
|
||||
func (c *Client) PrepareWorkspace(path string, vars Variables) error {
|
||||
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {
|
||||
return fmt.Errorf("prepare workspace: %w", err)
|
||||
}
|
||||
|
||||
return c.writeVars(vars)
|
||||
}
|
||||
|
||||
// PrepareUpgradeWorkspace prepares a Terraform workspace for a Constellation version upgrade.
|
||||
// It copies the Terraform state from the old working dir and the embedded Terraform files into the new working dir.
|
||||
func (c *Client) PrepareUpgradeWorkspace(path, oldWorkingDir, newWorkingDir, backupDir string, vars Variables) error {
|
||||
if err := prepareUpgradeWorkspace(path, c.file, oldWorkingDir, newWorkingDir, backupDir); err != nil {
|
||||
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
||||
}
|
||||
|
||||
return c.writeVars(vars)
|
||||
}
|
||||
|
||||
// PrepareIAMUpgradeWorkspace prepares a Terraform workspace for a Constellation IAM upgrade.
|
||||
func PrepareIAMUpgradeWorkspace(file file.Handler, path, oldWorkingDir, newWorkingDir, backupDir string) error {
|
||||
if err := prepareUpgradeWorkspace(path, file, oldWorkingDir, newWorkingDir, backupDir); err != nil {
|
||||
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
||||
}
|
||||
// copy the vars file from the old working dir to the new working dir
|
||||
if err := file.CopyFile(filepath.Join(oldWorkingDir, terraformVarsFile), filepath.Join(newWorkingDir, terraformVarsFile)); err != nil {
|
||||
return fmt.Errorf("copying vars file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCluster creates a Constellation cluster using Terraform.
|
||||
func (c *Client) CreateCluster(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (ApplyOutput, error) {
|
||||
if err := c.setLogLevel(logLevel); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
|
||||
}
|
||||
|
||||
if err := c.tf.Init(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform init: %w", err)
|
||||
}
|
||||
|
||||
if err := c.applyManualStateMigrations(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("apply manual state migrations: %w", err)
|
||||
}
|
||||
|
||||
if err := c.tf.Apply(ctx); err != nil {
|
||||
return ApplyOutput{}, fmt.Errorf("terraform apply: %w", err)
|
||||
}
|
||||
|
||||
return c.ShowCluster(ctx, provider)
|
||||
}
|
||||
|
||||
// ApplyOutput contains the Terraform output values of a cluster creation
|
||||
// or apply operation.
|
||||
type ApplyOutput struct {
|
||||
IP string
|
||||
APIServerCertSANs []string
|
||||
Secret string
|
||||
UID string
|
||||
GCP *GCPApplyOutput
|
||||
Azure *AzureApplyOutput
|
||||
}
|
||||
|
||||
// AzureApplyOutput contains the Terraform output values of a terraform apply operation on Microsoft Azure.
|
||||
type AzureApplyOutput struct {
|
||||
ResourceGroup string
|
||||
SubscriptionID string
|
||||
NetworkSecurityGroupName string
|
||||
LoadBalancerName string
|
||||
UserAssignedIdentity string
|
||||
// AttestationURL is the URL of the attestation provider.
|
||||
AttestationURL string
|
||||
}
|
||||
|
||||
// GCPApplyOutput contains the Terraform output values of a terraform apply operation on GCP.
|
||||
type GCPApplyOutput struct {
|
||||
ProjectID string
|
||||
IPCidrNode string
|
||||
IPCidrPod string
|
||||
}
|
||||
|
||||
// IAMOutput contains the output information of the Terraform IAM operations.
|
||||
type IAMOutput struct {
|
||||
GCP GCPIAMOutput
|
||||
Azure AzureIAMOutput
|
||||
AWS AWSIAMOutput
|
||||
}
|
||||
|
||||
// GCPIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||
type GCPIAMOutput struct {
|
||||
SaKey string
|
||||
}
|
||||
|
||||
// AzureIAMOutput contains the output information of the Terraform IAM operation on Microsoft Azure.
|
||||
type AzureIAMOutput struct {
|
||||
SubscriptionID string
|
||||
TenantID string
|
||||
UAMIID string
|
||||
}
|
||||
|
||||
// AWSIAMOutput contains the output information of the Terraform IAM operation on GCP.
|
||||
type AWSIAMOutput struct {
|
||||
ControlPlaneInstanceProfile string
|
||||
WorkerNodeInstanceProfile string
|
||||
}
|
||||
|
||||
// ApplyIAMConfig creates an IAM configuration using Terraform.
|
||||
func (c *Client) ApplyIAMConfig(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (IAMOutput, error) {
|
||||
if err := c.setLogLevel(logLevel); err != nil {
|
||||
return IAMOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
|
||||
}
|
||||
|
||||
if err := c.tf.Init(ctx); err != nil {
|
||||
return IAMOutput{}, err
|
||||
}
|
||||
|
||||
if err := c.tf.Apply(ctx); err != nil {
|
||||
return IAMOutput{}, err
|
||||
}
|
||||
return c.ShowIAM(ctx, provider)
|
||||
}
|
||||
|
||||
// Plan determines the diff that will be applied by Terraform. The plan output is written to the planFile.
|
||||
// If there is a diff, the returned bool is true. Otherwise, it is false.
|
||||
func (c *Client) Plan(ctx context.Context, logLevel LogLevel, planFile string) (bool, error) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue