cli: remove/refactor upgrade package (#2266)

* Move IAM migration client to cloudcmd package

* Move Terraform Cluster upgrade client to cloudcmd package

* Use hcl for creating Terraform IAM variables files

* Unify terraform upgrade code

* Rename some cloudcmd files for better clarity

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-08-23 10:35:42 +02:00 committed by GitHub
parent 3d5d291891
commit 0a911806d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1197 additions and 1194 deletions

View file

@ -83,7 +83,9 @@ go_library(
"@com_github_hashicorp_hc_install//product",
"@com_github_hashicorp_hc_install//releases",
"@com_github_hashicorp_hc_install//src",
"@com_github_hashicorp_hcl_v2//:hcl",
"@com_github_hashicorp_hcl_v2//gohcl",
"@com_github_hashicorp_hcl_v2//hclsyntax",
"@com_github_hashicorp_hcl_v2//hclwrite",
"@com_github_hashicorp_terraform_exec//tfexec",
"@com_github_hashicorp_terraform_json//:terraform-json",

View file

@ -47,18 +47,6 @@ const (
terraformUpgradePlanFile = "plan.zip"
)
// 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
}
// ErrTerraformWorkspaceExistsWithDifferentVariables is returned when existing Terraform files differ from the version the CLI wants to extract.
var ErrTerraformWorkspaceExistsWithDifferentVariables = errors.New("creating cluster: a Terraform workspace already exists with different variables")
@ -347,10 +335,12 @@ func (c *Client) PrepareWorkspace(path string, vars Variables) error {
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 {
// PrepareUpgradeWorkspace prepares a Terraform workspace for an upgrade.
// It copies the Terraform state from the old working dir and the embedded Terraform files
// into the working dir of the Terraform client.
// Additionally, a backup of the old working dir is created in the backup dir.
func (c *Client) PrepareUpgradeWorkspace(path, oldWorkingDir, backupDir string, vars Variables) error {
if err := prepareUpgradeWorkspace(path, c.file, oldWorkingDir, c.workingDir, backupDir); err != nil {
return fmt.Errorf("prepare upgrade workspace: %w", err)
}

View file

@ -8,9 +8,10 @@ package terraform
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
)
@ -30,6 +31,19 @@ type ClusterVariables interface {
GetCreateMAA() bool
}
// VariablesFromBytes parses the given bytes into the given variables struct.
func VariablesFromBytes[T any](b []byte, vars *T) error {
file, err := hclsyntax.ParseConfig(b, "", hcl.Pos{Line: 1, Column: 1})
if err != nil {
return fmt.Errorf("parsing variables: %w", err)
}
if err := gohcl.DecodeBody(file.Body, nil, vars); err != nil {
return fmt.Errorf("decoding variables: %w", err)
}
return nil
}
// AWSClusterVariables is user configuration for creating a cluster with Terraform on AWS.
type AWSClusterVariables struct {
// Name of the cluster.
@ -87,18 +101,16 @@ type AWSNodeGroup struct {
// AWSIAMVariables is user configuration for creating the IAM configuration with Terraform on Microsoft Azure.
type AWSIAMVariables struct {
// Region is the AWS location to use. (e.g. us-east-2)
Region string
Region string `hcl:"region" cty:"region"`
// Prefix is the name prefix of the resources to use.
Prefix string
Prefix string `hcl:"name_prefix" cty:"name_prefix"`
}
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
func (v *AWSIAMVariables) String() string {
b := &strings.Builder{}
writeLinef(b, "name_prefix = %q", v.Prefix)
writeLinef(b, "region = %q", v.Region)
return b.String()
f := hclwrite.NewEmptyFile()
gohcl.EncodeIntoBody(v, f.Body())
return string(f.Bytes())
}
// GCPClusterVariables is user configuration for creating resources with Terraform on GCP.
@ -151,24 +163,20 @@ type GCPNodeGroup struct {
// GCPIAMVariables is user configuration for creating the IAM confioguration with Terraform on GCP.
type GCPIAMVariables struct {
// Project is the ID of the GCP project to use.
Project string
Project string `hcl:"project_id" cty:"project_id"`
// Region is the GCP region to use.
Region string
Region string `hcl:"region" cty:"region"`
// Zone is the GCP zone to use.
Zone string
Zone string `hcl:"zone" cty:"zone"`
// ServiceAccountID is the ID of the service account to use.
ServiceAccountID string
ServiceAccountID string `hcl:"service_account_id" cty:"service_account_id"`
}
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
func (v *GCPIAMVariables) String() string {
b := &strings.Builder{}
writeLinef(b, "project_id = %q", v.Project)
writeLinef(b, "region = %q", v.Region)
writeLinef(b, "zone = %q", v.Zone)
writeLinef(b, "service_account_id = %q", v.ServiceAccountID)
return b.String()
f := hclwrite.NewEmptyFile()
gohcl.EncodeIntoBody(v, f.Body())
return string(f.Bytes())
}
// AzureClusterVariables is user configuration for creating a cluster with Terraform on Azure.
@ -229,21 +237,18 @@ type AzureNodeGroup struct {
// AzureIAMVariables is user configuration for creating the IAM configuration with Terraform on Microsoft Azure.
type AzureIAMVariables struct {
// Region is the Azure region to use. (e.g. westus)
Region string
Region string `hcl:"region" cty:"region"`
// ServicePrincipal is the name of the service principal to use.
ServicePrincipal string
ServicePrincipal string `hcl:"service_principal_name" cty:"service_principal_name"`
// ResourceGroup is the name of the resource group to use.
ResourceGroup string
ResourceGroup string `hcl:"resource_group_name" cty:"resource_group_name"`
}
// String returns a string representation of the IAM-specific variables, formatted as Terraform variables.
func (v *AzureIAMVariables) String() string {
b := &strings.Builder{}
writeLinef(b, "service_principal_name = %q", v.ServicePrincipal)
writeLinef(b, "region = %q", v.Region)
writeLinef(b, "resource_group_name = %q", v.ResourceGroup)
return b.String()
f := hclwrite.NewEmptyFile()
gohcl.EncodeIntoBody(v, f.Body())
return string(f.Bytes())
}
// OpenStackClusterVariables is user configuration for creating a cluster with Terraform on OpenStack.
@ -380,11 +385,6 @@ type QEMUNodeGroup struct {
MemorySize int `hcl:"memory" cty:"memory"`
}
func writeLinef(builder *strings.Builder, format string, a ...any) {
builder.WriteString(fmt.Sprintf(format, a...))
builder.WriteByte('\n')
}
func toPtr[T any](v T) *T {
return &v
}

View file

@ -86,8 +86,8 @@ func TestAWSIAMVariables(t *testing.T) {
}
// test that the variables are correctly rendered
want := `name_prefix = "my-prefix"
region = "eu-central-1"
want := `region = "eu-central-1"
name_prefix = "my-prefix"
`
got := vars.String()
assert.Equal(t, want, got)
@ -162,9 +162,9 @@ func TestGCPIAMVariables(t *testing.T) {
}
// test that the variables are correctly rendered
want := `project_id = "my-project"
region = "eu-central-1"
zone = "eu-central-1a"
want := `project_id = "my-project"
region = "eu-central-1"
zone = "eu-central-1a"
service_account_id = "my-service-account"
`
got := vars.String()
@ -226,9 +226,9 @@ func TestAzureIAMVariables(t *testing.T) {
}
// test that the variables are correctly rendered
want := `service_principal_name = "my-service-principal"
region = "eu-central-1"
resource_group_name = "my-resource-group"
want := `region = "eu-central-1"
service_principal_name = "my-service-principal"
resource_group_name = "my-resource-group"
`
got := vars.String()
assert.Equal(t, want, got)
@ -337,3 +337,34 @@ custom_endpoint = "example.com"
got := vars.String()
assert.Equal(t, want, got)
}
func TestVariablesFromBytes(t *testing.T) {
assert := assert.New(t)
awsVars := AWSIAMVariables{
Region: "test",
}
var loadedAWSVars AWSIAMVariables
err := VariablesFromBytes([]byte(awsVars.String()), &loadedAWSVars)
assert.NoError(err)
assert.Equal(awsVars, loadedAWSVars)
azureVars := AzureIAMVariables{
Region: "test",
}
var loadedAzureVars AzureIAMVariables
err = VariablesFromBytes([]byte(azureVars.String()), &loadedAzureVars)
assert.NoError(err)
assert.Equal(azureVars, loadedAzureVars)
gcpVars := GCPIAMVariables{
Region: "test",
}
var loadedGCPVars GCPIAMVariables
err = VariablesFromBytes([]byte(gcpVars.String()), &loadedGCPVars)
assert.NoError(err)
assert.Equal(gcpVars, loadedGCPVars)
err = VariablesFromBytes([]byte("invalid"), &loadedGCPVars)
assert.Error(err)
}