From 1a1b16d695c05c3d146a82cd66f72cc31e191f57 Mon Sep 17 00:00:00 2001 From: aristosvo <8375124+aristosvo@users.noreply.github.com> Date: Wed, 19 May 2021 08:02:33 +0200 Subject: [PATCH] New Resource `azurerm_static_site` (#7150) Co-authored-by: jackofallops Co-authored-by: magodo Co-authored-by: kt Fixes #7029 --- .../internal/services/web/client/client.go | 5 + .../services/web/parse/static_site.go | 69 ++++++ .../services/web/parse/static_site_test.go | 112 ++++++++++ azurerm/internal/services/web/registration.go | 1 + azurerm/internal/services/web/resourceids.go | 1 + .../services/web/static_site_resource.go | 204 ++++++++++++++++++ .../services/web/static_site_resource_test.go | 100 +++++++++ .../services/web/validate/static_site.go | 16 ++ .../services/web/validate/static_site_id.go | 23 ++ .../web/validate/static_site_id_test.go | 76 +++++++ .../web/static-site/azure-static-web-app.tpl | 48 +++++ examples/web/static-site/main.tf | 51 +++++ website/docs/r/static_site.html.markdown | 60 ++++++ 13 files changed, 766 insertions(+) create mode 100644 azurerm/internal/services/web/parse/static_site.go create mode 100644 azurerm/internal/services/web/parse/static_site_test.go create mode 100644 azurerm/internal/services/web/static_site_resource.go create mode 100644 azurerm/internal/services/web/static_site_resource_test.go create mode 100644 azurerm/internal/services/web/validate/static_site.go create mode 100644 azurerm/internal/services/web/validate/static_site_id.go create mode 100644 azurerm/internal/services/web/validate/static_site_id_test.go create mode 100644 examples/web/static-site/azure-static-web-app.tpl create mode 100644 examples/web/static-site/main.tf create mode 100644 website/docs/r/static_site.html.markdown diff --git a/azurerm/internal/services/web/client/client.go b/azurerm/internal/services/web/client/client.go index 7bef029e92fa6..56c14da1e2893 100644 --- a/azurerm/internal/services/web/client/client.go +++ b/azurerm/internal/services/web/client/client.go @@ -12,6 +12,7 @@ type Client struct { BaseClient *web.BaseClient CertificatesClient *web.CertificatesClient CertificatesOrderClient *web.AppServiceCertificateOrdersClient + StaticSitesClient *web.StaticSitesClient } func NewClient(o *common.ClientOptions) *Client { @@ -33,6 +34,9 @@ func NewClient(o *common.ClientOptions) *Client { certificatesOrderClient := web.NewAppServiceCertificateOrdersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&certificatesOrderClient.Client, o.ResourceManagerAuthorizer) + staticSitesClient := web.NewStaticSitesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&staticSitesClient.Client, o.ResourceManagerAuthorizer) + return &Client{ AppServiceEnvironmentsClient: &appServiceEnvironmentsClient, AppServicePlansClient: &appServicePlansClient, @@ -40,5 +44,6 @@ func NewClient(o *common.ClientOptions) *Client { BaseClient: &baseClient, CertificatesClient: &certificatesClient, CertificatesOrderClient: &certificatesOrderClient, + StaticSitesClient: &staticSitesClient, } } diff --git a/azurerm/internal/services/web/parse/static_site.go b/azurerm/internal/services/web/parse/static_site.go new file mode 100644 index 0000000000000..be6e64a6f1204 --- /dev/null +++ b/azurerm/internal/services/web/parse/static_site.go @@ -0,0 +1,69 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type StaticSiteId struct { + SubscriptionId string + ResourceGroup string + Name string +} + +func NewStaticSiteID(subscriptionId, resourceGroup, name string) StaticSiteId { + return StaticSiteId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + Name: name, + } +} + +func (id StaticSiteId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Static Site", segmentsStr) +} + +func (id StaticSiteId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/staticSites/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.Name) +} + +// StaticSiteID parses a StaticSite ID into an StaticSiteId struct +func StaticSiteID(input string) (*StaticSiteId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := StaticSiteId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.Name, err = id.PopSegment("staticSites"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/web/parse/static_site_test.go b/azurerm/internal/services/web/parse/static_site_test.go new file mode 100644 index 0000000000000..9d4f7fb75ba90 --- /dev/null +++ b/azurerm/internal/services/web/parse/static_site_test.go @@ -0,0 +1,112 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = StaticSiteId{} + +func TestStaticSiteIDFormatter(t *testing.T) { + actual := NewStaticSiteID("12345678-1234-9876-4563-123456789012", "group1", "my-static-site1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/my-static-site1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestStaticSiteID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *StaticSiteId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/my-static-site1", + Expected: &StaticSiteId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + Name: "my-static-site1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/GROUP1/PROVIDERS/MICROSOFT.WEB/STATICSITES/MY-STATIC-SITE1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := StaticSiteID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/web/registration.go b/azurerm/internal/services/web/registration.go index e3ae37c7e4957..0d18dcc70ebf7 100644 --- a/azurerm/internal/services/web/registration.go +++ b/azurerm/internal/services/web/registration.go @@ -51,6 +51,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_app_service": resourceAppService(), "azurerm_function_app": resourceFunctionApp(), "azurerm_function_app_slot": resourceFunctionAppSlot(), + "azurerm_static_site": resourceStaticSite(), } } diff --git a/azurerm/internal/services/web/resourceids.go b/azurerm/internal/services/web/resourceids.go index 734e806aaa7b0..aa1e9219d44db 100644 --- a/azurerm/internal/services/web/resourceids.go +++ b/azurerm/internal/services/web/resourceids.go @@ -12,4 +12,5 @@ package web //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=HybridConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/hybridConnectionNamespaces/hybridConnectionNamespace1/relays/relay1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedCertificate -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/certificates/customhost.contoso.com //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SlotVirtualNetworkSwiftConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/slots/slot1/config/virtualNetwork +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=StaticSite -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/my-static-site1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualNetworkSwiftConnection -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Web/sites/site1/config/virtualNetwork diff --git a/azurerm/internal/services/web/static_site_resource.go b/azurerm/internal/services/web/static_site_resource.go new file mode 100644 index 0000000000000..18a0a0233f7a3 --- /dev/null +++ b/azurerm/internal/services/web/static_site_resource.go @@ -0,0 +1,204 @@ +package web + +import ( + "fmt" + "log" + "time" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + + "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2020-06-01/web" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceStaticSite() *schema.Resource { + return &schema.Resource{ + Create: resourceStaticSiteCreateOrUpdate, + Read: resourceStaticSiteRead, + Update: resourceStaticSiteCreateOrUpdate, + Delete: resourceStaticSiteDelete, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.StaticSiteID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.StaticSiteName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "sku_tier": { + Type: schema.TypeString, + Optional: true, + Default: "Free", + ValidateFunc: validation.StringInSlice([]string{"Free"}, false), + }, + + "sku_size": { + Type: schema.TypeString, + Optional: true, + Default: "Free", + ValidateFunc: validation.StringInSlice([]string{"Free"}, false), + }, + + "default_host_name": { + Type: schema.TypeString, + Computed: true, + }, + + "api_key": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceStaticSiteCreateOrUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.StaticSitesClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for AzureRM Static Site creation.") + + id := parse.NewStaticSiteID(subscriptionId, d.Get("resource_group_name").(string), d.Get("name").(string)) + + if d.IsNewResource() { + existing, err := client.GetStaticSite(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("failed checking for presence of existing %s: %+v", id, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_static_site", id.ID()) + } + } + + loc := location.Normalize(d.Get("location").(string)) + + siteEnvelope := web.StaticSiteARMResource{ + Sku: &web.SkuDescription{ + Name: utils.String(d.Get("sku_size").(string)), + Tier: utils.String(d.Get("sku_tier").(string)), + }, + StaticSite: &web.StaticSite{}, + Location: &loc, + } + + if _, err := client.CreateOrUpdateStaticSite(ctx, id.ResourceGroup, id.Name, siteEnvelope); err != nil { + return fmt.Errorf("failed creating %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceStaticSiteRead(d, meta) +} + +func resourceStaticSiteRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.StaticSitesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.StaticSiteID(d.Id()) + if err != nil { + return err + } + + resp, err := client.GetStaticSite(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Static Site %q (resource group %q) was not found - removing from state", id.Name, id.ResourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("failed making Read request on %s: %+v", id, err) + } + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + + d.Set("location", location.NormalizeNilable(resp.Location)) + + if prop := resp.StaticSite; prop != nil { + defaultHostname := "" + if prop.DefaultHostname != nil { + defaultHostname = *prop.DefaultHostname + } + d.Set("default_host_name", defaultHostname) + } + + skuName := "" + skuTier := "" + if sku := resp.Sku; sku != nil { + if v := sku.Name; v != nil { + skuName = *v + } + + if v := sku.Tier; v != nil { + skuTier = *v + } + } + d.Set("sku_size", skuName) + d.Set("sku_tier", skuTier) + + secretResp, err := client.ListStaticSiteSecrets(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("listing secretes for %s: %v", id, err) + } + + apiKey := "" + if pkey := secretResp.Properties["apiKey"]; pkey != nil { + apiKey = *pkey + } + d.Set("api_key", apiKey) + + return nil +} + +func resourceStaticSiteDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Web.StaticSitesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.StaticSiteID(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting Static Site %q (resource group %q)", id.Name, id.ResourceGroup) + + resp, err := client.DeleteStaticSite(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return err + } + } + + return nil +} diff --git a/azurerm/internal/services/web/static_site_resource_test.go b/azurerm/internal/services/web/static_site_resource_test.go new file mode 100644 index 0000000000000..a6839a4722b9f --- /dev/null +++ b/azurerm/internal/services/web/static_site_resource_test.go @@ -0,0 +1,100 @@ +package web_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type StaticSiteResource struct{} + +func TestAccAzureStaticSite_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_static_site", "test") + r := StaticSiteResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("default_host_name").Exists(), + check.That(data.ResourceName).Key("api_key").Exists(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAzureStaticSite_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_static_site", "test") + r := StaticSiteResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func (r StaticSiteResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.StaticSiteID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Web.StaticSitesClient.GetStaticSite(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving Static Site %q: %+v", id, err) + } + + return utils.Bool(true), nil +} + +func (r StaticSiteResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_static_site" "test" { + name = "acctestSS-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func (r StaticSiteResource) requiresImport(data acceptance.TestData) string { + template := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_static_site" "import" { + name = azurerm_static_site.test.name + location = azurerm_static_site.test.location + resource_group_name = azurerm_static_site.test.resource_group_name +} +`, template) +} diff --git a/azurerm/internal/services/web/validate/static_site.go b/azurerm/internal/services/web/validate/static_site.go new file mode 100644 index 0000000000000..19bbe2b9a4e24 --- /dev/null +++ b/azurerm/internal/services/web/validate/static_site.go @@ -0,0 +1,16 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func StaticSiteName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^[0-9a-zA-Z-]{1,60}$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and up to 60 characters in length", k)) + } + + return warnings, errors +} diff --git a/azurerm/internal/services/web/validate/static_site_id.go b/azurerm/internal/services/web/validate/static_site_id.go new file mode 100644 index 0000000000000..cfcfcfcad9e21 --- /dev/null +++ b/azurerm/internal/services/web/validate/static_site_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/web/parse" +) + +func StaticSiteID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.StaticSiteID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/web/validate/static_site_id_test.go b/azurerm/internal/services/web/validate/static_site_id_test.go new file mode 100644 index 0000000000000..73dce4065710f --- /dev/null +++ b/azurerm/internal/services/web/validate/static_site_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestStaticSiteID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Web/staticSites/my-static-site1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/GROUP1/PROVIDERS/MICROSOFT.WEB/STATICSITES/MY-STATIC-SITE1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := StaticSiteID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/examples/web/static-site/azure-static-web-app.tpl b/examples/web/static-site/azure-static-web-app.tpl new file mode 100644 index 0000000000000..1783e85fd3dfd --- /dev/null +++ b/examples/web/static-site/azure-static-web-app.tpl @@ -0,0 +1,48 @@ +name: Azure Static Web Apps CI/CD + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, closed] + branches: + - main + +jobs: + build_and_deploy_job: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + runs-on: ubuntu-latest + name: Build and Deploy Job + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: setup vue environment file + run: | + echo "VUE_APP_NOT_SECRET_CODE=some_value" > $GITHUB_WORKSPACE/.env + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v0.0.1-preview + with: + azure_static_web_apps_api_token: $${{ secrets.${ api_token_var } }} + repo_token: $${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match you app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "${ app_location }" # App source code path + api_location: "${ api_location }" # Api source code path - optional + output_location: "${ output_location }" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### + + close_pull_request_job: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + name: Close Pull Request Job + steps: + - name: Close Pull Request + id: closepullrequest + uses: Azure/static-web-apps-deploy@v0.0.1-preview + with: + azure_static_web_apps_api_token: $${{ secrets.${ api_token_var } }} + action: "close" diff --git a/examples/web/static-site/main.tf b/examples/web/static-site/main.tf new file mode 100644 index 0000000000000..16181f82b05ee --- /dev/null +++ b/examples/web/static-site/main.tf @@ -0,0 +1,51 @@ +locals { + api_token_var = "AZURE_STATIC_WEB_APPS_API_TOKEN" +} + +variable "github_token" {} +variable "github_owner" {} + +provider "azurerm" { + features {} +} + +output hostname { + value = azurerm_static_site.test.default_host_name +} + +provider "github" { + token = var.github_token + owner = var.github_owner +} + +resource "azurerm_resource_group" "test" { + name = "example" + location = "west europe" +} + +resource "azurerm_static_site" "test" { + name = "example" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "github_actions_secret" "test" { + repository = "my-first-static-web-app" + secret_name = local.api_token_var + plaintext_value = azurerm_static_site.test.api_key +} + +# This will cause github provider crash, until https://github.com/integrations/terraform-provider-github/pull/732 is merged. +resource "github_repository_file" "foo" { + repository = "my-first-static-web-app" + branch = "main" + file = ".github/workflows/azure-static-web-app.yml" + content = templatefile("./azure-static-web-app.tpl", + { + app_location = "/" + api_location = "api" + output_location = "" + api_token_var = local.api_token_var + } + ) +} diff --git a/website/docs/r/static_site.html.markdown b/website/docs/r/static_site.html.markdown new file mode 100644 index 0000000000000..b1a33f433af0e --- /dev/null +++ b/website/docs/r/static_site.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "App Service (Web Apps)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_static_site" +description: |- + Manages a Static Site. +--- + +# azurerm_static_site + +Manages an App Service Static Site. + +->**NOTE**: After the Static Site is provisioned, you'll need to associate your target repository, which contains your web app, to the Static Site, by following the [Azure Static Site document](https://docs.microsoft.com/en-us/azure/static-web-apps/github-actions-workflow). + +## Example Usage + +```hcl +resource "azurerm_static_site" "example" { + name = "example" + resource_group_name = "example" + location = "West Europe" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Static Web App. Changing this forces a new Static Web App to be created. + +* `location` - (Required) The Azure Region where the Static Web App should exist. Changing this forces a new Static Web App to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Static Web App should exist. Changing this forces a new Static Web App to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Static Web App. + +* `api_key` - The API key of this Static Web App, which is used for later interacting with this Static Web App from other clients, e.g. Github Action. + +* `default_host_name` - The default host name of the Static Web App. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Static Web App. +* `read` - (Defaults to 5 minutes) Used when retrieving the Static Web App. +* `update` - (Defaults to 30 minutes) Used when updating the Static Web App. +* `delete` - (Defaults to 30 minutes) Used when deleting the Static Web App. + +## Import + +Static Web Apps can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_static_site.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Web/staticSites/my-static-site1 +``` \ No newline at end of file