From d250fc8b0647a244e2a30468122489a99ff06b8a Mon Sep 17 00:00:00 2001 From: Thomas De Meyer Date: Fri, 6 Sep 2024 14:02:25 +0200 Subject: [PATCH] fix: added all azurem backend options --- .../unreleased/Fixed-20240906-140210.yaml | 3 + .../expected/main/test-1/component-1/main.tf | 4 +- .../azure-basic/expected/main/test-1/main.tf | 4 +- internal/state/azure.go | 164 +++++++++++- internal/state/azure_test.go | 239 +++++++++++++++++- internal/state/schemas/azure.schema.json | 97 ++++++- 6 files changed, 485 insertions(+), 26 deletions(-) create mode 100644 .changes/unreleased/Fixed-20240906-140210.yaml diff --git a/.changes/unreleased/Fixed-20240906-140210.yaml b/.changes/unreleased/Fixed-20240906-140210.yaml new file mode 100644 index 00000000..f6d9765c --- /dev/null +++ b/.changes/unreleased/Fixed-20240906-140210.yaml @@ -0,0 +1,3 @@ +kind: Fixed +body: Added all the terraform azurem backend options +time: 2024-09-06T14:02:10.609294121+02:00 diff --git a/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/component-1/main.tf b/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/component-1/main.tf index 73fde2c2..32f6a0da 100755 --- a/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/component-1/main.tf +++ b/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/component-1/main.tf @@ -2,10 +2,10 @@ # SiteComponent: test-1/component-1 terraform { backend "azurerm" { - resource_group_name = "resourcegroupid" + key = "test-1/component-1" storage_account_name = "storageaccount" container_name = "container-name" - key = "test-1/component-1" + resource_group_name = "resourcegroupid" } required_providers {} } diff --git a/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/main.tf b/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/main.tf index 7b01061e..afe70e3b 100755 --- a/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/main.tf +++ b/internal/cmd/testdata/cases/generate/azure-basic/expected/main/test-1/main.tf @@ -2,10 +2,10 @@ # site: test-1 terraform { backend "azurerm" { - resource_group_name = "resourcegroupid" + key = "test-1" storage_account_name = "storageaccount" container_name = "container-name" - key = "test-1" + resource_group_name = "resourcegroupid" } required_providers {} } diff --git a/internal/state/azure.go b/internal/state/azure.go index c3b19b60..51ae3eef 100644 --- a/internal/state/azure.go +++ b/internal/state/azure.go @@ -6,10 +6,30 @@ import ( ) type AzureState struct { - ResourceGroup string `mapstructure:"resource_group"` - StorageAccount string `mapstructure:"storage_account"` - ContainerName string `mapstructure:"container_name"` - StateFolder string `mapstructure:"state_folder"` + StorageAccount string `mapstructure:"storage_account"` + ContainerName string `mapstructure:"container_name"` + Environment string `mapstructure:"environment"` + Endpoint string `mapstructure:"endpoint"` + MetadataHost string `mapstructure:"metadata_host"` + Snapshot bool `mapstructure:"snapshot"` + ResourceGroup string `mapstructure:"resource_group"` + MsiEndpoint string `mapstructure:"msi_endpoint"` + SubscriptionId string `mapstructure:"subscription_id"` + TenantId string `mapstructure:"tenant_id"` + UseMsi bool `mapstructure:"use_msi"` + OidcRequestUrl string `mapstructure:"oidc_request_url"` + OidcRequestToken string `mapstructure:"oidc_request_token"` + OidcToken string `mapstructure:"oidc_token"` + OidcTokenFilePath string `mapstructure:"oidc_token_file_path"` + UseOidc bool `mapstructure:"use_oidc"` + SasToken string `mapstructure:"sas_token"` + AccessKey string `mapstructure:"access_key"` + UseAzureAdAuth bool `mapstructure:"use_azuread_auth"` + ClientId string `mapstructure:"client_id"` + ClientSecret string `mapstructure:"client_secret"` + ClientCertificatePassword string `mapstructure:"client_certificate_password"` + ClientCertificatePath string `mapstructure:"client_certificate_path"` + StateFolder string `mapstructure:"state_folder"` } func (a AzureState) Identifier(identifier string) string { @@ -35,10 +55,72 @@ func (ar *AzureRenderer) Backend() (string, error) { tpl := ` backend "azurerm" { - resource_group_name = "{{ .State.ResourceGroup }}" + key = "{{ .Identifier }}" storage_account_name = "{{ .State.StorageAccount }}" - container_name = "{{ .State.ContainerName }}" - key = "{{ .Identifier }}" + container_name = "{{ .State.ContainerName }}" + {{- if .State.Environment }} + environment = "{{ .State.Environment }}" + {{- end }} + {{- if .State.Endpoint }} + endpoint = "{{ .State.Endpoint }}" + {{- end }} + {{- if .State.MetadataHost }} + metadata_host = "{{ .State.MetadataHost }}" + {{- end }} + {{- if .State.Snapshot }} + snapshot = true + {{- end }} + {{- if .State.ResourceGroup }} + resource_group_name = "{{ .State.ResourceGroup }}" + {{- end }} + {{- if .State.MsiEndpoint }} + msi_endpoint = "{{ .State.MsiEndpoint }}" + {{- end }} + {{- if .State.SubscriptionId }} + subscription_id = "{{ .State.SubscriptionId }}" + {{- end }} + {{- if .State.TenantId }} + tenant_id = "{{ .State.TenantId }}" + {{- end }} + {{- if .State.UseMsi }} + use_msi = true + {{- end }} + {{- if .State.OidcRequestUrl }} + oidc_request_url = "{{ .State.OidcRequestUrl }}" + {{- end }} + {{- if .State.OidcRequestToken }} + oidc_request_token = "{{ .State.OidcRequestToken }}" + {{- end }} + {{- if .State.OidcToken }} + oidc_token = "{{ .State.OidcToken }}" + {{- end }} + {{- if .State.OidcTokenFilePath }} + oidc_token_file_path = "{{ .State.OidcTokenFilePath }}" + {{- end }} + {{- if .State.UseOidc }} + use_oidc = true + {{- end }} + {{- if .State.SasToken }} + sas_token = "{{ .State.SasToken }}" + {{- end }} + {{- if .State.AccessKey }} + access_key = "{{ .State.AccessKey }}" + {{- end }} + {{- if .State.UseAzureAdAuth }} + use_azuread_auth = true + {{- end }} + {{- if .State.ClientId }} + client_id = "{{ .State.ClientId }}" + {{- end }} + {{- if .State.ClientSecret }} + client_secret = "{{ .State.ClientSecret }}" + {{- end }} + {{- if .State.ClientCertificatePassword }} + client_certificate_password = "{{ .State.ClientCertificatePassword }}" + {{- end }} + {{- if .State.ClientCertificatePath }} + client_certificate_path = "{{ .State.ClientCertificatePath }}" + {{- end }} } ` return utils.RenderGoTemplate(tpl, templateContext) @@ -60,10 +142,72 @@ func (ar *AzureRenderer) RemoteState() (string, error) { backend = "azurerm" config = { - resource_group_name = "{{ .State.ResourceGroup }}" + key = "{{ .Identifier }}" storage_account_name = "{{ .State.StorageAccount }}" - container_name = "{{ .State.ContainerName }}" - key = "{{ .Identifier }}" + container_name = "{{ .State.ContainerName }}" + {{- if .State.Environment }} + environment = "{{ .State.Environment }}" + {{- end }} + {{- if .State.Endpoint }} + endpoint = "{{ .State.Endpoint }}" + {{- end }} + {{- if .State.MetadataHost }} + metadata_host = "{{ .State.MetadataHost }}" + {{- end }} + {{- if .State.Snapshot }} + snapshot = true + {{- end }} + {{- if .State.ResourceGroup }} + resource_group_name = "{{ .State.ResourceGroup }}" + {{- end }} + {{- if .State.MsiEndpoint }} + msi_endpoint = "{{ .State.MsiEndpoint }}" + {{- end }} + {{- if .State.SubscriptionId }} + subscription_id = "{{ .State.SubscriptionId }}" + {{- end }} + {{- if .State.TenantId }} + tenant_id = "{{ .State.TenantId }}" + {{- end }} + {{- if .State.UseMsi }} + use_msi = true + {{- end }} + {{- if .State.OidcRequestUrl }} + oidc_request_url = "{{ .State.OidcRequestUrl }}" + {{- end }} + {{- if .State.OidcRequestToken }} + oidc_request_token = "{{ .State.OidcRequestToken }}" + {{- end }} + {{- if .State.OidcToken }} + oidc_token = "{{ .State.OidcToken }}" + {{- end }} + {{- if .State.OidcTokenFilePath }} + oidc_token_file_path = "{{ .State.OidcTokenFilePath }}" + {{- end }} + {{- if .State.UseOidc }} + use_oidc = true + {{- end }} + {{- if .State.SasToken }} + sas_token = "{{ .State.SasToken }}" + {{- end }} + {{- if .State.AccessKey }} + access_key = "{{ .State.AccessKey }}" + {{- end }} + {{- if .State.UseAzureAdAuth }} + use_azuread_auth = true + {{- end }} + {{- if .State.ClientId }} + client_id = "{{ .State.ClientId }}" + {{- end }} + {{- if .State.ClientSecret }} + client_secret = "{{ .State.ClientSecret }}" + {{- end }} + {{- if .State.ClientCertificatePassword }} + client_certificate_password = "{{ .State.ClientCertificatePassword }}" + {{- end }} + {{- if .State.ClientCertificatePath }} + client_certificate_path = "{{ .State.ClientCertificatePath }}" + {{- end }} } } ` diff --git a/internal/state/azure_test.go b/internal/state/azure_test.go index 5fabc79b..b43e6082 100644 --- a/internal/state/azure_test.go +++ b/internal/state/azure_test.go @@ -5,12 +5,15 @@ import ( "testing" ) -func TestAzureRendererBackendFull(t *testing.T) { +func TestAzureRendererBackendBase(t *testing.T) { r := AzureRenderer{ state: &AzureState{ - ResourceGroup: "resource_group", StorageAccount: "storage_account", ContainerName: "container_name", + Environment: "public", + Endpoint: "https://management.azure.com", + MetadataHost: "http://metadata", + Snapshot: true, StateFolder: "state_folder", }, BaseRenderer: BaseRenderer{ @@ -23,20 +26,235 @@ func TestAzureRendererBackendFull(t *testing.T) { assert.NoError(t, err) assert.Equal(t, ` backend "azurerm" { - resource_group_name = "resource_group" + key = "state_folder/test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + environment = "public" + endpoint = "https://management.azure.com" + metadata_host = "http://metadata" + snapshot = true + } + `, b) +} + +func TestAzureRendererBackendMsi(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + ResourceGroup: "resource_group", + MsiEndpoint: "http://msi", + SubscriptionId: "subscription_id", + TenantId: "tenant_id", + UseMsi: true, + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + resource_group_name = "resource_group" + msi_endpoint = "http://msi" + subscription_id = "subscription_id" + tenant_id = "tenant_id" + use_msi = true + } + `, b) +} + +func TestAzureRendererBackendOIDC(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + OidcRequestUrl: "http://oidc", + OidcRequestToken: "request-token", + OidcToken: "token", + OidcTokenFilePath: "token-file-path", + UseOidc: true, + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + oidc_request_url = "http://oidc" + oidc_request_token = "request-token" + oidc_token = "token" + oidc_token_file_path = "token-file-path" + use_oidc = true + } + `, b) +} + +func TestAzureRendererBackendSas(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + SasToken: "sas-token", + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + sas_token = "sas-token" + } + `, b) +} + +func TestAzureRendererBackendAccessKey(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + AccessKey: "access-key", + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" storage_account_name = "storage_account" - container_name = "container_name" - key = "state_folder/test-1/component-1" + container_name = "container_name" + access_key = "access-key" } `, b) } -func TestAzureRendererRemoteStateFull(t *testing.T) { +func TestAzureRendererBackendAzureAD(t *testing.T) { r := AzureRenderer{ state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + UseAzureAdAuth: true, + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + use_azuread_auth = true + } + `, b) +} + +func TestAzureRendererBackendServicePrincipalWithClientCertificate(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", + ResourceGroup: "resource_group", + ClientId: "client_id", + ClientCertificatePassword: "client_certificate_password", + ClientCertificatePath: "client_certificate_path", + SubscriptionId: "subscription_id", + TenantId: "tenant_id", + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + resource_group_name = "resource_group" + subscription_id = "subscription_id" + tenant_id = "tenant_id" + client_id = "client_id" + client_certificate_password = "client_certificate_password" + client_certificate_path = "client_certificate_path" + } + `, b) +} + +func TestAzureRendererBackendServicePrincipalWithClientSecret(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ + StorageAccount: "storage_account", + ContainerName: "container_name", ResourceGroup: "resource_group", + ClientId: "client_id", + ClientSecret: "client_secret", + SubscriptionId: "subscription_id", + TenantId: "tenant_id", + }, + BaseRenderer: BaseRenderer{ + identifier: "test-1/component-1", + stateKey: "component-1", + }, + } + + b, err := r.Backend() + assert.NoError(t, err) + assert.Equal(t, ` + backend "azurerm" { + key = "test-1/component-1" + storage_account_name = "storage_account" + container_name = "container_name" + resource_group_name = "resource_group" + subscription_id = "subscription_id" + tenant_id = "tenant_id" + client_id = "client_id" + client_secret = "client_secret" + } + `, b) +} + +func TestAzureRendererRemoteStateBase(t *testing.T) { + r := AzureRenderer{ + state: &AzureState{ StorageAccount: "storage_account", ContainerName: "container_name", + Environment: "public", + Endpoint: "https://management.azure.com", + MetadataHost: "http://metadata", + Snapshot: true, StateFolder: "state_folder", }, BaseRenderer: BaseRenderer{ @@ -52,10 +270,13 @@ func TestAzureRendererRemoteStateFull(t *testing.T) { backend = "azurerm" config = { - resource_group_name = "resource_group" + key = "state_folder/test-1/component-1" storage_account_name = "storage_account" - container_name = "container_name" - key = "state_folder/test-1/component-1" + container_name = "container_name" + environment = "public" + endpoint = "https://management.azure.com" + metadata_host = "http://metadata" + snapshot = true } } `, rs) diff --git a/internal/state/schemas/azure.schema.json b/internal/state/schemas/azure.schema.json index c40b568f..b999f675 100644 --- a/internal/state/schemas/azure.schema.json +++ b/internal/state/schemas/azure.schema.json @@ -3,7 +3,6 @@ "description": "Azure storage account state backend configuration.", "additionalProperties": false, "required": [ - "resource_group", "storage_account", "container_name" ], @@ -11,16 +10,108 @@ "plugin": { "type": "string" }, + "storage_account": { + "description": "The Name of the Storage Account.", + "type": "string" + }, + "container_name": { + "description": "The Name of the Storage Container within the Storage Account.", + "type": "string" + }, + "environment": { + "description": "The Azure Environment which should be used.", + "enum": [ + "public", + "china", + "german", + "stack", + "usgovernment" + ], + "default": "public" + }, + "endpoint": { + "description": "The Custom Endpoint for Azure Resource Manager.", + "type": "string" + }, + "metadata_host": { + "description": "The Hostname of the Azure Metadata Service (for example management.azure.com), used to obtain the Cloud Environment when using a Custom Azure Environment.", + "type": "string" + }, + "snapshot": { + "description": "Should the Blob used to store the Terraform Statefile be snapshotted before use?", + "type": "boolean", + "default": false + }, "resource_group": { + "description": "The Name of the Resource Group in which the Storage Account exists.", "type": "string" }, - "storage_account": { + "msi_endpoint": { + "description": "The path to a custom Managed Service Identity endpoint which is automatically determined if not specified.", "type": "string" }, - "container_name": { + "subscription_id": { + "description": "The Subscription ID in which the Storage Account exists.", + "type": "string" + }, + "tenant_id": { + "description": "The Tenant ID in which the Subscription exists.", + "type": "string" + }, + "use_msi": { + "description": "Should Managed Service Identity authentication be used?", + "type": "boolean" + }, + "oidc_request_url": { + "description": "The URL for the OIDC provider from which to request an ID token.", + "type": "string" + }, + "oidc_request_token": { + "description": "The bearer token for the request to the OIDC provider.", + "type": "string" + }, + "oidc_token": { + "description": "The ID token when authenticating using OpenID Connect (OIDC).", + "type": "string" + }, + "oidc_token_file_path": { + "description": "The path to a file containing the ID token when authenticating using OpenID Connect (OIDC).", + "type": "string" + }, + "use_oidc": { + "description": "Should OIDC authentication be used?", + "type": "boolean" + }, + "sas_token": { + "description": "The SAS Token used to access the Blob Storage Account.", + "type": "string" + }, + "access_key": { + "description": "The Access Key used to access the Blob Storage Account.", + "type": "string" + }, + "use_azuread_auth": { + "description": "Whether Azure Active Directory Authentication should be used to access the Blob Storage Account", + "type": "boolean" + }, + "client_id": { + "description": "The Client ID of the Service Principal.", + "type": "string" + }, + "client_secret": { + "description": "The Client Secret of the Service Principal.", + "type": "string" + }, + "client_certificate_password": { + "description": "The password associated with the Client Certificate specified in client_certificate_path.", + "type": "string" + }, + "client_certificate_path": { + "description": "The path to the PFX file used as the Client Certificate when authenticating as a Service Principal", "type": "string" }, "state_folder": { + "description": "The folder in which the Terraform state file should be stored.", "type": "string", "default": "" }