From 1c7c4ad1cb6567600645460cc939ec17f7d16d8c Mon Sep 17 00:00:00 2001 From: xuzhang3 Date: Wed, 21 Sep 2022 12:15:08 +0800 Subject: [PATCH 1/2] apim api enhance --- .../api_management_api_resource.go | 139 ++++++++++++++++++ .../api_management_api_resource_test.go | 69 +++++++++ .../docs/r/api_management_api.html.markdown | 24 +++ 3 files changed, 232 insertions(+) diff --git a/internal/services/apimanagement/api_management_api_resource.go b/internal/services/apimanagement/api_management_api_resource.go index 8d34ed3fdcba..fd4f141a4212 100644 --- a/internal/services/apimanagement/api_management_api_resource.go +++ b/internal/services/apimanagement/api_management_api_resource.go @@ -100,6 +100,32 @@ func resourceApiManagementApi() *pluginsdk.Resource { }, false), }, + "contact": { + Type: pluginsdk.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "email": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + }, + }, + }, + "description": { Type: pluginsdk.TypeString, Optional: true, @@ -158,6 +184,27 @@ func resourceApiManagementApi() *pluginsdk.Resource { }, }, + "license": { + Type: pluginsdk.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + }, + }, + }, + "service_url": { Type: pluginsdk.TypeString, Optional: true, @@ -191,6 +238,12 @@ func resourceApiManagementApi() *pluginsdk.Resource { Default: true, }, + "terms_of_service_url": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + }, + "source_api_id": { Type: pluginsdk.TypeString, Optional: true, @@ -413,6 +466,12 @@ func resourceApiManagementApiCreateUpdate(d *pluginsdk.ResourceData, meta interf openIDAuthorizationSettings := expandApiManagementOpenIDAuthenticationSettingsContract(openIDAuthorizationSettingsRaw) authenticationSettings.Openid = openIDAuthorizationSettings + contactInfoRaw := d.Get("contact").([]interface{}) + contactInfo := expandApiManagementApiContact(contactInfoRaw) + + licenseInfoRaw := d.Get("license").([]interface{}) + licenseInfo := expandApiManagementApiLicense(licenseInfoRaw) + params := apimanagement.APICreateOrUpdateParameter{ APICreateOrUpdateProperties: &apimanagement.APICreateOrUpdateProperties{ APIType: apiType, @@ -427,6 +486,8 @@ func resourceApiManagementApiCreateUpdate(d *pluginsdk.ResourceData, meta interf AuthenticationSettings: authenticationSettings, APIRevisionDescription: utils.String(d.Get("revision_description").(string)), APIVersionDescription: utils.String(d.Get("version_description").(string)), + Contact: contactInfo, + License: licenseInfo, }, } @@ -442,6 +503,10 @@ func resourceApiManagementApiCreateUpdate(d *pluginsdk.ResourceData, meta interf params.APICreateOrUpdateProperties.APIVersionSetID = utils.String(versionSetId) } + if v, ok := d.GetOk("terms_of_service_url"); ok { + params.APICreateOrUpdateProperties.TermsOfServiceURL = utils.String(v.(string)) + } + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ServiceName, apiId, params, "") if err != nil { return fmt.Errorf("creating/updating %s: %+v", id, err) @@ -508,6 +573,7 @@ func resourceApiManagementApiRead(d *pluginsdk.ResourceData, meta interface{}) e d.Set("version_set_id", props.APIVersionSetID) d.Set("revision_description", props.APIRevisionDescription) d.Set("version_description", props.APIVersionDescription) + d.Set("terms_of_service_url", props.TermsOfServiceURL) if err := d.Set("protocols", flattenApiManagementApiProtocols(props.Protocols)); err != nil { return fmt.Errorf("setting `protocols`: %s", err) @@ -524,6 +590,14 @@ func resourceApiManagementApiRead(d *pluginsdk.ResourceData, meta interface{}) e if err := d.Set("openid_authentication", flattenApiManagementOpenIDAuthentication(props.AuthenticationSettings.Openid)); err != nil { return fmt.Errorf("setting `openid_authentication`: %+v", err) } + + if err := d.Set("contact", flattenApiManagementApiContact(props.Contact)); err != nil { + return fmt.Errorf("setting `contact`: %+v", err) + } + + if err := d.Set("license", flattenApiManagementApiLicense(props.License)); err != nil { + return fmt.Errorf("setting `license`: %+v", err) + } } return nil @@ -694,3 +768,68 @@ func flattenApiManagementOpenIDAuthentication(input *apimanagement.OpenIDAuthent return []interface{}{result} } + +func expandApiManagementApiContact(input []interface{}) *apimanagement.APIContactInformation { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + return &apimanagement.APIContactInformation{ + Email: utils.String(v["email"].(string)), + Name: utils.String(v["name"].(string)), + URL: utils.String(v["url"].(string)), + } +} + +func flattenApiManagementApiContact(contact *apimanagement.APIContactInformation) []interface{} { + if contact == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + if contact.Email != nil { + result["email"] = *contact.Email + } + + if contact.Name != nil { + result["name"] = *contact.Name + } + + if contact.URL != nil { + result["url"] = *contact.URL + } + + return []interface{}{result} +} + +func expandApiManagementApiLicense(input []interface{}) *apimanagement.APILicenseInformation { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + return &apimanagement.APILicenseInformation{ + Name: utils.String(v["name"].(string)), + URL: utils.String(v["url"].(string)), + } +} + +func flattenApiManagementApiLicense(license *apimanagement.APILicenseInformation) []interface{} { + if license == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + if license.Name != nil { + result["name"] = *license.Name + } + + if license.URL != nil { + result["url"] = *license.URL + } + + return []interface{}{result} +} diff --git a/internal/services/apimanagement/api_management_api_resource_test.go b/internal/services/apimanagement/api_management_api_resource_test.go index 6ee70e14f5f3..4d58043c618d 100644 --- a/internal/services/apimanagement/api_management_api_resource_test.go +++ b/internal/services/apimanagement/api_management_api_resource_test.go @@ -380,6 +380,21 @@ func TestAccApiManagementApi_createRevisionFromExistingRevision(t *testing.T) { }) } +func TestAccApiManagementApi_contact(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_api", "test") + r := ApiManagementApiResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.contact(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (ApiManagementApiResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.ApiID(state.ID) if err != nil { @@ -610,6 +625,19 @@ resource "azurerm_api_management_api" "test" { header = "X-Butter-Robot-API-Key" query = "location" } + + contact { + email = "test@test.com" + name = "test" + url = "https://example:8080" + } + + license { + name = "test-license" + url = "https://example:8080/license" + } + + terms_of_service_url = "https://example:8080/service" } `, r.template(data, SkuNameConsumption), data.RandomInteger) } @@ -779,10 +807,51 @@ resource "azurerm_api_management_api" "revision" { revision = "18" source_api_id = "${azurerm_api_management_api.test.id};rev=3" revision_description = "Creating a Revision of an existing API" + contact { + email = "test@test.com" + name = "test" + url = "https://example:8080" + } + + license { + name = "test-license" + url = "https://example:8080/license" + } + + terms_of_service_url = "https://example:8080/service" } `, r.complete(data), data.RandomInteger) } +func (r ApiManagementApiResource) contact(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_api_management_api" "test" { + name = "acctestapi-%d" + resource_group_name = azurerm_resource_group.test.name + api_management_name = azurerm_api_management.test.name + display_name = "api1" + path = "api1" + protocols = ["https"] + revision = "1" + + contact { + email = "test@test.com" + name = "test" + url = "https://example:8080" + } + + license { + name = "test-license" + url = "https://example:8080/license" + } + + terms_of_service_url = "https://example:8080/service" +} +`, r.template(data, SkuNameConsumption), data.RandomInteger) +} + func (ApiManagementApiResource) template(data acceptance.TestData, skuName string) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/api_management_api.html.markdown b/website/docs/r/api_management_api.html.markdown index fa6180da6d35..843aacf39259 100644 --- a/website/docs/r/api_management_api.html.markdown +++ b/website/docs/r/api_management_api.html.markdown @@ -68,10 +68,14 @@ The following arguments are supported: -> **NOTE:** `display_name`, `path` and `protocols` are required when `source_api_id` is not set. +* `contact` - (Optional) A `contact` block as documented below. + * `description` - (Optional) A description of the API Management API, which may include HTML formatting tags. * `import` - (Optional) A `import` block as documented below. +* `license` - (Optional) A `license` block as documented below. + * `oauth2_authorization` - (Optional) An `oauth2_authorization` block as documented below. * `openid_authentication` - (Optional) An `openid_authentication` block as documented below. @@ -86,6 +90,8 @@ The following arguments are supported: * `subscription_required` - (Optional) Should this API require a subscription key? +* `terms_of_service_url` - (Optional) Absolute URL of the Terms of Service for the API. + * `version` - (Optional) The Version number of this API, if this API is versioned. * `version_set_id` - (Optional) The ID of the Version Set which this API is associated with. @@ -100,6 +106,16 @@ The following arguments are supported: --- +A `contact` block supports the following: + +* `email` - (Optional) The email address of the contact person/organization. + +* `name` - (Optional) The name of the contact person/organization. + +* `url` - (Optional) Absolute URL of the contact information. + +--- + A `import` block supports the following: * `content_format` - (Required) The format of the content from which the API Definition should be imported. Possible values are: `openapi`, `openapi+json`, `openapi+json-link`, `openapi-link`, `swagger-json`, `swagger-link-json`, `wadl-link-json`, `wadl-xml`, `wsdl` and `wsdl-link`. @@ -110,6 +126,14 @@ A `import` block supports the following: --- +A `license` block supports the following: + +* `name` - (Optional) The name of the license . + +* `url` - (Optional) Absolute URL of the license. + +--- + A `oauth2_authorization` block supports the following: * `authorization_server_name` - (Required) OAuth authorization server identifier. The name of an [OAuth2 Authorization Server](https://www.terraform.io/docs/providers/azurerm/r/api_management_authorization_server.html). From 7def0fc31a8d34451d1bf4b89a82a64f42741ecd Mon Sep 17 00:00:00 2001 From: xuzhang3 Date: Fri, 23 Sep 2022 16:26:10 +0800 Subject: [PATCH 2/2] validate email address --- .../api_management_api_resource.go | 2 +- .../api_management_api_email_address.go | 15 ++++++ .../api_management_api_email_address_test.go | 47 +++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 internal/services/apimanagement/validate/api_management_api_email_address.go create mode 100644 internal/services/apimanagement/validate/api_management_api_email_address_test.go diff --git a/internal/services/apimanagement/api_management_api_resource.go b/internal/services/apimanagement/api_management_api_resource.go index fd4f141a4212..6118dd33cd82 100644 --- a/internal/services/apimanagement/api_management_api_resource.go +++ b/internal/services/apimanagement/api_management_api_resource.go @@ -110,7 +110,7 @@ func resourceApiManagementApi() *pluginsdk.Resource { "email": { Type: pluginsdk.TypeString, Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + ValidateFunc: validate.EmailAddress, }, "name": { Type: pluginsdk.TypeString, diff --git a/internal/services/apimanagement/validate/api_management_api_email_address.go b/internal/services/apimanagement/validate/api_management_api_email_address.go new file mode 100644 index 000000000000..c39eea6e2525 --- /dev/null +++ b/internal/services/apimanagement/validate/api_management_api_email_address.go @@ -0,0 +1,15 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func EmailAddress(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("test: %s, %q is not an valida email address", k, v)) + } + return warnings, errors +} diff --git a/internal/services/apimanagement/validate/api_management_api_email_address_test.go b/internal/services/apimanagement/validate/api_management_api_email_address_test.go new file mode 100644 index 000000000000..c8bba8d69476 --- /dev/null +++ b/internal/services/apimanagement/validate/api_management_api_email_address_test.go @@ -0,0 +1,47 @@ +package validate + +import "testing" + +func TestEmailAddress(t *testing.T) { + testData := []struct { + Value string + Error bool + }{ + { + Value: "a", + Error: true, + }, + { + Value: "abc", + Error: true, + }, + { + Value: "123", + Error: true, + }, + { + Value: "test.com", + Error: true, + }, + { + Value: "test@.com", + Error: true, + }, + { + Value: "test.com", + Error: true, + }, + { + Value: "test@test.com", + Error: false, + }, + } + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Value) + + _, err := EmailAddress(v.Value, "unit test") + if err != nil && !v.Error { + t.Fatalf("Expected pass but got an error: %s", err) + } + } +}