Skip to content

Commit

Permalink
cli: allow tagging cloud resources with custom tags (#3033)
Browse files Browse the repository at this point in the history
  • Loading branch information
miampf authored Apr 19, 2024
1 parent f60c133 commit b187966
Show file tree
Hide file tree
Showing 27 changed files with 172 additions and 42 deletions.
12 changes: 12 additions & 0 deletions cli/internal/cloudcmd/tfvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func awsTerraformVars(conf *config.Config, imageRef string) *terraform.AWSCluste
EnableSNP: conf.GetAttestationConfig().GetVariant().Equal(variant.AWSSEVSNP{}),
CustomEndpoint: conf.CustomEndpoint,
InternalLoadBalancer: conf.InternalLoadBalancer,
AdditionalTags: conf.Tags,
}
}

Expand Down Expand Up @@ -158,6 +159,7 @@ func azureTerraformVars(conf *config.Config, imageRef string) (*terraform.AzureC
CustomEndpoint: conf.CustomEndpoint,
InternalLoadBalancer: conf.InternalLoadBalancer,
MarketplaceImage: nil,
AdditionalTags: conf.Tags,
}

if conf.UseMarketplaceImage() {
Expand Down Expand Up @@ -226,6 +228,7 @@ func gcpTerraformVars(conf *config.Config, imageRef string) *terraform.GCPCluste
CustomEndpoint: conf.CustomEndpoint,
InternalLoadBalancer: conf.InternalLoadBalancer,
CCTechnology: ccTech,
AdditionalLabels: conf.Tags,
}
}

Expand Down Expand Up @@ -261,6 +264,14 @@ func openStackTerraformVars(conf *config.Config, imageRef string) (*terraform.Op
StateDiskType: group.StateDiskType,
}
}

// since openstack does not support tags in the form of key = value, the tags will be converted
// to an array of "key=value" strings
tags := []string{}
for key, value := range conf.Tags {
tags = append(tags, fmt.Sprintf("%s=%s", key, value))
}

return &terraform.OpenStackClusterVariables{
Name: conf.Name,
Cloud: toPtr(conf.Provider.OpenStack.Cloud),
Expand All @@ -272,6 +283,7 @@ func openStackTerraformVars(conf *config.Config, imageRef string) (*terraform.Op
CustomEndpoint: conf.CustomEndpoint,
InternalLoadBalancer: conf.InternalLoadBalancer,
STACKITProjectID: conf.Provider.OpenStack.STACKITProjectID,
AdditionalTags: tags,
}, nil
}

Expand Down
33 changes: 33 additions & 0 deletions cli/internal/cmd/configgenerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func newConfigGenerateCmd() *cobra.Command {
}
cmd.Flags().StringP("kubernetes", "k", semver.MajorMinor(string(config.Default().KubernetesVersion)), "Kubernetes version to use in format MAJOR.MINOR")
cmd.Flags().StringP("attestation", "a", "", fmt.Sprintf("attestation variant to use %s. If not specified, the default for the cloud provider is used", printFormattedSlice(variant.GetAvailableAttestationVariants())))
cmd.Flags().StringSliceP("tags", "t", nil, "additional tags for created resources given a list of key=value")

return cmd
}
Expand All @@ -45,6 +46,7 @@ type generateFlags struct {
rootFlags
k8sVersion versions.ValidK8sVersion
attestationVariant variant.Variant
tags cloudprovider.Tags
}

func (f *generateFlags) parse(flags *pflag.FlagSet) error {
Expand All @@ -64,6 +66,12 @@ func (f *generateFlags) parse(flags *pflag.FlagSet) error {
}
f.attestationVariant = variant

tags, err := parseTagsFlags(flags)
if err != nil {
return err
}
f.tags = tags

return nil
}

Expand Down Expand Up @@ -99,6 +107,7 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file
return fmt.Errorf("creating config: %w", err)
}
conf.KubernetesVersion = cg.flags.k8sVersion
conf.Tags = cg.flags.tags
cg.log.Debug("Writing YAML data to configuration file")
if err := fileHandler.WriteYAML(constants.ConfigFilename, conf, file.OptMkdirAll); err != nil {
return fmt.Errorf("writing config file: %w", err)
Expand Down Expand Up @@ -221,3 +230,27 @@ func parseAttestationFlag(flags *pflag.FlagSet) (variant.Variant, error) {

return attestationVariant, nil
}

func parseTagsFlags(flags *pflag.FlagSet) (cloudprovider.Tags, error) {
tagsSlice, err := flags.GetStringSlice("tags")
if err != nil {
return nil, fmt.Errorf("getting tags flag: %w", err)
}

// no tags given
if tagsSlice == nil {
return nil, nil
}

tags := make(cloudprovider.Tags)
for _, tag := range tagsSlice {
tagSplit := strings.Split(tag, "=")
if len(tagSplit) != 2 {
return nil, fmt.Errorf("wrong format of tags: expected \"key=value\", got %q", tag)
}

tags[tagSplit[0]] = tagSplit[1]
}

return tags, nil
}
10 changes: 9 additions & 1 deletion cli/internal/terraform/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package terraform
import (
"fmt"

"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
Expand Down Expand Up @@ -69,6 +70,8 @@ type AWSClusterVariables struct {
CustomEndpoint string `hcl:"custom_endpoint" cty:"custom_endpoint"`
// InternalLoadBalancer is true if an internal load balancer should be created.
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
// AdditionalTags describes (optional) additional tags that should be applied to created resources.
AdditionalTags cloudprovider.Tags `hcl:"additional_tags" cty:"additional_tags"`
}

// GetCreateMAA gets the CreateMAA variable.
Expand Down Expand Up @@ -138,6 +141,8 @@ type GCPClusterVariables struct {
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
// CCTechnology is the confidential computing technology to use on the VMs. (`SEV` or `SEV_SNP`)
CCTechnology string `hcl:"cc_technology" cty:"cc_technology"`
// AdditionalLables are (optional) additional labels that should be applied to created resources.
AdditionalLabels cloudprovider.Tags `hcl:"additional_labels" cty:"additional_labels"`
}

// GetCreateMAA gets the CreateMAA variable.
Expand Down Expand Up @@ -214,6 +219,8 @@ type AzureClusterVariables struct {
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
// MarketplaceImage is the (optional) Azure Marketplace image to use.
MarketplaceImage *AzureMarketplaceImageVariables `hcl:"marketplace_image" cty:"marketplace_image"`
// AdditionalTags are (optional) additional tags that get applied to created resources.
AdditionalTags cloudprovider.Tags `hcl:"additional_tags" cty:"additional_tags"`
}

// GetCreateMAA gets the CreateMAA variable.
Expand Down Expand Up @@ -295,7 +302,8 @@ type OpenStackClusterVariables struct {
// CustomEndpoint is the (optional) custom dns hostname for the kubernetes api server.
CustomEndpoint string `hcl:"custom_endpoint" cty:"custom_endpoint"`
// InternalLoadBalancer is true if an internal load balancer should be created.
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
InternalLoadBalancer bool `hcl:"internal_load_balancer" cty:"internal_load_balancer"`
AdditionalTags []string `hcl:"additional_tags" cty:"additional_tags"`
}

// GetCreateMAA gets the CreateMAA variable.
Expand Down
4 changes: 4 additions & 0 deletions cli/internal/terraform/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ node_groups = {
}
custom_endpoint = "example.com"
internal_load_balancer = false
additional_tags = null
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
Expand Down Expand Up @@ -153,6 +154,7 @@ node_groups = {
custom_endpoint = "example.com"
internal_load_balancer = false
cc_technology = "SEV_SNP"
additional_labels = null
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
Expand Down Expand Up @@ -231,6 +233,7 @@ marketplace_image = {
publisher = "edgelesssys"
version = "2.13.0"
}
additional_tags = null
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
Expand Down Expand Up @@ -294,6 +297,7 @@ image_id = "8e10b92d-8f7a-458c-91c6-59b42f82ef81"
debug = true
custom_endpoint = "example.com"
internal_load_balancer = false
additional_tags = null
`
got := vars.String()
assert.Equal(t, strings.Fields(want), strings.Fields(got)) // to ignore whitespace differences
Expand Down
1 change: 1 addition & 0 deletions docs/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags]
-a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-tdx|azure-trustedlaunch|gcp-sev-es|gcp-sev-snp|qemu-vtpm}. If not specified, the default for the cloud provider is used
-h, --help help for generate
-k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.28")
-t, --tags strings additional tags for created resources given a list of key=value
```

### Options inherited from parent commands
Expand Down
3 changes: 3 additions & 0 deletions internal/cloud/cloudprovider/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
// Provider is cloud provider used by the CLI.
type Provider uint32

// Tags is the type that holds additional tags for cloud resources.
type Tags map[string]string

const (
// Unknown is default value for Provider.
Unknown Provider = iota
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ type Config struct {
// The Kubernetes Service CIDR to be used for the cluster. This value will only be used during the first initialization of the Constellation.
ServiceCIDR string `yaml:"serviceCIDR" validate:"omitempty,cidrv4"`
// description: |
// Additional tags that are applied to created resources.
Tags cloudprovider.Tags `yaml:"tags" validate:"omitempty"`
// description: |
// Supported cloud providers and their specific configurations.
Provider ProviderConfig `yaml:"provider" validate:"dive"`
// description: |
Expand Down Expand Up @@ -322,6 +325,7 @@ func Default() *Config {
KubernetesVersion: versions.Default,
DebugCluster: toPtr(false),
ServiceCIDR: "10.96.0.0/12",
Tags: cloudprovider.Tags{},
Provider: ProviderConfig{
AWS: &AWSConfig{
Region: "",
Expand Down
31 changes: 18 additions & 13 deletions internal/config/config_doc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions terraform/infrastructure/aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ resource "random_password" "init_secret" {

resource "aws_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
tags = merge(local.tags, { Name = "${local.name}-vpc" })
tags = merge(local.tags, var.additional_tags, { Name = "${local.name}-vpc" })
}

module "public_private_subnet" {
Expand All @@ -79,7 +79,7 @@ module "public_private_subnet" {
cidr_vpc_subnet_internet = "192.168.0.0/20"
zone = var.zone
zones = local.zones
tags = local.tags
tags = merge(local.tags, var.additional_tags)
}

resource "aws_eip" "lb" {
Expand All @@ -89,14 +89,14 @@ resource "aws_eip" "lb" {
# control-plane.
for_each = var.internal_load_balancer ? [] : toset([var.zone])
domain = "vpc"
tags = merge(local.tags, { "constellation-ip-endpoint" = each.key == var.zone ? "legacy-primary-zone" : "additional-zone" })
tags = merge(local.tags, var.additional_tags, { "constellation-ip-endpoint" = each.key == var.zone ? "legacy-primary-zone" : "additional-zone" })
}

resource "aws_lb" "front_end" {
name = "${local.name}-loadbalancer"
internal = var.internal_load_balancer
load_balancer_type = "network"
tags = local.tags
tags = merge(local.tags, var.additional_tags)
security_groups = [aws_security_group.security_group.id]

dynamic "subnet_mapping" {
Expand All @@ -123,7 +123,7 @@ resource "aws_security_group" "security_group" {
name = local.name
vpc_id = aws_vpc.vpc.id
description = "Security group for ${local.name}"
tags = local.tags
tags = merge(local.tags, var.additional_tags)

egress {
from_port = 0
Expand Down Expand Up @@ -171,7 +171,7 @@ module "load_balancer_targets" {
healthcheck_path = each.value.name == "kubernetes" ? "/readyz" : ""
vpc_id = aws_vpc.vpc.id
lb_arn = aws_lb.front_end.arn
tags = local.tags
tags = merge(local.tags, var.additional_tags)
}

module "instance_group" {
Expand All @@ -194,6 +194,7 @@ module "instance_group" {
enable_snp = var.enable_snp
tags = merge(
local.tags,
var.additional_tags,
{ Name = "${local.name}-${each.value.role}" },
{ constellation-role = each.value.role },
{ constellation-node-group = each.key },
Expand All @@ -212,4 +213,5 @@ module "jump_host" {
ports = [for port in local.load_balancer_ports : port.port]
security_groups = [aws_security_group.security_group.id]
iam_instance_profile = var.iam_instance_profile_name_worker_nodes
additional_tags = var.additional_tags
}
4 changes: 2 additions & 2 deletions terraform/infrastructure/aws/modules/jump_host/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ resource "aws_instance" "jump_host" {
subnet_id = var.subnet_id
vpc_security_group_ids = var.security_groups

tags = {
tags = merge(var.additional_tags, {
"Name" = "${var.base_name}-jump-host"
}
})

user_data = <<EOF
#!/bin/bash
Expand Down
5 changes: 5 additions & 0 deletions terraform/infrastructure/aws/modules/jump_host/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ variable "security_groups" {
type = list(string)
description = "List of IDs of the security groups for an instance."
}

variable "additional_tags" {
type = map(any)
description = "Additional tags for the jump host."
}
5 changes: 5 additions & 0 deletions terraform/infrastructure/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ variable "enable_snp" {
default = true
description = "Enable AMD SEV SNP. Setting this to true sets the cpu-option AmdSevSnp to enable."
}

variable "additional_tags" {
type = map(any)
description = "Additional tags that should be applied to created resources."
}
Loading

0 comments on commit b187966

Please sign in to comment.