diff --git a/internal/services/appconfiguration/app_configuration_key_data_source_test.go b/internal/services/appconfiguration/app_configuration_key_data_source_test.go index f0a1f6b91a03..6b26605e429d 100644 --- a/internal/services/appconfiguration/app_configuration_key_data_source_test.go +++ b/internal/services/appconfiguration/app_configuration_key_data_source_test.go @@ -11,7 +11,7 @@ import ( type AppConfigurationKeyDataSource struct{} -func TestAccAppServicePlanDataSource_basic(t *testing.T) { +func TestAccAppConfigurationKeyDataSource_basic(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_key", "test") d := AppConfigurationKeyDataSource{} @@ -31,7 +31,7 @@ func TestAccAppServicePlanDataSource_basic(t *testing.T) { }) } -func TestAccAppServicePlanDataSource_basicNoLabel(t *testing.T) { +func TestAccAppConfigurationKeyDataSource_basicNoLabel(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_key", "test") d := AppConfigurationKeyDataSource{} @@ -51,7 +51,7 @@ func TestAccAppServicePlanDataSource_basicNoLabel(t *testing.T) { }) } -func TestAccAppServicePlanDataSource_basicVault(t *testing.T) { +func TestAccAppConfigurationKeyDataSource_basicVault(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_key", "test") d := AppConfigurationKeyDataSource{} diff --git a/internal/services/appconfiguration/app_configuration_keys_data_source.go b/internal/services/appconfiguration/app_configuration_keys_data_source.go new file mode 100644 index 000000000000..54a3af120f3d --- /dev/null +++ b/internal/services/appconfiguration/app_configuration_keys_data_source.go @@ -0,0 +1,197 @@ +package appconfiguration + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type KeysDataSource struct{} + +var _ sdk.DataSource = KeysDataSource{} + +type KeyDataSourceModel struct { + Key string `tfschema:"key"` + ContentType string `tfschema:"content_type"` + Etag string `tfschema:"etag"` + Label string `tfschema:"label"` + Value string `tfschema:"value"` + Locked bool `tfschema:"locked"` + Tags map[string]interface{} `tfschema:"tags"` + Type string `tfschema:"type"` + VaultKeyReference string `tfschema:"vault_key_reference"` +} + +type KeysDataSourceModel struct { + ConfigurationStoreId string `tfschema:"configuration_store_id"` + Key string `tfschema:"key"` + Label string `tfschema:"label"` + Items []KeyDataSourceModel `tfschema:"items"` +} + +func (k KeysDataSource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "configuration_store_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + "key": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "", + }, + "label": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "", + }, + } +} + +func (k KeysDataSource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "items": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "key": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "label": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "content_type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "etag": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "value": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "locked": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "vault_key_reference": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tags": tags.SchemaDataSource(), + }, + }, + }, + } +} + +func (k KeysDataSource) ModelObject() interface{} { + return &KeysDataSourceModel{} +} + +func (k KeysDataSource) ResourceType() string { + return "azurerm_app_configuration_keys" +} + +func (k KeysDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + var model KeysDataSourceModel + if err := metadata.Decode(&model); err != nil { + return err + } + + decodedKey, err := url.QueryUnescape(model.Key) + if err != nil { + return fmt.Errorf("while decoding key of resource ID: %+v", err) + } + + id := parse.AppConfigurationKeyId{ + ConfigurationStoreId: model.ConfigurationStoreId, + Key: decodedKey, + Label: model.Label, + } + + client, err := metadata.Client.AppConfiguration.DataPlaneClient(ctx, model.ConfigurationStoreId) + if client == nil { + return fmt.Errorf("building data plane client: app configuration %q was not found", model.ConfigurationStoreId) + } + if err != nil { + return err + } + + iter, err := client.GetKeyValuesComplete(ctx, decodedKey, model.Label, "", "", []string{}) + if err != nil { + if v, ok := err.(autorest.DetailedError); ok { + if utils.ResponseWasNotFound(autorest.Response{Response: v.Response}) { + return fmt.Errorf("key %s was not found", decodedKey) + } + } else { + return fmt.Errorf("while checking for key's %q existence: %+v", decodedKey, err) + } + return fmt.Errorf("while checking for key's %q existence: %+v", decodedKey, err) + } + + for iter.NotDone() { + kv := iter.Value() + var krmodel KeyDataSourceModel + krmodel.Key = *kv.Key + krmodel.Label = *kv.Label + if contentType := utils.NormalizeNilableString(kv.ContentType); contentType != VaultKeyContentType { + krmodel.Type = KeyTypeKV + krmodel.ContentType = contentType + krmodel.Value = utils.NormalizeNilableString(kv.Value) + } else { + var ref VaultKeyReference + refBytes := []byte(utils.NormalizeNilableString(kv.Value)) + err := json.Unmarshal(refBytes, &ref) + if err != nil { + return fmt.Errorf("while unmarshalling vault reference: %+v", err) + } + + krmodel.Type = KeyTypeVault + krmodel.VaultKeyReference = ref.URI + krmodel.ContentType = VaultKeyContentType + krmodel.Value = ref.URI + } + + if kv.Locked != nil { + krmodel.Locked = *kv.Locked + } + krmodel.Etag = utils.NormalizeNilableString(kv.Etag) + if id.Label == "" { + // We set an empty label as %00 in the resource ID + // Otherwise it breaks the ID parsing logic + id.Label = "%00" + } + model.Items = append(model.Items, krmodel) + if err := iter.NextWithContext(ctx); err != nil { + return fmt.Errorf("fetching keys for %q: %+v", id, err) + } + } + metadata.SetID(id) + return metadata.Encode(&model) + }, + } +} diff --git a/internal/services/appconfiguration/app_configuration_keys_data_source_test.go b/internal/services/appconfiguration/app_configuration_keys_data_source_test.go new file mode 100644 index 000000000000..2d8380ad5a79 --- /dev/null +++ b/internal/services/appconfiguration/app_configuration_keys_data_source_test.go @@ -0,0 +1,148 @@ +package appconfiguration_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type AppConfigurationKeysDataSource struct{} + +func TestAccAppConfigurationKeysDataSource_allkeys(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_keys", "test") + d := AppConfigurationKeysDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: d.allKeys(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("items.#").HasValue("4"), + ), + }, + }) +} + +func TestAccAppConfigurationKeysDataSource_key(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_keys", "test") + d := AppConfigurationKeysDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: d.key(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("items.#").HasValue("2"), + ), + }, + }) +} + +func TestAccAppConfigurationKeysDataSource_label(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_app_configuration_keys", "test") + d := AppConfigurationKeysDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: d.label(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("items.#").HasValue("2"), + ), + }, + }) +} + +func (t AppConfigurationKeysDataSource) keys() string { + return fmt.Sprintf(` +resource "azurerm_app_configuration_key" "test" { + configuration_store_id = azurerm_app_configuration.test.id + key = "key1" + content_type = "test" + label = "label1" + value = "a test" +} + +resource "azurerm_app_configuration_key" "test2" { + configuration_store_id = azurerm_app_configuration.test.id + key = "key1" + content_type = "test" + label = "label2" + value = "a test" +} + +resource "azurerm_app_configuration_key" "test3" { + configuration_store_id = azurerm_app_configuration.test.id + key = "key2" + content_type = "test" + label = "testlabel" + value = "a test" +} + +resource "azurerm_app_configuration_key" "test4" { + configuration_store_id = azurerm_app_configuration.test.id + key = "key3" + content_type = "test" + label = "testlabel" + value = "a test" +} +`) +} + +func (t AppConfigurationKeysDataSource) allKeys(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +%s + +data "azurerm_app_configuration_keys" "test" { + configuration_store_id = azurerm_app_configuration.test.id + + depends_on = [ + azurerm_app_configuration_key.test, + azurerm_app_configuration_key.test2, + azurerm_app_configuration_key.test3, + azurerm_app_configuration_key.test4 + ] +} +`, AppConfigurationKeyResource{}.base(data), t.keys()) +} + +func (t AppConfigurationKeysDataSource) key(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +%s + +data "azurerm_app_configuration_keys" "test" { + configuration_store_id = azurerm_app_configuration.test.id + key = "key1" + + depends_on = [ + azurerm_app_configuration_key.test, + azurerm_app_configuration_key.test2, + azurerm_app_configuration_key.test3, + azurerm_app_configuration_key.test4 + ] +} +`, AppConfigurationKeyResource{}.base(data), t.keys()) +} + +func (t AppConfigurationKeysDataSource) label(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +%s + +data "azurerm_app_configuration_keys" "test" { + configuration_store_id = azurerm_app_configuration.test.id + label = "testlabel" + + depends_on = [ + azurerm_app_configuration_key.test, + azurerm_app_configuration_key.test2, + azurerm_app_configuration_key.test3, + azurerm_app_configuration_key.test4 + ] +} +`, AppConfigurationKeyResource{}.base(data), t.keys()) +} diff --git a/internal/services/appconfiguration/registration.go b/internal/services/appconfiguration/registration.go index 331b02309139..733ef8f138b8 100644 --- a/internal/services/appconfiguration/registration.go +++ b/internal/services/appconfiguration/registration.go @@ -19,6 +19,7 @@ func (r Registration) AssociatedGitHubLabel() string { func (r Registration) DataSources() []sdk.DataSource { return []sdk.DataSource{ KeyDataSource{}, + KeysDataSource{}, } } diff --git a/website/docs/d/app_configuration_keys.html.markdown b/website/docs/d/app_configuration_keys.html.markdown new file mode 100644 index 000000000000..53038453200f --- /dev/null +++ b/website/docs/d/app_configuration_keys.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "App Configuration" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_app_configuration_keys" +description: |- + Gets information about existing Azure App Configuration Keys. +--- + +# Data Source: azurerm_app_configuration_keys + +Use this data source to access information about existing Azure App Configuration Keys. + +-> **Note:** App Configuration Keys are provisioned using a Data Plane API which requires the role `App Configuration Data Owner` on either the App Configuration or a parent scope (such as the Resource Group/Subscription). [More information can be found in the Azure Documentation for App Configuration](https://docs.microsoft.com/azure/azure-app-configuration/concept-enable-rbac#azure-built-in-roles-for-azure-app-configuration). + +## Example Usage + +```hcl +data "azurerm_app_configuration_keys" "test" { + configuration_store_id = azurerm_app_configuration.appconf.id +} + +output "value" { + value = data.azurerm_app_configuration_keys.test.items +} +``` + +## Argument Reference + +The following arguments are supported: + +* `configuration_store_id` - (Required) Specifies the id of the App Configuration. + +* `key` - (Optional) The name of the App Configuration Keys to look up. + +* `label` - (Optional) The label of the App Configuration Keys tp look up. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `items` - A list of `items` blocks as defined below. + +--- + +Each element in `items` block exports the following: + +* `key` - The name of the App Configuration Key. + +* `label` - The label of the App Configuration Key. + +* `content_type` - The content type of the App Configuration Key. + +* `value` - The value of the App Configuration Key. + +* `locked` - Is this App Configuration Key be Locked to prevent changes. + +* `type` - The type of the App Configuration Key. It can either be `kv` (simple [key/value](https://docs.microsoft.com/azure/azure-app-configuration/concept-key-value)) or `vault` (where the value is a reference to a [Key Vault Secret](https://azure.microsoft.com/en-gb/services/key-vault/). + +* `vault_key_reference` - The ID of the vault secret this App Configuration Key refers to, when `type` is `vault`. + +* `tags` - A mapping of tags assigned to the resource. + +* `id` - The App Configuration Key ID. + +* `etag` - The ETag of the key. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the App Configuration Key.