Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support app service application logs blob storage #3520

Merged
merged 1 commit into from
Jun 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions azurerm/helpers/azure/app_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,57 @@ func SchemaAppServiceSiteConfig() *schema.Schema {
}
}

func SchemaAppServiceLogsConfig() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"application_logs": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"azure_blob_storage": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"level": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
string(web.Error),
string(web.Information),
string(web.Off),
string(web.Verbose),
string(web.Warning),
}, false),
},
"sas_url": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
"retention_in_days": {
Type: schema.TypeInt,
Required: true,
},
},
},
},
},
},
},
},
},
}
}

func SchemaAppServiceDataSourceSiteConfig() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Expand Down Expand Up @@ -1002,6 +1053,84 @@ func FlattenAppServiceAuthSettings(input *web.SiteAuthSettingsProperties) []inte
return append(results, result)
}

func FlattenAppServiceLogs(input *web.SiteLogsConfigProperties) []interface{} {
results := make([]interface{}, 0)
if input == nil {
return results
}

result := make(map[string]interface{})

if input.ApplicationLogs != nil {
appLogs := make([]interface{}, 0)

appLogsItem := make(map[string]interface{})

if blobStorageInput := input.ApplicationLogs.AzureBlobStorage; blobStorageInput != nil {
blobStorage := make([]interface{}, 0)

blobStorageItem := make(map[string]interface{})

blobStorageItem["level"] = string(blobStorageInput.Level)

if blobStorageInput.SasURL != nil {
blobStorageItem["sas_url"] = *blobStorageInput.SasURL
}

if blobStorageInput.RetentionInDays != nil {
blobStorageItem["retention_in_days"] = *blobStorageInput.RetentionInDays
}

blobStorage = append(blobStorage, blobStorageItem)

appLogsItem["azure_blob_storage"] = blobStorage
}

appLogs = append(appLogs, appLogsItem)

result["application_logs"] = appLogs
}

return append(results, result)
}

func ExpandAppServiceLogs(input interface{}) web.SiteLogsConfigProperties {
configs := input.([]interface{})
logs := web.SiteLogsConfigProperties{}

if len(configs) == 0 {
return logs
}

config := configs[0].(map[string]interface{})

if v, ok := config["application_logs"]; ok {
appLogsConfigs := v.([]interface{})

for _, config := range appLogsConfigs {
appLogsConfig := config.(map[string]interface{})

logs.ApplicationLogs = &web.ApplicationLogsConfig{}

if v, ok := appLogsConfig["azure_blob_storage"]; ok {
storageConfigs := v.([]interface{})

for _, config := range storageConfigs {
storageConfig := config.(map[string]interface{})

logs.ApplicationLogs.AzureBlobStorage = &web.AzureBlobStorageApplicationLogsConfig{
Level: web.LogLevel(storageConfig["level"].(string)),
SasURL: utils.String(storageConfig["sas_url"].(string)),
RetentionInDays: utils.Int32(int32(storageConfig["retention_in_days"].(int))),
}
}
}
}
}

return logs
}

func ExpandAppServiceSiteConfig(input interface{}) web.SiteConfig {
configs := input.([]interface{})
siteConfig := web.SiteConfig{}
Expand Down
43 changes: 42 additions & 1 deletion azurerm/resource_arm_app_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ func resourceArmAppService() *schema.Resource {

"auth_settings": azure.SchemaAppServiceAuthSettings(),

"logs": azure.SchemaAppServiceLogsConfig(),

"client_affinity_enabled": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -292,6 +294,16 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error updating auth settings for App Service %q (Resource Group %q): %+s", name, resGroup, err)
}

logsConfig := azure.ExpandAppServiceLogs(d.Get("logs"))

logs := web.SiteLogsConfig{
ID: read.ID,
SiteLogsConfigProperties: &logsConfig}

if _, err := client.UpdateDiagnosticLogsConfig(ctx, resGroup, name, logs); err != nil {
return fmt.Errorf("Error updating diagnostic logs config for App Service %q (Resource Group %q): %+s", name, resGroup, err)
}

return resourceArmAppServiceUpdate(d, meta)
}

Expand Down Expand Up @@ -365,6 +377,19 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("logs") {
logs := azure.ExpandAppServiceLogs(d.Get("logs"))
id := d.Id()
logsResource := web.SiteLogsConfig{
ID: &id,
SiteLogsConfigProperties: &logs,
}

if _, err := client.UpdateDiagnosticLogsConfig(ctx, resGroup, name, logsResource); err != nil {
return fmt.Errorf("Error updating Diagnostics Logs for App Service %q: %+v", name, err)
}
}

if d.HasChange("client_affinity_enabled") {

affinity := d.Get("client_affinity_enabled").(bool)
Expand Down Expand Up @@ -465,6 +490,11 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error retrieving the AuthSettings for App Service %q (Resource Group %q): %+v", name, resGroup, err)
}

logsResp, err := client.GetDiagnosticLogsConfiguration(ctx, resGroup, name)
if err != nil {
return fmt.Errorf("Error retrieving the DiagnosticsLogsConfiguration for App Service %q (Resource Group %q): %+v", name, resGroup, err)
}

appSettingsResp, err := client.ListApplicationSettings(ctx, resGroup, name)
if err != nil {
if utils.ResponseWasNotFound(appSettingsResp.Response) {
Expand Down Expand Up @@ -515,7 +545,13 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("possible_outbound_ip_addresses", props.PossibleOutboundIPAddresses)
}

if err := d.Set("app_settings", flattenAppServiceAppSettings(appSettingsResp.Properties)); err != nil {
appSettings := flattenAppServiceAppSettings(appSettingsResp.Properties)

// remove DIAGNOSTICS* settings - Azure will sync these, so just maintain the logs block equivalents in the state
delete(appSettings, "DIAGNOSTICS_AZUREBLOBCONTAINERSASURL")
delete(appSettings, "DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS")

if err := d.Set("app_settings", appSettings); err != nil {
return err
}

Expand All @@ -533,6 +569,11 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error setting `auth_settings`: %s", err)
}

logs := azure.FlattenAppServiceLogs(logsResp.SiteLogsConfigProperties)
if err := d.Set("logs", logs); err != nil {
return fmt.Errorf("Error setting `logs`: %s", err)
}

scm := flattenAppServiceSourceControl(scmResp.SiteSourceControlProperties)
if err := d.Set("source_control", scm); err != nil {
return err
Expand Down
65 changes: 65 additions & 0 deletions azurerm/resource_arm_app_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,34 @@ func TestAccAzureRMAppService_localMySql(t *testing.T) {
})
}

func TestAccAzureRMAppService_applicationBlobStorageLogs(t *testing.T) {
resourceName := "azurerm_app_service.test"
ri := tf.AccRandTimeInt()
config := testAccAzureRMAppService_applicationBlobStorageLogs(ri, testLocation())

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMAppServiceDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAppServiceExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "logs.0.application_logs.0.azure_blob_storage.0.level", "Information"),
resource.TestCheckResourceAttr(resourceName, "logs.0.application_logs.0.azure_blob_storage.0.sas_url", "http://x.com/"),
resource.TestCheckResourceAttr(resourceName, "logs.0.application_logs.0.azure_blob_storage.0.retention_in_days", "3"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAzureRMAppService_managedPipelineMode(t *testing.T) {
resourceName := "azurerm_app_service.test"
ri := tf.AccRandTimeInt()
Expand Down Expand Up @@ -2605,6 +2633,43 @@ resource "azurerm_app_service" "test" {
`, rInt, location, rInt, rInt)
}

func testAccAzureRMAppService_applicationBlobStorageLogs(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_app_service_plan" "test" {
name = "acctestASP-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"

sku {
tier = "Standard"
size = "S1"
}
}

resource "azurerm_app_service" "test" {
name = "acctestAS-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
app_service_plan_id = "${azurerm_app_service_plan.test.id}"

logs {
application_logs {
azure_blob_storage {
level = "Information"
sas_url = "http://x.com/"
retention_in_days = 3
}
}
}
}
`, rInt, location, rInt, rInt)
}

func testAccAzureRMAppService_managedPipelineMode(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
Expand Down
24 changes: 24 additions & 0 deletions website/docs/r/app_service.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ The following arguments are supported:

* `https_only` - (Optional) Can the App Service only be accessed via HTTPS? Defaults to `false`.

* `logs` - (Optional) A `logs` block as defined below.

* `site_config` - (Optional) A `site_config` block as defined below.

* `tags` - (Optional) A mapping of tags to assign to the resource.
Expand All @@ -107,6 +109,28 @@ A `identity` block supports the following:

---

A `logs` block supports the following:

* `application_logs` - (Optional) An `application_logs` block as defined below.

---

An `application_logs` block supports the following:

* `azure_blob_storage` - (Optional) An `azure_blob_storage` block as defined below.

---

An `azure_blob_storage` block supports the following:

* `level` - (Required) The level at which to log. Possible values include `Error`, `Warning`, `Information`, `Verbose` and `Off`.

* `sas_url` - (Required) The URL to the storage container, with a Service SAS token appended. **NOTE:** there is currently no means of generating Service SAS tokens with the `azurerm` provider.

* `retention_in_days` - (Required) The number of days to retain logs for.

---

A `site_config` block supports the following:

* `always_on` - (Optional) Should the app be loaded at all times? Defaults to `false`.
Expand Down