terraform: gcp node groups (#1941)

* terraform: GCP node groups

* cli: marshal GCP node groups to terraform variables

This does not have any side effects for users.
We still strictly create one control-plane and one worker group.
This is a preparation for enabling customizable node groups in the future.
This commit is contained in:
Malte Poll 2023-06-19 13:02:01 +02:00 committed by GitHub
parent 5823aa2438
commit 2808012c9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 545 additions and 163 deletions

View file

@ -97,6 +97,14 @@ def go_dependencies():
sum = "h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=", sum = "h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=",
version = "v0.0.0-20180502004556-fa1af6a1f4f5", version = "v0.0.0-20180502004556-fa1af6a1f4f5",
) )
go_repository(
name = "com_github_agext_levenshtein",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/agext/levenshtein",
sum = "h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=",
version = "v1.2.1",
)
go_repository( go_repository(
name = "com_github_alcortesm_tgz", name = "com_github_alcortesm_tgz",
@ -249,6 +257,14 @@ def go_dependencies():
sum = "h1:I4z+fAUqvKfvZV/CHi5dV0QuwbmIvYYFDjG0Ss5QpAs=", sum = "h1:I4z+fAUqvKfvZV/CHi5dV0QuwbmIvYYFDjG0Ss5QpAs=",
version = "v0.2.0", version = "v0.2.0",
) )
go_repository(
name = "com_github_apparentlymart_go_dump",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/apparentlymart/go-dump",
sum = "h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA=",
version = "v0.0.0-20180507223929-23540a00eaa3",
)
go_repository( go_repository(
name = "com_github_apparentlymart_go_textseg", name = "com_github_apparentlymart_go_textseg",
@ -3312,6 +3328,14 @@ def go_dependencies():
sum = "h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=", sum = "h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=",
version = "v1.0.0", version = "v1.0.0",
) )
go_repository(
name = "com_github_hashicorp_hcl_v2",
build_file_generation = "on",
build_file_proto_mode = "disable_global",
importpath = "github.com/hashicorp/hcl/v2",
sum = "h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY=",
version = "v2.17.0",
)
go_repository( go_repository(
name = "com_github_hashicorp_logutils", name = "com_github_hashicorp_logutils",

View file

@ -166,18 +166,28 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, opts Create
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) { func (c *Creator) createGCP(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
vars := terraform.GCPClusterVariables{ vars := terraform.GCPClusterVariables{
CommonVariables: terraform.CommonVariables{
Name: opts.Config.Name, Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount, NodeGroups: map[string]terraform.GCPNodeGroup{
CountWorkers: opts.WorkerCount, "control_plane_default": {
Role: "ControlPlane",
StateDiskSizeGB: opts.Config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
InitialCount: opts.ControlPlaneCount,
Zone: opts.Config.Provider.GCP.Zone,
InstanceType: opts.InsType,
DiskType: opts.Config.Provider.GCP.StateDiskType,
},
"worker_default": {
Role: "Worker",
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
InitialCount: opts.WorkerCount,
Zone: opts.Config.Provider.GCP.Zone,
InstanceType: opts.InsType,
DiskType: opts.Config.Provider.GCP.StateDiskType,
},
}, },
Project: opts.Config.Provider.GCP.Project, Project: opts.Config.Provider.GCP.Project,
Region: opts.Config.Provider.GCP.Region, Region: opts.Config.Provider.GCP.Region,
Zone: opts.Config.Provider.GCP.Zone, Zone: opts.Config.Provider.GCP.Zone,
CredentialsFile: opts.Config.Provider.GCP.ServiceAccountKeyPath,
InstanceType: opts.InsType,
StateDiskType: opts.Config.Provider.GCP.StateDiskType,
ImageID: opts.image, ImageID: opts.image,
Debug: opts.Config.IsDebugCluster(), Debug: opts.Config.IsDebugCluster(),
} }

View file

@ -172,6 +172,7 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, file file.Handler
if hasDiff { if hasDiff {
// If there are any Terraform migrations to apply, ask for confirmation // If there are any Terraform migrations to apply, ask for confirmation
fmt.Fprintln(cmd.OutOrStdout(), "The upgrade requires a migration of Constellation cloud resources by applying an updated Terraform template. Please manually review the suggested changes below.")
if !flags.yes { if !flags.yes {
ok, err := askToConfirm(cmd, "Do you want to apply the Terraform migrations?") ok, err := askToConfirm(cmd, "Do you want to apply the Terraform migrations?")
if err != nil { if err != nil {
@ -258,13 +259,26 @@ func (u *upgradeApplyCmd) parseUpgradeVars(cmd *cobra.Command, conf *config.Conf
targets := []string{} targets := []string{}
vars := &terraform.GCPClusterVariables{ vars := &terraform.GCPClusterVariables{
CommonVariables: commonVariables, Name: conf.Name,
NodeGroups: map[string]terraform.GCPNodeGroup{
"control_plane_default": {
Role: "ControlPlane",
StateDiskSizeGB: conf.StateDiskSizeGB,
Zone: conf.Provider.GCP.Zone,
InstanceType: conf.Provider.GCP.InstanceType,
DiskType: conf.Provider.GCP.StateDiskType,
},
"worker_default": {
Role: "Worker",
StateDiskSizeGB: conf.StateDiskSizeGB,
Zone: conf.Provider.GCP.Zone,
InstanceType: conf.Provider.GCP.InstanceType,
DiskType: conf.Provider.GCP.StateDiskType,
},
},
Project: conf.Provider.GCP.Project, Project: conf.Provider.GCP.Project,
Region: conf.Provider.GCP.Region, Region: conf.Provider.GCP.Region,
Zone: conf.Provider.GCP.Zone, Zone: conf.Provider.GCP.Zone,
CredentialsFile: conf.Provider.GCP.ServiceAccountKeyPath,
InstanceType: conf.Provider.GCP.InstanceType,
StateDiskType: conf.Provider.GCP.StateDiskType,
ImageID: imageRef, ImageID: imageRef,
Debug: conf.IsDebugCluster(), Debug: conf.IsDebugCluster(),
} }

View file

@ -83,6 +83,8 @@ go_library(
"@com_github_hashicorp_hc_install//product", "@com_github_hashicorp_hc_install//product",
"@com_github_hashicorp_hc_install//releases", "@com_github_hashicorp_hc_install//releases",
"@com_github_hashicorp_hc_install//src", "@com_github_hashicorp_hc_install//src",
"@com_github_hashicorp_hcl_v2//gohcl",
"@com_github_hashicorp_hcl_v2//hclwrite",
"@com_github_hashicorp_terraform_exec//tfexec", "@com_github_hashicorp_terraform_exec//tfexec",
"@com_github_hashicorp_terraform_json//:terraform-json", "@com_github_hashicorp_terraform_json//:terraform-json",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
@ -94,6 +96,7 @@ go_test(
srcs = [ srcs = [
"loader_test.go", "loader_test.go",
"terraform_test.go", "terraform_test.go",
"variables_test.go",
], ],
embed = [":terraform"], embed = [":terraform"],
deps = [ deps = [
@ -105,5 +108,6 @@ go_test(
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require", "@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
], ],
) )

View file

@ -44,6 +44,20 @@ locals {
cidr_vpc_subnet_nodes = "192.168.178.0/24" cidr_vpc_subnet_nodes = "192.168.178.0/24"
cidr_vpc_subnet_pods = "10.10.0.0/16" cidr_vpc_subnet_pods = "10.10.0.0/16"
kube_env = "AUTOSCALER_ENV_VARS: kube_reserved=cpu=1060m,memory=1019Mi,ephemeral-storage=41Gi;node_labels=;os=linux;os_distribution=cos;evictionHard=" kube_env = "AUTOSCALER_ENV_VARS: kube_reserved=cpu=1060m,memory=1019Mi,ephemeral-storage=41Gi;node_labels=;os=linux;os_distribution=cos;evictionHard="
control_plane_named_ports = flatten([
{ name = "kubernetes", port = local.ports_kubernetes },
{ name = "bootstrapper", port = local.ports_bootstrapper },
{ name = "verify", port = local.ports_verify },
{ name = "konnectivity", port = local.ports_konnectivity },
{ name = "recovery", port = local.ports_recovery },
var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [],
])
node_groups_by_role = {
for name, node_group in var.node_groups : node_group.role => name...
}
control_plane_instance_groups = [
for control_plane in local.node_groups_by_role["ControlPlane"] : module.instance_group[control_plane].instance_group
]
} }
resource "random_id" "uid" { resource "random_id" "uid" {
@ -134,48 +148,26 @@ resource "google_compute_firewall" "firewall_internal_pods" {
allow { protocol = "icmp" } allow { protocol = "icmp" }
} }
module "instance_group_control_plane" {
source = "./modules/instance_group"
name = local.name
role = "ControlPlane"
uid = local.uid
instance_type = var.instance_type
instance_count = var.control_plane_count
image_id = var.image_id
disk_size = var.state_disk_size
disk_type = var.state_disk_type
network = google_compute_network.vpc_network.id
subnetwork = google_compute_subnetwork.vpc_subnetwork.id
alias_ip_range_name = google_compute_subnetwork.vpc_subnetwork.secondary_ip_range[0].range_name
kube_env = local.kube_env
debug = var.debug
named_ports = flatten([
{ name = "kubernetes", port = local.ports_kubernetes },
{ name = "bootstrapper", port = local.ports_bootstrapper },
{ name = "verify", port = local.ports_verify },
{ name = "konnectivity", port = local.ports_konnectivity },
{ name = "recovery", port = local.ports_recovery },
var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [],
])
labels = local.labels
init_secret_hash = local.initSecretHash
}
module "instance_group_worker" { module "instance_group" {
source = "./modules/instance_group" source = "./modules/instance_group"
name = "${local.name}-1" for_each = var.node_groups
role = "Worker" base_name = local.name
node_group_name = each.key
role = each.value.role
zone = each.value.zone
uid = local.uid uid = local.uid
instance_type = var.instance_type instance_type = each.value.instance_type
instance_count = var.worker_count instance_count = each.value.initial_count
image_id = var.image_id image_id = var.image_id
disk_size = var.state_disk_size disk_size = each.value.disk_size
disk_type = var.state_disk_type disk_type = each.value.disk_type
network = google_compute_network.vpc_network.id network = google_compute_network.vpc_network.id
subnetwork = google_compute_subnetwork.vpc_subnetwork.id subnetwork = google_compute_subnetwork.vpc_subnetwork.id
alias_ip_range_name = google_compute_subnetwork.vpc_subnetwork.secondary_ip_range[0].range_name alias_ip_range_name = google_compute_subnetwork.vpc_subnetwork.secondary_ip_range[0].range_name
kube_env = local.kube_env kube_env = local.kube_env
debug = var.debug debug = var.debug
named_ports = each.value.role == "ControlPlane" ? local.control_plane_named_ports : []
labels = local.labels labels = local.labels
init_secret_hash = local.initSecretHash init_secret_hash = local.initSecretHash
} }
@ -189,7 +181,7 @@ module "loadbalancer_kube" {
name = local.name name = local.name
health_check = "HTTPS" health_check = "HTTPS"
backend_port_name = "kubernetes" backend_port_name = "kubernetes"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_kubernetes port = local.ports_kubernetes
frontend_labels = merge(local.labels, { constellation-use = "kubernetes" }) frontend_labels = merge(local.labels, { constellation-use = "kubernetes" })
@ -200,7 +192,7 @@ module "loadbalancer_boot" {
name = local.name name = local.name
health_check = "TCP" health_check = "TCP"
backend_port_name = "bootstrapper" backend_port_name = "bootstrapper"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_bootstrapper port = local.ports_bootstrapper
frontend_labels = merge(local.labels, { constellation-use = "bootstrapper" }) frontend_labels = merge(local.labels, { constellation-use = "bootstrapper" })
@ -211,7 +203,7 @@ module "loadbalancer_verify" {
name = local.name name = local.name
health_check = "TCP" health_check = "TCP"
backend_port_name = "verify" backend_port_name = "verify"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_verify port = local.ports_verify
frontend_labels = merge(local.labels, { constellation-use = "verify" }) frontend_labels = merge(local.labels, { constellation-use = "verify" })
@ -222,7 +214,7 @@ module "loadbalancer_konnectivity" {
name = local.name name = local.name
health_check = "TCP" health_check = "TCP"
backend_port_name = "konnectivity" backend_port_name = "konnectivity"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_konnectivity port = local.ports_konnectivity
frontend_labels = merge(local.labels, { constellation-use = "konnectivity" }) frontend_labels = merge(local.labels, { constellation-use = "konnectivity" })
@ -233,7 +225,7 @@ module "loadbalancer_recovery" {
name = local.name name = local.name
health_check = "TCP" health_check = "TCP"
backend_port_name = "recovery" backend_port_name = "recovery"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_recovery port = local.ports_recovery
frontend_labels = merge(local.labels, { constellation-use = "recovery" }) frontend_labels = merge(local.labels, { constellation-use = "recovery" })
@ -245,8 +237,18 @@ module "loadbalancer_debugd" {
name = local.name name = local.name
health_check = "TCP" health_check = "TCP"
backend_port_name = "debugd" backend_port_name = "debugd"
backend_instance_group = module.instance_group_control_plane.instance_group backend_instance_groups = local.control_plane_instance_groups
ip_address = google_compute_global_address.loadbalancer_ip.self_link ip_address = google_compute_global_address.loadbalancer_ip.self_link
port = local.ports_debugd port = local.ports_debugd
frontend_labels = merge(local.labels, { constellation-use = "debugd" }) frontend_labels = merge(local.labels, { constellation-use = "debugd" })
} }
moved {
from = module.instance_group_control_plane
to = module.instance_group["control_plane_default"]
}
moved {
from = module.instance_group_worker
to = module.instance_group["worker_default"]
}

View file

@ -4,20 +4,42 @@ terraform {
source = "hashicorp/google" source = "hashicorp/google"
version = "4.69.1" version = "4.69.1"
} }
random = {
source = "hashicorp/random"
version = "3.5.1"
}
} }
} }
locals { locals {
# migration: allow the old node group names to work since they were created without the uid
# and without multiple node groups in mind
# node_group: worker_default => name == "<base>-1-worker"
# node_group: control_plane_default => name: "<base>-control-plane"
# new names:
# node_group: foo, role: Worker => name == "<base>-worker-<uid>"
# node_group: bar, role: ControlPlane => name == "<base>-control-plane-<uid>"
role_dashed = var.role == "ControlPlane" ? "control-plane" : "worker" role_dashed = var.role == "ControlPlane" ? "control-plane" : "worker"
name = "${var.name}-${local.role_dashed}" group_uid = random_id.uid.hex
maybe_uid = (var.node_group_name == "control_plane_default" || var.node_group_name == "worker_default") ? "" : "-${local.group_uid}"
maybe_one = var.node_group_name == "worker_default" ? "-1" : ""
name = "${var.base_name}${local.maybe_one}-${local.role_dashed}${local.maybe_uid}"
state_disk_name = "state-disk" state_disk_name = "state-disk"
} }
resource "random_id" "uid" {
byte_length = 4
}
resource "google_compute_instance_template" "template" { resource "google_compute_instance_template" "template" {
name = local.name name = local.name
machine_type = var.instance_type machine_type = var.instance_type
tags = ["constellation-${var.uid}"] // Note that this is also applied as a label tags = ["constellation-${var.uid}"] // Note that this is also applied as a label
labels = merge(var.labels, { constellation-role = local.role_dashed }) labels = merge(var.labels, {
constellation-role = local.role_dashed,
constellation-node-group = var.node_group_name,
})
confidential_instance_config { confidential_instance_config {
enable_confidential_compute = true enable_confidential_compute = true
@ -98,6 +120,7 @@ resource "google_compute_instance_group_manager" "instance_group_manager" {
name = local.name name = local.name
description = "Instance group manager for Constellation" description = "Instance group manager for Constellation"
base_instance_name = local.name base_instance_name = local.name
zone = var.zone
target_size = var.instance_count target_size = var.instance_count
dynamic "stateful_disk" { dynamic "stateful_disk" {

View file

@ -1,8 +1,13 @@
variable "name" { variable "base_name" {
type = string type = string
description = "Base name of the instance group." description = "Base name of the instance group."
} }
variable "node_group_name" {
type = string
description = "Constellation name for the node group (used for configuration and CSP-independent naming)."
}
variable "role" { variable "role" {
type = string type = string
description = "The role of the instance group." description = "The role of the instance group."
@ -84,3 +89,8 @@ variable "alias_ip_range_name" {
type = string type = string
description = "Name of the alias IP range to use." description = "Name of the alias IP range to use."
} }
variable "zone" {
type = string
description = "Zone to deploy the instance group in."
}

View file

@ -41,10 +41,13 @@ resource "google_compute_backend_service" "backend" {
port_name = var.backend_port_name port_name = var.backend_port_name
timeout_sec = 240 timeout_sec = 240
backend { dynamic "backend" {
group = var.backend_instance_group for_each = var.backend_instance_groups
content {
group = backend.value
balancing_mode = "UTILIZATION" balancing_mode = "UTILIZATION"
} }
}
} }
resource "google_compute_target_tcp_proxy" "proxy" { resource "google_compute_target_tcp_proxy" "proxy" {

View file

@ -13,9 +13,9 @@ variable "backend_port_name" {
description = "Name of backend port. The same name should appear in the instance groups referenced by this service." description = "Name of backend port. The same name should appear in the instance groups referenced by this service."
} }
variable "backend_instance_group" { variable "backend_instance_groups" {
type = string type = list(string)
description = "The URL of the instance group resource from which the load balancer will direct traffic." description = "The URLs of the instance group resources from which the load balancer will direct traffic."
} }
variable "ip_address" { variable "ip_address" {

View file

@ -4,20 +4,16 @@ variable "name" {
description = "Base name of the cluster." description = "Base name of the cluster."
} }
variable "control_plane_count" { variable "node_groups" {
type = number type = map(object({
description = "The number of control plane nodes to deploy." role = string
} zone = string
instance_type = string
variable "worker_count" { disk_size = number
type = number disk_type = string
description = "The number of worker nodes to deploy." initial_count = number
} }))
description = "A map of node group names to node group configurations."
variable "state_disk_size" {
type = number
default = 30
description = "The size of the state disk in GB."
} }
variable "project" { variable "project" {
@ -35,17 +31,6 @@ variable "zone" {
description = "The GCP zone to deploy the cluster in." description = "The GCP zone to deploy the cluster in."
} }
variable "instance_type" {
type = string
description = "The GCP instance type to deploy."
}
variable "state_disk_type" {
type = string
default = "pd-ssd"
description = "The type of the state disk."
}
variable "image_id" { variable "image_id" {
type = string type = string
description = "The GCP image to use for the cluster nodes." description = "The GCP image to use for the cluster nodes."

View file

@ -24,8 +24,13 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak"
) )
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestPrepareCluster(t *testing.T) { func TestPrepareCluster(t *testing.T) {
qemuVars := &QEMUVariables{ qemuVars := &QEMUVariables{
CommonVariables: CommonVariables{ CommonVariables: CommonVariables{

View file

@ -9,6 +9,9 @@ package terraform
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclwrite"
) )
// Variables is a struct that holds all variables that are passed to Terraform. // Variables is a struct that holds all variables that are passed to Terraform.
@ -98,40 +101,40 @@ func (v *AWSIAMVariables) String() string {
// GCPClusterVariables is user configuration for creating resources with Terraform on GCP. // GCPClusterVariables is user configuration for creating resources with Terraform on GCP.
type GCPClusterVariables struct { type GCPClusterVariables struct {
// CommonVariables contains common variables. // Name of the cluster.
CommonVariables Name string `hcl:"name" cty:"name"`
// Project is the ID of the GCP project to use. // Project is the ID of the GCP project to use.
Project string Project string `hcl:"project" cty:"project"`
// Region is the GCP region to use. // Region is the GCP region to use.
Region string Region string `hcl:"region" cty:"region"`
// Zone is the GCP zone to use. // Zone is the GCP zone to use.
Zone string Zone string `hcl:"zone" cty:"zone"`
// CredentialsFile is the path to the GCP credentials file.
CredentialsFile string
// InstanceType is the GCP instance type to use.
InstanceType string
// StateDiskType is the GCP disk type to use for the state disk.
StateDiskType string
// ImageID is the ID of the GCP image to use. // ImageID is the ID of the GCP image to use.
ImageID string ImageID string `hcl:"image_id" cty:"image_id"`
// Debug is true if debug mode is enabled. // Debug is true if debug mode is enabled.
Debug bool Debug bool `hcl:"debug" cty:"debug"`
// NodeGroups is a map of node groups to create.
NodeGroups map[string]GCPNodeGroup `hcl:"node_groups" cty:"node_groups"`
}
// GCPNodeGroup is a node group to create on GCP.
type GCPNodeGroup struct {
// Role is the role of the node group.
Role string `hcl:"role" cty:"role"`
// StateDiskSizeGB is the size of the state disk to allocate to each node, in GB.
StateDiskSizeGB int `hcl:"disk_size" cty:"disk_size"`
// InitialCount is the initial number of nodes to create in the node group.
InitialCount int `hcl:"initial_count" cty:"initial_count"`
Zone string `hcl:"zone" cty:"zone"`
InstanceType string `hcl:"instance_type" cty:"instance_type"`
DiskType string `hcl:"disk_type" cty:"disk_type"`
} }
// String returns a string representation of the variables, formatted as Terraform variables. // String returns a string representation of the variables, formatted as Terraform variables.
func (v *GCPClusterVariables) String() string { func (v *GCPClusterVariables) String() string {
b := &strings.Builder{} f := hclwrite.NewEmptyFile()
b.WriteString(v.CommonVariables.String()) gohcl.EncodeIntoBody(v, f.Body())
writeLinef(b, "project = %q", v.Project) return string(f.Bytes())
writeLinef(b, "region = %q", v.Region)
writeLinef(b, "zone = %q", v.Zone)
writeLinef(b, "instance_type = %q", v.InstanceType)
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
writeLinef(b, "image_id = %q", v.ImageID)
writeLinef(b, "debug = %t", v.Debug)
return b.String()
} }
// GCPIAMVariables is user configuration for creating the IAM confioguration with Terraform on GCP. // GCPIAMVariables is user configuration for creating the IAM confioguration with Terraform on GCP.

View file

@ -0,0 +1,287 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package terraform
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAWSClusterVariables(t *testing.T) {
vars := AWSClusterVariables{
CommonVariables: CommonVariables{
Name: "cluster-name",
CountControlPlanes: 1,
CountWorkers: 2,
StateDiskSizeGB: 30,
},
Region: "eu-central-1",
Zone: "eu-central-1a",
AMIImageID: "ami-0123456789abcdef",
InstanceType: "x1.foo",
StateDiskType: "bardisk",
IAMProfileControlPlane: "arn:aws:iam::123456789012:instance-profile/cluster-name-controlplane",
IAMProfileWorkerNodes: "arn:aws:iam::123456789012:instance-profile/cluster-name-worker",
Debug: true,
EnableSNP: true,
}
// test that the variables are correctly rendered
want := `name = "cluster-name"
control_plane_count = 1
worker_count = 2
state_disk_size = 30
region = "eu-central-1"
zone = "eu-central-1a"
ami = "ami-0123456789abcdef"
instance_type = "x1.foo"
state_disk_type = "bardisk"
iam_instance_profile_control_plane = "arn:aws:iam::123456789012:instance-profile/cluster-name-controlplane"
iam_instance_profile_worker_nodes = "arn:aws:iam::123456789012:instance-profile/cluster-name-worker"
debug = true
enable_snp = true
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestAWSIAMVariables(t *testing.T) {
vars := AWSIAMVariables{
Region: "eu-central-1",
Prefix: "my-prefix",
}
// test that the variables are correctly rendered
want := `name_prefix = "my-prefix"
region = "eu-central-1"
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestGCPClusterVariables(t *testing.T) {
vars := GCPClusterVariables{
Name: "cluster-name",
Project: "my-project",
Region: "eu-central-1",
Zone: "eu-central-1a",
ImageID: "image-0123456789abcdef",
Debug: true,
NodeGroups: map[string]GCPNodeGroup{
"control_plane_default": {
Role: "ControlPlane",
StateDiskSizeGB: 30,
InitialCount: 1,
Zone: "eu-central-1a",
InstanceType: "n2d-standard-4",
DiskType: "pd-ssd",
},
"worker_default": {
Role: "Worker",
StateDiskSizeGB: 10,
InitialCount: 1,
Zone: "eu-central-1b",
InstanceType: "n2d-standard-8",
DiskType: "pd-ssd",
},
},
}
// test that the variables are correctly rendered
want := `name = "cluster-name"
project = "my-project"
region = "eu-central-1"
zone = "eu-central-1a"
image_id = "image-0123456789abcdef"
debug = true
node_groups = {
control_plane_default = {
disk_size = 30
disk_type = "pd-ssd"
initial_count = 1
instance_type = "n2d-standard-4"
role = "ControlPlane"
zone = "eu-central-1a"
}
worker_default = {
disk_size = 10
disk_type = "pd-ssd"
initial_count = 1
instance_type = "n2d-standard-8"
role = "Worker"
zone = "eu-central-1b"
}
}
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestGCPIAMVariables(t *testing.T) {
vars := GCPIAMVariables{
Project: "my-project",
Region: "eu-central-1",
Zone: "eu-central-1a",
ServiceAccountID: "my-service-account",
}
// test that the variables are correctly rendered
want := `project_id = "my-project"
region = "eu-central-1"
zone = "eu-central-1a"
service_account_id = "my-service-account"
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestAzureClusterVariables(t *testing.T) {
vars := AzureClusterVariables{
CommonVariables: CommonVariables{
Name: "cluster-name",
CountControlPlanes: 1,
CountWorkers: 2,
StateDiskSizeGB: 30,
},
ResourceGroup: "my-resource-group",
Location: "eu-central-1",
UserAssignedIdentity: "my-user-assigned-identity",
InstanceType: "Standard_D2s_v3",
StateDiskType: "StandardSSD_LRS",
ImageID: "image-0123456789abcdef",
ConfidentialVM: true,
SecureBoot: false,
CreateMAA: true,
Debug: true,
}
// test that the variables are correctly rendered
want := `name = "cluster-name"
control_plane_count = 1
worker_count = 2
state_disk_size = 30
resource_group = "my-resource-group"
location = "eu-central-1"
user_assigned_identity = "my-user-assigned-identity"
instance_type = "Standard_D2s_v3"
state_disk_type = "StandardSSD_LRS"
image_id = "image-0123456789abcdef"
confidential_vm = true
secure_boot = false
create_maa = true
debug = true
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestAzureIAMVariables(t *testing.T) {
vars := AzureIAMVariables{
Region: "eu-central-1",
ServicePrincipal: "my-service-principal",
ResourceGroup: "my-resource-group",
}
// test that the variables are correctly rendered
want := `service_principal_name = "my-service-principal"
region = "eu-central-1"
resource_group_name = "my-resource-group"
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestOpenStackClusterVariables(t *testing.T) {
vars := OpenStackClusterVariables{
CommonVariables: CommonVariables{
Name: "cluster-name",
CountControlPlanes: 1,
CountWorkers: 2,
StateDiskSizeGB: 30,
},
Cloud: "my-cloud",
AvailabilityZone: "az-01",
FlavorID: "flavor-0123456789abcdef",
FloatingIPPoolID: "fip-pool-0123456789abcdef",
StateDiskType: "performance-8",
ImageURL: "https://example.com/image.raw",
DirectDownload: true,
OpenstackUserDomainName: "my-user-domain",
OpenstackUsername: "my-username",
OpenstackPassword: "my-password",
Debug: true,
}
// test that the variables are correctly rendered
want := `name = "cluster-name"
control_plane_count = 1
worker_count = 2
state_disk_size = 30
cloud = "my-cloud"
availability_zone = "az-01"
flavor_id = "flavor-0123456789abcdef"
floating_ip_pool_id = "fip-pool-0123456789abcdef"
image_url = "https://example.com/image.raw"
direct_download = true
state_disk_type = "performance-8"
openstack_user_domain_name = "my-user-domain"
openstack_username = "my-username"
openstack_password = "my-password"
debug = true
`
got := vars.String()
assert.Equal(t, want, got)
}
func TestQEMUClusterVariables(t *testing.T) {
vars := QEMUVariables{
CommonVariables: CommonVariables{
Name: "cluster-name",
CountControlPlanes: 1,
CountWorkers: 2,
StateDiskSizeGB: 30,
},
LibvirtURI: "qemu:///system",
LibvirtSocketPath: "/var/run/libvirt/libvirt-sock",
BootMode: "uefi",
CPUCount: 4,
MemorySizeMiB: 8192,
ImagePath: "/var/lib/libvirt/images/cluster-name.qcow2",
ImageFormat: "raw",
MetadataAPIImage: "example.com/metadata-api:latest",
MetadataLibvirtURI: "qemu:///system",
NVRAM: "production",
Firmware: "/usr/share/OVMF/OVMF_CODE.fd",
BzImagePath: "/var/lib/libvirt/images/cluster-name-bzimage",
InitrdPath: "/var/lib/libvirt/images/cluster-name-initrd",
KernelCmdline: "console=ttyS0,115200n8",
}
// test that the variables are correctly rendered
want := `name = "cluster-name"
control_plane_count = 1
worker_count = 2
state_disk_size = 30
libvirt_uri = "qemu:///system"
libvirt_socket_path = "/var/run/libvirt/libvirt-sock"
constellation_os_image = "/var/lib/libvirt/images/cluster-name.qcow2"
image_format = "raw"
constellation_boot_mode = "uefi"
constellation_kernel = "/var/lib/libvirt/images/cluster-name-bzimage"
constellation_initrd = "/var/lib/libvirt/images/cluster-name-initrd"
constellation_cmdline = "console=ttyS0,115200n8"
vcpus = 4
memory = 8192
metadata_api_image = "example.com/metadata-api:latest"
metadata_libvirt_uri = "qemu:///system"
nvram = "/usr/share/OVMF/constellation_vars.production.fd"
firmware = "/usr/share/OVMF/OVMF_CODE.fd"
`
got := vars.String()
assert.Equal(t, want, got)
}

2
go.mod
View file

@ -126,6 +126,7 @@ require (
require ( require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
@ -248,6 +249,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl/v2 v2.17.0
github.com/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect

4
go.sum
View file

@ -188,6 +188,8 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/
github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -841,6 +843,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
github.com/hashicorp/hc-install v0.5.2 h1:SfwMFnEXVVirpwkDuSF5kymUOhrUxrTq3udEseZdOD0= github.com/hashicorp/hc-install v0.5.2 h1:SfwMFnEXVVirpwkDuSF5kymUOhrUxrTq3udEseZdOD0=
github.com/hashicorp/hc-install v0.5.2/go.mod h1:9QISwe6newMWIfEiXpzuu1k9HAGtQYgnSH8H9T8wmoI= github.com/hashicorp/hc-install v0.5.2/go.mod h1:9QISwe6newMWIfEiXpzuu1k9HAGtQYgnSH8H9T8wmoI=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY=
github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=

View file

@ -87,6 +87,7 @@ require (
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect
@ -192,6 +193,7 @@ require (
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.5.2 // indirect github.com/hashicorp/hc-install v0.5.2 // indirect
github.com/hashicorp/hcl/v2 v2.17.0 // indirect
github.com/hashicorp/terraform-exec v0.18.1 // indirect github.com/hashicorp/terraform-exec v0.18.1 // indirect
github.com/hashicorp/terraform-json v0.15.0 // indirect github.com/hashicorp/terraform-json v0.15.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect

View file

@ -167,6 +167,8 @@ github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -821,6 +823,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
github.com/hashicorp/hc-install v0.5.2 h1:SfwMFnEXVVirpwkDuSF5kymUOhrUxrTq3udEseZdOD0= github.com/hashicorp/hc-install v0.5.2 h1:SfwMFnEXVVirpwkDuSF5kymUOhrUxrTq3udEseZdOD0=
github.com/hashicorp/hc-install v0.5.2/go.mod h1:9QISwe6newMWIfEiXpzuu1k9HAGtQYgnSH8H9T8wmoI= github.com/hashicorp/hc-install v0.5.2/go.mod h1:9QISwe6newMWIfEiXpzuu1k9HAGtQYgnSH8H9T8wmoI=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY=
github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=