diff --git a/README.md b/README.md index d91b23e..985eecc 100644 --- a/README.md +++ b/README.md @@ -596,6 +596,7 @@ This is the current compreheensive status of the implemented resources in the pr |azurerm_relay_hybrid_connection | ✔ | |azurerm_relay_namespace | ✔ | |azurerm_resource_group | ✔ | +|azurerm_resource_group_policy_assignment | ✔ | |azurerm_resource_group_template_deployment | ❌ | |azurerm_role_assignment | ✔ | |azurerm_role_definition | ✔ | @@ -684,6 +685,7 @@ This is the current compreheensive status of the implemented resources in the pr |azurerm_subnet_network_security_group_association | ❌ | |azurerm_subnet_route_table_association | ❌ | |azurerm_subscription | ❌ | +|azurerm_subscription_policy_assignment | ✔ | |azurerm_subscription_template_deployment | ❌ | |azurerm_subscriptions | ❌ | |azurerm_synapse_firewall_rule | ✔ | diff --git a/azurecaf/data_environment_variable.go b/azurecaf/data_environment_variable.go index d4e6640..60ca6b4 100644 --- a/azurecaf/data_environment_variable.go +++ b/azurecaf/data_environment_variable.go @@ -2,9 +2,9 @@ package azurecaf import ( "context" - "os" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "os" ) func dataEnvironmentVariable() *schema.Resource { @@ -46,4 +46,4 @@ func resourceAction(ctx context.Context, d *schema.ResourceData, meta interface{ _ = d.Set("value", value) return diags -} \ No newline at end of file +} diff --git a/azurecaf/data_name.go b/azurecaf/data_name.go new file mode 100644 index 0000000..999f0b9 --- /dev/null +++ b/azurecaf/data_name.go @@ -0,0 +1,125 @@ +package azurecaf + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataName() *schema.Resource { + resourceMapsKeys := make([]string, 0, len(ResourceDefinitions)) + for k := range ResourceDefinitions { + resourceMapsKeys = append(resourceMapsKeys, k) + } + + return &schema.Resource{ + ReadContext: dataNameRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "", + }, + "prefixes": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + Optional: true, + ForceNew: true, + }, + "suffixes": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + Optional: true, + ForceNew: true, + }, + "random_length": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + Default: 0, + }, + "result": { + Type: schema.TypeString, + Computed: true, + }, + "separator": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "-", + }, + "clean_input": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: true, + }, + "passthrough": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + "resource_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(resourceMapsKeys, false), + ForceNew: true, + }, + "random_seed": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "use_slug": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: true, + }, + }, + } +} + +func dataNameRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + getNameReadResult(d, meta) + return diag.Diagnostics{} +} + +func getNameReadResult(d *schema.ResourceData, meta interface{}) error { + name := d.Get("name").(string) + prefixes := convertInterfaceToString(d.Get("prefixes").([]interface{})) + suffixes := convertInterfaceToString(d.Get("suffixes").([]interface{})) + separator := d.Get("separator").(string) + resourceType := d.Get("resource_type").(string) + cleanInput := d.Get("clean_input").(bool) + passthrough := d.Get("passthrough").(bool) + useSlug := d.Get("use_slug").(bool) + randomLength := d.Get("random_length").(int) + randomSeed := int64(d.Get("random_seed").(int)) + + convention := ConventionCafClassic + + randomSuffix := randSeq(int(randomLength), &randomSeed) + + namePrecedence := []string{"name", "slug", "random", "suffixes", "prefixes"} + + resourceName, err := getResourceName(resourceType, separator, prefixes, name, suffixes, randomSuffix, convention, cleanInput, passthrough, useSlug, namePrecedence) + if err != nil { + return err + } + d.Set("result", resourceName) + + d.SetId(resourceName) + return nil +} diff --git a/azurecaf/models_generated.go b/azurecaf/models_generated.go index d3e44de..6c3c264 100644 --- a/azurecaf/models_generated.go +++ b/azurecaf/models_generated.go @@ -1,6 +1,6 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated by robots at -// 2022-11-01 08:25:39.661982793 +0000 UTC m=+0.005481209 +// 2022-11-04 18:07:55.956721 +0800 +08 m=+0.040022043 // using data from // resourceDefinition.json and resourceDefinition_out_of_docs.json @@ -116,7 +116,7 @@ var ResourceDefinitions = map[string]ResourceStructure{ "azurerm_digital_twins_endpoint_eventhub": {"azurerm_digital_twins_endpoint_eventhub", "adteh", 3, 50, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9_-]{1,50}$", true, "parent"}, "azurerm_digital_twins_endpoint_servicebus": {"azurerm_digital_twins_endpoint_servicebus", "adtsb", 3, 50, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9_-]{1,50}$", true, "parent"}, "azurerm_digital_twins_instance": {"azurerm_digital_twins_instance", "adt", 4, 63, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9_-]{1,63}$", true, "subscription"}, - "azurerm_disk_encryption_set": {"azurerm_disk_encryption_set", "des", 1, 80, false, "[^0-9A-Za-z_]", "^[a-zA-Z0-9_]{1,80}$", false, "resourceGroup"}, + "azurerm_disk_encryption_set": {"azurerm_disk_encryption_set", "des", 1, 80, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9-_]{1,80}$", true, "resourceGroup"}, "azurerm_dns_a_record": {"azurerm_dns_a_record", "dnsrec", 1, 80, false, "[^a-zA-Z0-9\\-\\._]", "^[a-zA-Z0-9][a-zA-Z0-9\\-\\._]{0,78}[a-zA-Z0-9_]$", true, "parent"}, "azurerm_dns_aaaa_record": {"azurerm_dns_aaaa_record", "dnsrec", 1, 80, false, "[^a-zA-Z0-9\\-\\._]", "^[a-zA-Z0-9][a-zA-Z0-9\\-\\._]{0,78}[a-zA-Z0-9_]$", true, "parent"}, "azurerm_dns_caa_record": {"azurerm_dns_caa_record", "dnsrec", 1, 80, false, "[^a-zA-Z0-9\\-\\._]", "^[a-zA-Z0-9][a-zA-Z0-9\\-\\._]{0,78}[a-zA-Z0-9_]$", true, "parent"}, @@ -274,6 +274,7 @@ var ResourceDefinitions = map[string]ResourceStructure{ "azurerm_relay_hybrid_connection": {"azurerm_relay_hybrid_connection", "rlhc", 1, 260, false, "[^0-9A-Za-z_.-]", "^[a-zA-Z0-9][a-zA-Z0-9-._]{0,258}[a-zA-Z0-9]$", true, "parent"}, "azurerm_relay_namespace": {"azurerm_relay_namespace", "rln", 6, 50, false, "[^0-9A-Za-z-]", "^[a-zA-Z][a-zA-Z0-9-]{4,48}[a-zA-Z0-9]$", true, "global"}, "azurerm_resource_group": {"azurerm_resource_group", "rg", 1, 90, false, `[^a-zA-Z0-9-._\\(\\)]`, "^[a-zA-Z0-9-._\\(\\)]{0,89}[a-zA-Z0-9-_\\(\\)]$", true, "subscription"}, + "azurerm_resource_group_policy_assignment": {"azurerm_resource_group_policy_assignment", "argpa", 1, 128, false, "[^a-zA-Z0-9\\-\\._]", "^[a-zA-Z0-9][a-zA-Z0-9\\-\\._]{0,126}[a-zA-Z0-9_]$", true, "resourceGroup"}, "azurerm_role_assignment": {"azurerm_role_assignment", "ra", 1, 64, false, "[^0-9A-Za-z_.-]", "^[^%]{0,63}[^ %.]$", true, "assignment"}, "azurerm_role_definition": {"azurerm_role_definition", "rd", 1, 64, false, "[^0-9A-Za-z_.-]", "^[^%]{0,63}[^ %.]$", true, "definition"}, "azurerm_route": {"azurerm_route", "rt", 1, 80, false, "[^0-9A-Za-z_.-]", "^[a-zA-Z0-9][a-zA-Z0-9-._]{0,78}[a-zA-Z0-9_]$", true, "parent"}, @@ -318,6 +319,7 @@ var ResourceDefinitions = map[string]ResourceStructure{ "azurerm_stream_analytics_stream_input_eventhub": {"azurerm_stream_analytics_stream_input_eventhub", "asaieh", 3, 63, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9-_]{3,63}$", true, "parent"}, "azurerm_stream_analytics_stream_input_iothub": {"azurerm_stream_analytics_stream_input_iothub", "asaiiot", 3, 63, false, "[^0-9A-Za-z_-]", "^[a-zA-Z0-9-_]{3,63}$", true, "parent"}, "azurerm_subnet": {"azurerm_subnet", "snet", 1, 80, false, "[^0-9A-Za-z_.-]", "^[a-zA-Z0-9][a-zA-Z0-9-._]{0,78}[a-zA-Z0-9_]$", true, "parent"}, + "azurerm_subscription_policy_assignment": {"azurerm_subscription_policy_assignment", "aspa", 1, 128, false, "[^a-zA-Z0-9\\-\\._]", "^[a-zA-Z0-9][a-zA-Z0-9\\-\\._]{0,126}[a-zA-Z0-9_]$", true, "subscription"}, "azurerm_synapse_firewall_rule": {"azurerm_synapse_firewall_rule", "syfw", 1, 128, false, `[<>*%:?\\+\\/]`, "^[^<>*%:?\\+\\/]{1,127}[^<>*%:.?\\+\\/]$", true, "parent"}, "azurerm_synapse_integration_runtime_azure": {"azurerm_synapse_integration_runtime_azure", "synira", 3, 63, false, "[^0-9A-Za-z-]", "^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9-]$", true, "subscription"}, "azurerm_synapse_integration_runtime_self_hosted": {"azurerm_synapse_integration_runtime_self_hosted", "synirsh", 3, 63, false, "[^0-9A-Za-z-]", "^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9_-]$", true, "subscription"}, @@ -431,6 +433,7 @@ var ResourceMaps = map[string]string{ "app": "azurerm_app_service", "appcg": "azurerm_app_configuration", "appi": "azurerm_application_insights", + "argpa": "azurerm_resource_group_policy_assignment", "aroc": "azurerm_redhat_openshift_cluster", "arod": "azurerm_redhat_openshift_domain", "as": "azurerm_analysis_services_server", @@ -447,6 +450,7 @@ var ResourceMaps = map[string]string{ "asarblob": "azurerm_stream_analytics_reference_input_blob", "ase": "azurerm_app_service_environment", "asg": "azurerm_application_security_group", + "aspa": "azurerm_subscription_policy_assignment", "avail": "azurerm_availability_set", "ba": "azurerm_batch_account", "baapp": "azurerm_batch_application", diff --git a/azurecaf/provider.go b/azurecaf/provider.go index bf7ee44..de08059 100644 --- a/azurecaf/provider.go +++ b/azurecaf/provider.go @@ -15,6 +15,7 @@ func Provider() *schema.Provider { }, DataSourcesMap: map[string]*schema.Resource{ "azurecaf_environment_variable": dataEnvironmentVariable(), + "azurecaf_name": dataName(), }, } } diff --git a/azurecaf/provider_test.go b/azurecaf/provider_test.go index 84ca662..f5e9813 100644 --- a/azurecaf/provider_test.go +++ b/azurecaf/provider_test.go @@ -30,7 +30,7 @@ func TestProvider_impl(t *testing.T) { func testAccPreCheck(t *testing.T) { } -//Resource are locale and are no instrastructure is created in the test suite +// Resource are locale and are no instrastructure is created in the test suite func testAccCheckResourceDestroy(s *terraform.State) error { return nil } diff --git a/docs/data-sources/azurecaf_name.md b/docs/data-sources/azurecaf_name.md new file mode 100644 index 0000000..ed4dada --- /dev/null +++ b/docs/data-sources/azurecaf_name.md @@ -0,0 +1,95 @@ +# azurecaf_name + +The data source azurecaf_name generate a name for a resource (recommended way as data sources are evaluated before resources got created). + +The azurecaf_name resource allows you to: + +* Clean inputs to make sure they remain compliant with the allowed patterns for each Azure resource +* Generate random characters to append at the end of the resource name +* Handle prefix, suffixes (either manual or as per the Azure cloud adoption framework resource conventions) +* Allow passthrough mode (simply validate the output) + +## Exemple usage +This example shows how to get the value of an environment variable. + +```hcl +data "azurecaf_name" "rg_example" { + name = "demogroup" + resource_type = "azurerm_resource_group" + prefixes = ["a", "b"] + suffixes = ["y", "z"] + random_length = 5 + clean_input = true +} + +output "rg_example" { + value = data.azurecaf_name.rg_example.result +} +``` +```bash +data.azurecaf_name.rg_example: Reading... +data.azurecaf_name.rg_example: Read complete after 0s [id=a-b-rg-demogroup-sjdeh-y-z] + +Changes to Outputs: + + rg_example = "a-b-rg-demogroup-sjdeh-y-z" +``` + +## Combined with Azure resource +The data source evaluates the name before the resource got created and is visible at plan time. + +```hcl +data "azurecaf_name" "rg_example" { + name = "demogroup" + resource_type = "azurerm_resource_group" + prefixes = ["a", "b"] + suffixes = ["y", "z"] + random_length = 5 + clean_input = true +} + +resource "azurerm_resource_group" "rg" { + name = data.azurecaf_name.rg_example.result + location = "southeastasia" +} +``` +```bash +data.azurecaf_name.rg_example: Reading... +data.azurecaf_name.rg_example: Read complete after 0s [id=a-b-rg-demogroup-wjyhr-y-z] + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # azurerm_resource_group.rg will be created + + resource "azurerm_resource_group" "rg" { + + id = (known after apply) + + location = "southeastasia" + + name = "a-b-rg-demogroup-wjyhr-y-z" + } + +Plan: 1 to add, 0 to change, 0 to destroy. +``` + + +## Argument Reference + +The following arguments are supported: + +* **name** - (optional) the basename of the resource to create, the basename will be sanitized as per supported characters set for each Azure resources. +* **prefixes** (optional) - a list of prefix to append as the first characters of the generated name - prefixes will be separated by the separator character +* **suffixes** (optional) - a list of additional suffix added after the basename, this is can be used to append resource index (eg. vm-001). Suffixes are separated by the separator character +* **random_length** (optional) - default to ``0`` : configure additional characters to append to the generated resource name. Random characters will remain compliant with the set of allowed characters per resources and will be appended before suffix(ess). +* **random_seed** (optional) - default to ``0`` : Define the seed to be used for random generator. 0 will not be respected and will generate a seed based in the unix time of the generation. +* **resource_type** (optional) - describes the type of azure resource you are requesting a name from (eg. azure container registry: azurerm_container_registry). See the Resource Type section +* **separator** (optional) - defaults to ``-``. The separator character to use between prefixes, resource type, name, suffixes, random character +* **clean_input** (optional) - defaults to ``true``. remove any noncompliant character from the name, suffix or prefix. +* **passthrough** (optional) - defaults to ``false``. Enables the passthrough mode - in that case only the clean input option is considered and the prefixes, suffixes, random, and are ignored. The resource prefixe is not added either to the resulting string +* **use_slug** (optional) - defaults to ``true``. If a slug should be added to the name - If you put false no slug (the few letters that identify the resource type) will be added to the name. + +## Attributes Reference + +The following attributes are exported: + +* **id** - The id of the naming convention object (same as the result value) +* **result** - The generated named for an Azure Resource based on the input parameter and the selected naming convention diff --git a/resourceDefinition.json b/resourceDefinition.json index 0a536e5..ca38481 100644 --- a/resourceDefinition.json +++ b/resourceDefinition.json @@ -488,12 +488,12 @@ "name": "azurerm_disk_encryption_set", "min_length": 1, "max_length": 80, - "validation_regex": "\"^[a-zA-Z0-9_]{1,80}$\"", + "validation_regex": "\"^[a-zA-Z0-9-_]{1,80}$\"", "scope": "resourceGroup", "slug": "des", - "dashes": false, + "dashes": true, "lowercase": false, - "regex": "\"[^0-9A-Za-z_]\"" + "regex": "\"[^0-9A-Za-z_-]\"" }, { "name": "azurerm_image", @@ -3577,5 +3577,27 @@ "dashes": false, "lowercase": false, "regex": "\"[^0-9A-Za-z_`,.\\\\[\\\\]]\"" + }, + { + "name": "azurerm_subscription_policy_assignment", + "min_length": 1, + "max_length": 128, + "validation_regex": "\"^[a-zA-Z0-9][a-zA-Z0-9\\\\-\\\\._]{0,126}[a-zA-Z0-9_]$\"", + "scope": "subscription", + "slug": "aspa", + "dashes": true, + "lowercase": false, + "regex": "\"[^a-zA-Z0-9\\\\-\\\\._]\"" + }, + { + "name": "azurerm_resource_group_policy_assignment", + "min_length": 1, + "max_length": 128, + "validation_regex": "\"^[a-zA-Z0-9][a-zA-Z0-9\\\\-\\\\._]{0,126}[a-zA-Z0-9_]$\"", + "scope": "resourceGroup", + "slug": "argpa", + "dashes": true, + "lowercase": false, + "regex": "\"[^a-zA-Z0-9\\\\-\\\\._]\"" } ]