AB#2579 Add constellation iam create command (#624)

This commit is contained in:
Moritz Sanft 2022-12-07 11:48:54 +01:00 committed by GitHub
parent be01cf7129
commit 286803fb97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 2029 additions and 108 deletions

View file

@ -11,11 +11,9 @@ import (
"embed"
"errors"
"io/fs"
"path"
"path/filepath"
"strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/spf13/afero"
)
@ -29,9 +27,8 @@ var terraformFS embed.FS
// prepareWorkspace loads the embedded Terraform files,
// and writes them into the workspace.
func prepareWorkspace(fileHandler file.Handler, provider cloudprovider.Provider, workingDir string) error {
// use path.Join to ensure no forward slashes are used to read the embedded FS
rootDir := path.Join("terraform", strings.ToLower(provider.String()))
func prepareWorkspace(path string, fileHandler file.Handler, workingDir string) error {
rootDir := path
return fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err

View file

@ -8,7 +8,9 @@ package terraform
import (
"io/fs"
"path"
"path/filepath"
"strings"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -21,11 +23,13 @@ import (
func TestLoader(t *testing.T) {
testCases := map[string]struct {
pathBase string
provider cloudprovider.Provider
fileList []string
testAlreadyUnpacked bool
}{
"aws": {
"awsCluster": {
pathBase: "terraform",
provider: cloudprovider.AWS,
fileList: []string{
"main.tf",
@ -34,7 +38,8 @@ func TestLoader(t *testing.T) {
"modules",
},
},
"gcp": {
"gcpCluster": {
pathBase: "terraform",
provider: cloudprovider.GCP,
fileList: []string{
"main.tf",
@ -43,7 +48,8 @@ func TestLoader(t *testing.T) {
"modules",
},
},
"qemu": {
"qemuCluster": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
fileList: []string{
"main.tf",
@ -52,7 +58,35 @@ func TestLoader(t *testing.T) {
"modules",
},
},
"gcpIAM": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
fileList: []string{
"main.tf",
"variables.tf",
"outputs.tf",
},
},
"azureIAM": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
fileList: []string{
"main.tf",
"variables.tf",
"outputs.tf",
},
},
"awsIAM": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
fileList: []string{
"main.tf",
"variables.tf",
"outputs.tf",
},
},
"continue on (partially) unpacked": {
pathBase: "terraform",
provider: cloudprovider.AWS,
fileList: []string{
"main.tf",
@ -71,14 +105,16 @@ func TestLoader(t *testing.T) {
file := file.NewHandler(afero.NewMemMapFs())
err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir)
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
err := prepareWorkspace(path, file, constants.TerraformWorkingDir)
require.NoError(err)
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
if tc.testAlreadyUnpacked {
// Let's try the same again and check if we don't get a "file already exists" error.
require.NoError(file.Remove(filepath.Join(constants.TerraformWorkingDir, "variables.tf")))
err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir)
err := prepareWorkspace(path, file, constants.TerraformWorkingDir)
assert.NoError(err)
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
}

View file

@ -61,8 +61,8 @@ func New(ctx context.Context, workingDir string) (*Client, error) {
}
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
func (c *Client) PrepareWorkspace(provider cloudprovider.Provider, vars Variables) error {
if err := prepareWorkspace(c.file, provider, c.workingDir); err != nil {
func (c *Client) PrepareWorkspace(path string, vars Variables) error {
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {
return err
}
@ -109,6 +109,141 @@ func (c *Client) CreateCluster(ctx context.Context) (string, string, error) {
return ip, secret, nil
}
// 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
ApplicationID string
UAMIID string
ApplicationClientSecretValue string
}
// AWSIAMOutput contains the output information of the Terraform IAM operation on GCP.
type AWSIAMOutput struct {
ControlPlaneInstanceProfile string
WorkerNodeInstanceProfile string
}
// CreateIAMConfig creates an IAM configuration using Terraform.
func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (IAMOutput, error) {
if err := c.tf.Init(ctx); err != nil {
return IAMOutput{}, err
}
if err := c.tf.Apply(ctx); err != nil {
return IAMOutput{}, err
}
tfState, err := c.tf.Show(ctx)
if err != nil {
return IAMOutput{}, err
}
switch provider {
case cloudprovider.GCP:
saKeyOutputRaw, ok := tfState.Values.Outputs["sa_key"]
if !ok {
return IAMOutput{}, errors.New("no service account key output found")
}
saKeyOutput, ok := saKeyOutputRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in service account key output: not a string")
}
return IAMOutput{
GCP: GCPIAMOutput{
SaKey: saKeyOutput,
},
}, nil
case cloudprovider.Azure:
subscriptionIDRaw, ok := tfState.Values.Outputs["subscription_id"]
if !ok {
return IAMOutput{}, errors.New("no subscription id output found")
}
subscriptionIDOutput, ok := subscriptionIDRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in subscription id output: not a string")
}
tenantIDRaw, ok := tfState.Values.Outputs["tenant_id"]
if !ok {
return IAMOutput{}, errors.New("no tenant id output found")
}
tenantIDOutput, ok := tenantIDRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in tenant id output: not a string")
}
applicationIDRaw, ok := tfState.Values.Outputs["application_id"]
if !ok {
return IAMOutput{}, errors.New("no application id output found")
}
applicationIDOutput, ok := applicationIDRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in application id output: not a string")
}
uamiIDRaw, ok := tfState.Values.Outputs["uami_id"]
if !ok {
return IAMOutput{}, errors.New("no UAMI id output found")
}
uamiIDOutput, ok := uamiIDRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in UAMI id output: not a string")
}
appClientSecretRaw, ok := tfState.Values.Outputs["application_client_secret_value"]
if !ok {
return IAMOutput{}, errors.New("no application client secret value output found")
}
appClientSecretOutput, ok := appClientSecretRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in application client secret valueoutput: not a string")
}
return IAMOutput{
Azure: AzureIAMOutput{
SubscriptionID: subscriptionIDOutput,
TenantID: tenantIDOutput,
ApplicationID: applicationIDOutput,
UAMIID: uamiIDOutput,
ApplicationClientSecretValue: appClientSecretOutput,
},
}, nil
case cloudprovider.AWS:
controlPlaneProfileRaw, ok := tfState.Values.Outputs["control_plane_instance_profile"]
if !ok {
return IAMOutput{}, errors.New("no control plane instance profile output found")
}
controlPlaneProfileOutput, ok := controlPlaneProfileRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in control plane instance profile output: not a string")
}
workerNodeProfileRaw, ok := tfState.Values.Outputs["worker_nodes_instance_profile"]
if !ok {
return IAMOutput{}, errors.New("no worker node instance profile output found")
}
workerNodeProfileOutput, ok := workerNodeProfileRaw.Value.(string)
if !ok {
return IAMOutput{}, errors.New("invalid type in worker node instance profile output: not a string")
}
return IAMOutput{
AWS: AWSIAMOutput{
ControlPlaneInstanceProfile: controlPlaneProfileOutput,
WorkerNodeInstanceProfile: workerNodeProfileOutput,
},
}, nil
default:
return IAMOutput{}, errors.New("unsupported cloud provider")
}
}
// DestroyCluster destroys a Constellation cluster using Terraform.
func (c *Client) DestroyCluster(ctx context.Context) error {
if err := c.tf.Init(ctx); err != nil {

View file

@ -0,0 +1,61 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
version = "4.45.0"
constraints = "4.45.0"
hashes = [
"h1:/zHxqox3/4xqLyZq71sfwkQMBMorroA/JF5eBgkYjxs=",
"h1:2Q1ZOA3hNuS0cJvyl5HcKELREDE9ULMrxFu6TTS6ZFA=",
"h1:AKX4R3U+kBpQB5oU08kSrzl5CLsMhbK+BKZVrwYDXZQ=",
"h1:EQ5L9Mfn1TLcSvslSYJ3xfrunQPzSi6jXewWqQZ7uRI=",
"h1:IKlAzeFeTENTaXij4jVZqOhZ9sJDxcJODV12p2QQ+Rs=",
"h1:J/XjRsEJIpxi+mczXQfnH3nvfACv3LRDtrthQJCIibY=",
"h1:SldVY6f+D5uiApQNemxU5jsmwsbzuSY14Gcs5axMmIw=",
"h1:VKmkAkwT5y56lSDzRMU4U/1TuCOt/W4PrTVEV7QO5xw=",
"h1:ZFb6RqY48Fe+18sOC62wiE38XYPaTg98RJZO+EnsvCU=",
"h1:aXWTWsY7wOAFvbyS4asC5L6TAnz1OWUj403tQdKTZjo=",
"h1:hJ8sqJFjzU/rc5mXEocl1b0wOrbkrsuOvMBP7vPdL8g=",
"h1:iNgrQzAnLH2z11JZghsx+mZ2qIMrwOCP1EL290Ehhko=",
"h1:jAJjxVeu+cHso5A/4DBVnaoed0sonKirD33Z/4GZB90=",
"h1:xwleZVEjEfgCEom/Pfk4kMY0RZllUEAx62NLbXSItFI=",
"zh:22da03786f25658a000d1bcc28c780816a97e7e8a1f59fff6eee7d452830e95e",
"zh:2543be56eee0491eb0c79ca1c901dcbf71da26625961fe719f088263fef062f4",
"zh:31a1da1e3beedfd88c3c152ab505bdcf330427f26b75835885526f7bb75c4857",
"zh:4409afe50f225659d5f378fe9303a45052953a1219f7f1acc82b69d07528b7ba",
"zh:4dadec3b783f10d2f8eef3dab5e817baae9c932a7967d45fe3d77fcbcbdaa438",
"zh:55be80d6e24828dcb0db7a0226fb275415c1c0ad63dd2f33b76f3ac0cd64e6a6",
"zh:560bba29efb7dbe0bfcc937369d88817aa31a8d18aa25395b1afe2576cb04495",
"zh:6caacc202e83438ff63d5d96733e283f44e349668d96c6b1c5c7df463ebf85cc",
"zh:6cabab83a61d5b4ac801c5a5d57556a0e76ec8dc879d28cf777509db5f6a657e",
"zh:96c4528bf9c16edb8841b68479ec51c499ed7fa680462fa28caeab3fc168bb43",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:cdc0b47ff840d708fbf75abfe86d23dc7f1dffdd233a771822a17b5c637f4769",
"zh:d9a9583e82776d1ebb6cf6c3d47acc2b302f8778f470ceffe7579dc794eb1feb",
"zh:e9367ca9f6f6418a23cdf8d01f29dd0c4f614e78499f52a767a422e4c334b915",
"zh:f6d355a2fb3bcebb597f68bbca4fa2aaa364efd29240236c582375e219d77656",
]
}
provider "registry.terraform.io/hashicorp/random" {
version = "3.4.3"
hashes = [
"h1:hV66lcagXXRwwCW3Y542bI1JgPo8z/taYKT7K+a2Z5U=",
"h1:hXUPrH8igYBhatzatkp80RCeeUJGu9lQFDyKemOlsTo=",
"h1:saZR+mhthL0OZl4SyHXZraxyaBNVMxiZzks78nWcZ2o=",
"h1:tL3katm68lX+4lAncjQA9AXL4GR/VM+RPwqYf4D2X8Q=",
"h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=",
"zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
"zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
"zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
"zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
"zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
"zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
"zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
"zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
"zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
"zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
]
}

View file

@ -0,0 +1,191 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.45.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = var.region
}
resource "random_id" "uid" {
byte_length = 8
}
resource "aws_iam_instance_profile" "control_plane_instance_profile" {
name = "${var.name_prefix}_control_plane_instance_profile"
role = aws_iam_role.control_plane_role.name
}
resource "aws_iam_role" "control_plane_role" {
name = "${var.name_prefix}_control_plane_role"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "control_plane_policy" {
name = "${var.name_prefix}_control_plane_policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeLaunchConfigurations",
"autoscaling:DescribeTags",
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ec2:DescribeRouteTables",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVolumes",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:ModifyInstanceAttribute",
"ec2:ModifyVolume",
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateRoute",
"ec2:DeleteRoute",
"ec2:DeleteSecurityGroup",
"ec2:DeleteVolume",
"ec2:DetachVolume",
"ec2:RevokeSecurityGroupIngress",
"ec2:DescribeVpcs",
"elasticloadbalancing:AddTags",
"elasticloadbalancing:AttachLoadBalancerToSubnets",
"elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:CreateLoadBalancerPolicy",
"elasticloadbalancing:CreateLoadBalancerListeners",
"elasticloadbalancing:ConfigureHealthCheck",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:DeleteLoadBalancerListeners",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DetachLoadBalancerFromSubnets",
"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
"elasticloadbalancing:SetLoadBalancerPoliciesForBackendServer",
"elasticloadbalancing:AddTags",
"elasticloadbalancing:CreateListener",
"elasticloadbalancing:CreateTargetGroup",
"elasticloadbalancing:DeleteListener",
"elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:DescribeLoadBalancerPolicies",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:ModifyListener",
"elasticloadbalancing:ModifyTargetGroup",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:SetLoadBalancerPoliciesOfListener",
"iam:CreateServiceLinkedRole",
"kms:DescribeKey",
"logs:DescribeLogGroups",
"logs:ListTagsLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"tag:GetResources",
"ec2:DescribeImages"
],
"Resource": [
"*"
]
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "attach_control_plane_policy" {
role = aws_iam_role.control_plane_role.name
policy_arn = aws_iam_policy.control_plane_policy.arn
}
resource "aws_iam_instance_profile" "worker_node_instance_profile" {
name = "${var.name_prefix}_worker_node_instance_profile"
role = aws_iam_role.control_plane_role.name
}
resource "aws_iam_role" "worker_node_role" {
name = "${var.name_prefix}_worker_node_role"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "worker_node_policy" {
name = "${var.name_prefix}_worker_node_policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeRegions",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:BatchGetImage",
"logs:DescribeLogGroups",
"logs:ListTagsLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"tag:GetResources",
"ec2:DescribeImages"
],
"Resource": "*"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "attach_worker_node_policy" {
role = aws_iam_role.worker_node_role.name
policy_arn = aws_iam_policy.worker_node_policy.arn
}

View file

@ -0,0 +1,7 @@
output "control_plane_instance_profile" {
value = aws_iam_instance_profile.control_plane_instance_profile.name
}
output "worker_nodes_instance_profile" {
value = aws_iam_instance_profile.worker_node_instance_profile.name
}

View file

@ -0,0 +1,10 @@
variable "name_prefix" {
type = string
description = "Prefix for all resources"
}
variable "region" {
type = string
description = "AWS region"
default = "us-east-2"
}

View file

@ -0,0 +1,62 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/azuread" {
version = "2.31.0"
constraints = "2.31.0"
hashes = [
"h1:0D8+cQBlCyA50NiiTJwNDK9QjKfZsjuHgXTFRlhIZyg=",
"h1:6bwCuD30Fz9DCkg8aaiod3qVJ72nMJQfmiSMat1K2PQ=",
"h1:7vAGKxHE8I17dlZZQ3lnVaySilJH7m6fsOjiFH1yHLk=",
"h1:BJrmwBctF1fOk2U2O7YX1OyDPHcBoPn78PRsZRkY6xs=",
"h1:BWeCrUwFDA14mJO7UjhA8PfwaNUB/xuxAIJkMnMqjVc=",
"h1:Fl5ASgCAamGAcwb0+3C+SrU8JVlWjiThDR9wybvbHjs=",
"h1:M5ELmP8Qi/vGanQ5nYaD3AStPUh8xOof1QgqaUM6V1M=",
"h1:ONdNfK0goPkMBPVe7D/ne13M3ZiMe+AzhPVv98f2V40=",
"h1:kP4Zt6MO9CiQq57Lk1S4SWs11UVdwMSah8liskireLE=",
"h1:sUL16Z8OsgIgeVJAXp1IqEs4VYoB9U8Iu/FNISuGj8I=",
"h1:tH/R1ZHc+Vt0Kf51BLLsp/KhG/7K4v94LDbrzJZTp+c=",
"zh:02a64db03707cc6970ab28a1da00d7fa011cc54e8a7806209f31bd8aad1794e1",
"zh:077ffce8135a57544ec3c227bbe0ee5f6ca649223bd1dc0bbbd31d3fdf616830",
"zh:0a369de6132edb0f4a69f2aa472b23f9bb5c430a3d539146d1c18d4cc7b12c7f",
"zh:14bfc5f2354c1389eb7ed8bf5a5eaadc9940e18c2dd15058eb9b48ea5c37ae66",
"zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7",
"zh:5629f020ac3409ad34a39e221fb2e63f82948c3eb936508331d5a7f870556e9d",
"zh:5b419eb59fa4e0b9c520c5cd5028f236bce6d9ab701c5ccca23cc040d3d690c4",
"zh:5e7e6207fd58a3e9ba54b7333169a3e3ea693c25c8f477622536600a8a09a3f1",
"zh:a9a552ad36d7a3db4554c6fbc716cf8631328331ea6188eddb4038b4c213ff46",
"zh:aee812d33916e5fdfb4d58ce74af0f3b2a7a58dbfb5ec8e0b42b5780ceff5414",
"zh:ce46738cd1909675b980bb90b9c3d919a4d1d655b4296082b86b6622ce818f7a",
"zh:db02dbe5ce139610688b354b15eb934f9f67ab32d6c5d63690dce6f9b8d90904",
]
}
provider "registry.terraform.io/hashicorp/azurerm" {
version = "3.34.0"
constraints = "3.34.0"
hashes = [
"h1:2FU6urJTxBaqA8KxC3rX1p+0bbJzDSYKPS+yPTF3cZU=",
"h1:7W2o3Hr5R6ZfD7J2ECOW4KHuIAY++GBg6aCKEtVSlo0=",
"h1:7x4Ov+xzUS4OiHrAhkG2y55VyJLKDZFCuyzfNAgQGoo=",
"h1:HA0WJNi5l9ZKN1mJrLfrJmP2Pe21Z0ugwyiCx7lKt8g=",
"h1:LR1I99AYQG8H4+RRmqDgqGqycDC7haqOChyMLBorkTA=",
"h1:XHe5dgOYCc1YNPGeffx98N/D+L4cjKpZb5tPaljor2M=",
"h1:ZiYxI829dERQ0Ytx8fwZNEIti74DsdCNEWHNvAJI+2s=",
"h1:pCUVVzQpBNUoPMyy0vwBeHwTL8GawDRfjSpZWVjX7WA=",
"h1:tYHnH0a3l3/IEqlYRjhx9NCPBMIZnFzDzVFLlMZw/FE=",
"h1:vLxp+DNhF9PsnbG/IFX2Vu3436Biit4s2mo5h2GPWqk=",
"h1:yf/fBkEZQU/fTXpkrZLkMtXr9VVrCl8zYxkpEgvHUJ0=",
"zh:04a3860959a9626469714a9986561ff04697fb6fe268cac6481ee570c3c20519",
"zh:3191647b011cd094c7db1f5709f46e0df7190ab8dad1896e15e763384273931c",
"zh:4428e5503fa614dec1ca3ea33d9479835a1c048a03cdec364ad8ad3340a3e137",
"zh:576df51dfba37c40983552f98077125c2eb12eb4e105bb805e935c75c73a7181",
"zh:5c1f4939a1e9ae96a977058c5056018f6b37220f1d0408531c89ea3295735f81",
"zh:644ebea720c22b3f665f9e087ad57122ce5727631b3d437a425fb97a44515a01",
"zh:87250563eed16db793ae9c309200f074f3b42acb4a44fdef4b26b9f7e988931e",
"zh:b8fff7fb51234eb13a8f3a0107ef6fc8033e28c3b4a1087fc837dfc7706d3274",
"zh:e21ecae5989348e9cbf07295f355a05dcae4758019d81c517f55b45e83a3e0e7",
"zh:ece35f508eda2edf5d4867a6e5ad2e24904278813cfce70e19063d310e66d790",
"zh:f421c6068713237fffce12f504fd5888b668352a22cb1075845fd612954ac3ec",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View file

@ -0,0 +1,84 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.34.0"
}
azuread = {
source = "hashicorp/azuread"
version = "2.31.0"
}
}
}
# Configure Azure resource management provider
provider "azurerm" {
features {}
}
# Configure Azure active directory provider
provider "azuread" {
tenant_id = data.azurerm_subscription.current.tenant_id
}
# Access current subscription (available via Azure CLI)
data "azurerm_subscription" "current" {}
# # Access current AzureAD configuration
data "azuread_client_config" "current" {}
# Create base resource group
resource "azurerm_resource_group" "base_resource_group" {
name = var.resource_group_name
location = var.region
}
# Create identity resource group
resource "azurerm_resource_group" "identity_resource_group" {
name = "${var.resource_group_name}-identity"
location = var.region
}
# Create managed identity
resource "azurerm_user_assigned_identity" "identity_uami" {
location = var.region
name = var.service_principal_name
resource_group_name = azurerm_resource_group.identity_resource_group.name
}
# Assign roles to managed identity
resource "azurerm_role_assignment" "virtual_machine_contributor_role" {
scope = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/resourceGroups/${var.resource_group_name}"
role_definition_name = "Virtual Machine Contributor"
principal_id = azurerm_user_assigned_identity.identity_uami.principal_id
}
resource "azurerm_role_assignment" "application_insights_component_contributor_role" {
scope = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/resourceGroups/${var.resource_group_name}"
role_definition_name = "Application Insights Component Contributor"
principal_id = azurerm_user_assigned_identity.identity_uami.principal_id
}
# Create application registration
resource "azuread_application" "base_application" {
display_name = "${var.resource_group_name}-application"
owners = [data.azuread_client_config.current.object_id]
}
resource "azuread_service_principal" "application_principal" {
application_id = azuread_application.base_application.application_id
app_role_assignment_required = false
owners = [data.azuread_client_config.current.object_id]
}
# Set identity as base resource group owner
resource "azurerm_role_assignment" "owner_role" {
scope = "/subscriptions/${data.azurerm_subscription.current.subscription_id}/resourceGroups/${var.resource_group_name}"
role_definition_name = "Owner"
principal_id = azuread_service_principal.application_principal.object_id
}
# Create application secret (password)
resource "azuread_application_password" "base_application_secret" {
application_object_id = azuread_application.base_application.object_id
}

View file

@ -0,0 +1,20 @@
output "subscription_id" {
value = data.azurerm_subscription.current.subscription_id
}
output "tenant_id" {
value = data.azurerm_subscription.current.tenant_id
}
output "application_id" {
value = azuread_application.base_application.application_id
}
output "uami_id" {
value = azurerm_user_assigned_identity.identity_uami.id
}
output "application_client_secret_value" {
value = azuread_application_password.base_application_secret.value
sensitive = true
}

View file

@ -0,0 +1,14 @@
variable "resource_group_name" {
type = string
description = "Resource group name"
}
variable "service_principal_name" {
type = string
description = "Service principal name"
}
variable "region" {
type = string
description = "Azure resource location"
}

View file

@ -0,0 +1,42 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/google" {
version = "4.44.0"
constraints = "4.44.0"
hashes = [
"h1:JpShTtgnxpiIVnr0R2Lccrh84mnrf7Z1/v/yw0UZ1gI=",
"h1:JzDVTkBVHTw67N7S9bD/+u7U3aipjutF4MZx0eplONw=",
"h1:OTNLlWoTq+SdbbtgLK7uFVAn3aP9QVjIGYU2ndKsz+Q=",
"h1:PSIkDVwksHe9oZd+XP369N8U+6/+SPF8Z5wHkcwmWKw=",
"h1:d6Ds7aGeW5M8+AgWfkfbh3HTf3tP8ePjYGhoJnOxklU=",
"h1:enGPHgPHCbmLU8BIHLHcOwRlbQnPPQzdmXNoFVs1lIg=",
"h1:gmUUWhuuY/YRIllvVBRGl1kUHqsNBHQ/4BHdwKQbzXQ=",
"h1:hMhGerpfzsbfQ3DFsAGwJYpSIK6an+wMGx2I/lwaNuY=",
"h1:iotFu763eGWVC4zfXkNuo3RUeQLDvwVX2E7R/2sNxCg=",
"h1:mheXqRMLbMeTr8/E6UakMhWwIL0HqwIHYBE2u2Sbldg=",
"zh:0b424cab24856dc47177733145fa61b731f345a6a42a0c0b7910ccfcf4e8c8a2",
"zh:0c6b3049957b942e1dbc6f8c38de653a78ff1efe40a7cfc506e60a8da2775591",
"zh:1e1d282abd9dbd30602ca52050f875deeb4d79b3b06cda69535db4863f1b98a0",
"zh:35b7bc7c4da98e47bc7827fe1c39400811c9af459e03c99217195830f6bb0e78",
"zh:5ea45d229afad298bdac80324ec9d6968443d54ed1d1a01fad70b3c1bd16e53b",
"zh:652b740a7f75d716daf0fa9b2ef1964944eb4f8b0b26834dd8659a6ac2f3ed52",
"zh:75ee47f9a28826de301c3c6d2fb7915414f3841b1d0248cad6b94697fb8ee634",
"zh:89222d36d8060beb13df6758d6d9b2d075fa809e90a910a2ce1a867cfa6ff654",
"zh:a8c04acc69a65cb68b91ec08aa89c4953840dad33482c9acf4cc0272375b3bf4",
"zh:b71c10a8167cb6c7c3ae174c8c181a06dc82564f097f89602c3d74e8a7627e92",
"zh:bb9a92b640cf0596edcc510ddd20725637c1ff295054f727277108a4a3c9baec",
"zh:bccae696aa84ff7978c6a81b25d61f722fc87261e4b524ec062392916c8494ab",
"zh:bcd028cd233287420ecfbe4102e59e351e6fd22a4a14698e6896c45fb0509a1e",
"zh:bd9d096abdc42a3cf5849ae8adc9c8ca327c026e6f6f287fd436b6adfc8630dc",
"zh:c3577ea597c9f8707b86a3056f59ff28c35c77b17392dd75a5dbe46617f7f7c6",
"zh:d44915d60ffb54edf48ff9a1c2661b358497a9ecb900e8a3b3983f083ea198ce",
"zh:d5dfcc1949e12fcc2d9ac834296872bded5b4201f2c492eb127872a72080b56b",
"zh:d9dc91823cbcd60464d5b168d7bd34a5b5f13d5b37786110226bfe139ea24782",
"zh:e8647c8ab63144013446b73c695a01f6bef16712613f1461d1c0bc37e1ba80d6",
"zh:ecd0cd61f13d401240301691f3042abca874a5c481a90b4f791c0e4ea10b6448",
"zh:ed01ea31e457d6c4e01a5d6dfd6ad3d09a0a58ff7dc4de494bf559fbc34fa936",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:ffb268a1b2b401175667a7e421100ce8964f6b32cab723766e8eb0b993b59e66",
]
}

View file

@ -0,0 +1,69 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.44.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
zone = var.zone
}
resource "google_service_account" "service_account" {
account_id = var.service_account_id
display_name = "Constellation service account"
description = "Service account used inside Constellation"
}
resource "google_project_iam_binding" "instance_admin_role" {
project = var.project_id
role = "roles/compute.instanceAdmin.v1"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
resource "google_project_iam_binding" "network_admin_role" {
project = var.project_id
role = "roles/compute.networkAdmin"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
resource "google_project_iam_binding" "security_admin_role" {
project = var.project_id
role = "roles/compute.securityAdmin"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
resource "google_project_iam_binding" "storage_admin_role" {
project = var.project_id
role = "roles/compute.storageAdmin"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
resource "google_project_iam_binding" "iam_service_account_user_role" {
project = var.project_id
role = "roles/iam.serviceAccountUser"
members = [
"serviceAccount:${google_service_account.service_account.email}",
]
}
resource "google_service_account_key" "service_account_key" {
service_account_id = google_service_account.service_account.name
}

View file

@ -0,0 +1,4 @@
output "sa_key" {
value = google_service_account_key.service_account_key.private_key
sensitive = true
}

View file

@ -0,0 +1,19 @@
variable "project_id" {
type = string
description = "GCP Project ID"
}
variable "service_account_id" {
type = string
description = "ID for the service account being created. Must match ^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$"
}
variable "region" {
type = string
description = "Region used for constellation clusters. Needs to have the N2D machine type available."
}
variable "zone" {
type = string
description = "Zone used for constellation clusters. Needs to be within the specified region."
}

View file

@ -10,7 +10,9 @@ import (
"context"
"errors"
"io/fs"
"path"
"path/filepath"
"strings"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -40,6 +42,7 @@ func TestPrepareCluster(t *testing.T) {
}
testCases := map[string]struct {
pathBase string
provider cloudprovider.Provider
vars Variables
fs afero.Fs
@ -47,17 +50,20 @@ func TestPrepareCluster(t *testing.T) {
wantErr bool
}{
"qemu": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
fs: afero.NewMemMapFs(),
wantErr: false,
},
"no vars": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
fs: afero.NewMemMapFs(),
wantErr: true,
},
"continue on partially extracted": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
fs: afero.NewMemMapFs(),
@ -65,6 +71,7 @@ func TestPrepareCluster(t *testing.T) {
wantErr: false,
},
"prepare workspace fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
@ -83,12 +90,107 @@ func TestPrepareCluster(t *testing.T) {
workingDir: constants.TerraformWorkingDir,
}
err := c.PrepareWorkspace(tc.provider, tc.vars)
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
err := c.PrepareWorkspace(path, tc.vars)
// Test case: Check if we can continue to create on an incomplete workspace.
if tc.partiallyExtracted {
require.NoError(c.file.Remove(filepath.Join(c.workingDir, "main.tf")))
err = c.PrepareWorkspace(tc.provider, tc.vars)
err = c.PrepareWorkspace(path, tc.vars)
}
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
})
}
}
func TestPrepareIAM(t *testing.T) {
gcpVars := &GCPIAMVariables{
Project: "const-1234",
Region: "europe-west1",
Zone: "europe-west1-a",
ServiceAccountID: "const-test-case",
}
azureVars := &AzureIAMVariables{
Region: "westus",
ServicePrincipal: "constell-test-sp",
ResourceGroup: "constell-test-rg",
}
awsVars := &AWSIAMVariables{
Region: "eu-east-2a",
Prefix: "test",
}
testCases := map[string]struct {
pathBase string
provider cloudprovider.Provider
vars Variables
fs afero.Fs
partiallyExtracted bool
wantErr bool
}{
"no vars": {
pathBase: path.Join("terraform", "iam"),
fs: afero.NewMemMapFs(),
wantErr: true,
},
"invalid path": {
pathBase: path.Join("abc", "123"),
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
fs: afero.NewMemMapFs(),
wantErr: false,
},
"continue on partially extracted": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
fs: afero.NewMemMapFs(),
partiallyExtracted: true,
wantErr: false,
},
"azure": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
fs: afero.NewMemMapFs(),
wantErr: false,
},
"aws": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
fs: afero.NewMemMapFs(),
wantErr: false,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
c := &Client{
tf: &stubTerraform{},
file: file.NewHandler(tc.fs),
workingDir: constants.TerraformIAMWorkingDir,
}
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
err := c.PrepareWorkspace(path, tc.vars)
// Test case: Check if we can continue to create on an incomplete workspace.
if tc.partiallyExtracted {
require.NoError(c.file.Remove(filepath.Join(c.workingDir, "main.tf")))
err = c.PrepareWorkspace(path, tc.vars)
}
if tc.wantErr {
@ -132,6 +234,7 @@ func TestCreateCluster(t *testing.T) {
}
testCases := map[string]struct {
pathBase string
provider cloudprovider.Provider
vars Variables
tf *stubTerraform
@ -139,12 +242,14 @@ func TestCreateCluster(t *testing.T) {
wantErr bool
}{
"works": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(),
},
"init fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{initErr: someErr},
@ -152,6 +257,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true,
},
"apply fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{applyErr: someErr},
@ -159,6 +265,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true,
},
"show fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{showErr: someErr},
@ -166,6 +273,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true,
},
"no ip": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{
@ -179,6 +287,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true,
},
"ip has wrong type": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{
@ -204,7 +313,8 @@ func TestCreateCluster(t *testing.T) {
workingDir: constants.TerraformWorkingDir,
}
require.NoError(c.PrepareWorkspace(tc.provider, tc.vars))
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars))
ip, initSecret, err := c.CreateCluster(context.Background())
if tc.wantErr {
@ -218,6 +328,282 @@ func TestCreateCluster(t *testing.T) {
}
}
func TestCreateIAM(t *testing.T) {
someErr := errors.New("failed")
newTestState := func() *tfjson.State {
workingState := tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"sa_key": {
Value: "12345678_abcdefg",
},
"subscription_id": {
Value: "test_subscription_id",
},
"tenant_id": {
Value: "test_tenant_id",
},
"application_id": {
Value: "test_application_id",
},
"uami_id": {
Value: "test_uami_id",
},
"application_client_secret_value": {
Value: "test_application_client_secret_value",
},
"control_plane_instance_profile": {
Value: "test_control_plane_instance_profile",
},
"worker_nodes_instance_profile": {
Value: "test_worker_nodes_instance_profile",
},
},
},
}
return &workingState
}
gcpVars := &GCPIAMVariables{
Project: "const-1234",
Region: "europe-west1",
Zone: "europe-west1-a",
ServiceAccountID: "const-test-case",
}
azureVars := &AzureIAMVariables{
Region: "westus",
ServicePrincipal: "constell-test-sp",
ResourceGroup: "constell-test-rg",
}
awsVars := &AWSIAMVariables{
Region: "eu-east-2a",
Prefix: "test",
}
testCases := map[string]struct {
pathBase string
provider cloudprovider.Provider
vars Variables
tf *stubTerraform
fs afero.Fs
wantErr bool
want IAMOutput
}{
"gcp works": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(),
want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}},
},
"gcp init fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{initErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp apply fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{applyErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp show fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{showErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp no sa_key": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp sa_key has wrong type": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{"sa_key": {Value: 42}},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure works": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(),
want: IAMOutput{Azure: AzureIAMOutput{
SubscriptionID: "test_subscription_id",
TenantID: "test_tenant_id",
ApplicationID: "test_application_id",
ApplicationClientSecretValue: "test_application_client_secret_value",
UAMIID: "test_uami_id",
}},
},
"azure init fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{initErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure apply fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{applyErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure show fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{showErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure no subscription_id": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure subscription_id has wrong type": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.Azure,
vars: azureVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{"subscription_id": {Value: 42}},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"aws works": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{showState: newTestState()},
fs: afero.NewMemMapFs(),
want: IAMOutput{AWS: AWSIAMOutput{
ControlPlaneInstanceProfile: "test_control_plane_instance_profile",
WorkerNodeInstanceProfile: "test_worker_nodes_instance_profile",
}},
},
"aws init fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{initErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"aws apply fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{applyErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"aws show fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{showErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"aws no control_plane_instance_profile": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"azure control_plane_instance_profile has wrong type": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.AWS,
vars: awsVars,
tf: &stubTerraform{
showState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{"control_plane_instance_profile": {Value: 42}},
},
},
},
fs: afero.NewMemMapFs(),
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
c := &Client{
tf: tc.tf,
file: file.NewHandler(tc.fs),
workingDir: constants.TerraformIAMWorkingDir,
}
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars))
IAMoutput, err := c.CreateIAMConfig(context.Background(), tc.provider)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.want, IAMoutput)
})
}
}
func TestDestroyInstances(t *testing.T) {
testCases := map[string]struct {
tf *stubTerraform

View file

@ -39,8 +39,8 @@ func (v *CommonVariables) String() string {
return b.String()
}
// AWSVariables is user configuration for creating a cluster with Terraform on GCP.
type AWSVariables struct {
// AWSClusterVariables is user configuration for creating a cluster with Terraform on GCP.
type AWSClusterVariables struct {
// CommonVariables contains common variables.
CommonVariables
// Region is the AWS region to use.
@ -61,8 +61,40 @@ type AWSVariables struct {
Debug bool
}
// GCPVariables is user configuration for creating a cluster with Terraform on GCP.
type GCPVariables struct {
func (v *AWSClusterVariables) String() string {
b := &strings.Builder{}
b.WriteString(v.CommonVariables.String())
writeLinef(b, "region = %q", v.Region)
writeLinef(b, "zone = %q", v.Zone)
writeLinef(b, "ami = %q", v.AMIImageID)
writeLinef(b, "instance_type = %q", v.InstanceType)
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
writeLinef(b, "iam_instance_profile_control_plane = %q", v.IAMProfileControlPlane)
writeLinef(b, "iam_instance_profile_worker_nodes = %q", v.IAMProfileWorkerNodes)
writeLinef(b, "debug = %t", v.Debug)
return b.String()
}
// 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
// Prefix is the name prefix of the resources to use.
Prefix string
}
// 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()
}
// GCPClusterVariables is user configuration for creating resources with Terraform on GCP.
type GCPClusterVariables struct {
// CommonVariables contains common variables.
CommonVariables
@ -84,23 +116,8 @@ type GCPVariables struct {
Debug bool
}
func (v *AWSVariables) String() string {
b := &strings.Builder{}
b.WriteString(v.CommonVariables.String())
writeLinef(b, "region = %q", v.Region)
writeLinef(b, "zone = %q", v.Zone)
writeLinef(b, "ami = %q", v.AMIImageID)
writeLinef(b, "instance_type = %q", v.InstanceType)
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
writeLinef(b, "iam_instance_profile_control_plane = %q", v.IAMProfileControlPlane)
writeLinef(b, "iam_instance_profile_worker_nodes = %q", v.IAMProfileWorkerNodes)
writeLinef(b, "debug = %t", v.Debug)
return b.String()
}
// String returns a string representation of the variables, formatted as Terraform variables.
func (v *GCPVariables) String() string {
func (v *GCPClusterVariables) String() string {
b := &strings.Builder{}
b.WriteString(v.CommonVariables.String())
writeLinef(b, "project = %q", v.Project)
@ -114,8 +131,31 @@ func (v *GCPVariables) String() string {
return b.String()
}
// AzureVariables is user configuration for creating a cluster with Terraform on Azure.
type AzureVariables 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
// Region is the GCP region to use.
Region string
// Zone is the GCP zone to use.
Zone string
// ServiceAccountID is the ID of the service account to use.
ServiceAccountID string
}
// 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()
}
// AzureClusterVariables is user configuration for creating a cluster with Terraform on Azure.
type AzureClusterVariables struct {
// CommonVariables contains common variables.
CommonVariables
@ -140,7 +180,7 @@ type AzureVariables struct {
}
// String returns a string representation of the variables, formatted as Terraform variables.
func (v *AzureVariables) String() string {
func (v *AzureClusterVariables) String() string {
b := &strings.Builder{}
b.WriteString(v.CommonVariables.String())
writeLinef(b, "resource_group = %q", v.ResourceGroup)
@ -156,6 +196,26 @@ func (v *AzureVariables) String() string {
return b.String()
}
// 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
// ServicePrincipal is the name of the service principal to use.
ServicePrincipal string
// ResourceGroup is the name of the resource group to use.
ResourceGroup string
}
// 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()
}
// QEMUVariables is user configuration for creating a QEMU cluster with Terraform.
type QEMUVariables struct {
// CommonVariables contains common variables.