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

Shared Dashboard Resource #4357

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/notificationhub"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/portal"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/privatedns"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/recoveryservices"
Expand Down Expand Up @@ -122,6 +123,7 @@ type ArmClient struct {
network *network.Client
notificationHubs *notificationhub.Client
policy *policy.Client
portal *portal.Client
postgres *postgres.Client
recoveryServices *recoveryservices.Client
redis *redis.Client
Expand Down Expand Up @@ -249,6 +251,7 @@ func getArmClient(authConfig *authentication.Config, skipProviderRegistration bo
client.network = network.BuildClient(o)
client.notificationHubs = notificationhub.BuildClient(o)
client.policy = policy.BuildClient(o)
client.portal = portal.BuildClient(o)
client.postgres = postgres.BuildClient(o)
client.privateDns = privatedns.BuildClient(o)
client.recoveryServices = recoveryservices.BuildClient(o)
Expand Down
20 changes: 20 additions & 0 deletions azurerm/internal/services/portal/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package portal

import (
"github.com/Azure/azure-sdk-for-go/services/preview/portal/mgmt/2019-01-01-preview/portal"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common"
)

type Client struct {
DashboardsClient *portal.DashboardsClient
}

func BuildClient(o *common.ClientOptions) *Client {

DashboardsClient := portal.NewDashboardsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&DashboardsClient.Client, o.ResourceManagerAuthorizer)

return &Client{
DashboardsClient: &DashboardsClient,
}
}
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_cosmosdb_sql_container": resourceArmCosmosDbSQLContainer(),
"azurerm_cosmosdb_sql_database": resourceArmCosmosDbSQLDatabase(),
"azurerm_cosmosdb_table": resourceArmCosmosDbTable(),
"azurerm_dashboard": resourceArmDashboard(),
"azurerm_data_factory": resourceArmDataFactory(),
"azurerm_data_factory_dataset_mysql": resourceArmDataFactoryDatasetMySQL(),
"azurerm_data_factory_dataset_postgresql": resourceArmDataFactoryDatasetPostgreSQL(),
Expand Down
153 changes: 153 additions & 0 deletions azurerm/resource_arm_dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package azurerm

import (
"encoding/json"
"fmt"
"github.com/Azure/azure-sdk-for-go/services/preview/portal/mgmt/2019-01-01-preview/portal"
"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
"regexp"
)

func resourceArmDashboard() *schema.Resource {
return &schema.Resource{
Create: resourceArmDashboardCreateUpdate,
Read: resourceArmDashboardRead,
Update: resourceArmDashboardCreateUpdate,
Delete: resourceArmDashboardDelete,
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateDashboardName,
},
"resource_group_name": azure.SchemaResourceGroupName(),
"location": azure.SchemaLocation(),
"tags": tags.Schema(),
"dashboard_properties": {
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: normalizeJson, // this func is in the arm template resource... should i copy it here to own my own one?
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
},
},
}
}

func resourceArmDashboardCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).portal.DashboardsClient
ctx := meta.(*ArmClient).StopContext

t := d.Get("tags").(map[string]interface{})
name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)
location := azure.NormalizeLocation(d.Get("location").(string))
dashboardProps := d.Get("dashboard_properties").(string)

dashboard := portal.Dashboard{
Location: &location,
Tags: tags.Expand(t),
}

var dashboardProperties portal.DashboardProperties

if err := json.Unmarshal([]byte(dashboardProps), &dashboardProperties); err != nil {
return fmt.Errorf("Error parsing JSON: %+v", err)
}
dashboard.DashboardProperties = &dashboardProperties

db, err := client.CreateOrUpdate(ctx, resourceGroup, name, dashboard)
if err != nil {
return fmt.Errorf("Error creating/updating Dashboard %q (Resource Group %q): %+v", name, resourceGroup, err)
}

d.SetId(*db.ID)
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved

// get it back again to set the props
resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
return nil
}
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("Error making Read request for Dashboard %q: %+v", name, err)
}

props, jsonErr := json.Marshal(resp.DashboardProperties)
if jsonErr != nil {
return fmt.Errorf("Error parsing DashboardProperties JSON: %+v", jsonErr)
}
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
d.Set("dashboard_properties", props)
d.Set("name", resp.Name)
d.Set("location", resp.Location)
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved

return resourceArmDashboardRead(d, meta)
}

func resourceArmDashboardRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).portal.DashboardsClient
ctx := meta.(*ArmClient).StopContext

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved

resp, err := client.Get(ctx, resourceGroup, name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
return nil
}
return fmt.Errorf("Error making Read request for Dashboard %q: %+v", name, err)
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
}

d.Set("name", resp.Name)
d.Set("location", resp.Location)
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved

props, jsonErr := json.Marshal(resp.DashboardProperties)
if jsonErr != nil {
return fmt.Errorf("Error parsing DashboardProperties JSON: %+v", jsonErr)
}
d.Set("dashboard_properties", string(props))

return tags.FlattenAndSet(d, resp.Tags)
}

func resourceArmDashboardDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).portal.DashboardsClient
ctx := meta.(*ArmClient).StopContext

id, parseErr := azure.ParseAzureResourceID(d.Id())
if parseErr != nil {
return parseErr
}
resourceGroup := id.ResourceGroup
name := id.Path["dashboards"]

resp, err := client.Delete(ctx, resourceGroup, name)
if err != nil {
if !response.WasNotFound(resp.Response) {
return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err)
}
}

return nil
}

func validateDashboardName(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

if len(value) > 64 {
errors = append(errors, fmt.Errorf("%q may not exceed 64 characters in length", k))
}

// only alpanumeric and hyphens
if matched := regexp.MustCompile(`^[-\w]+$`).Match([]byte(value)); !matched {
errors = append(errors, fmt.Errorf("%q may only contain alphanumeric and hyphen characters", k))
}

return warnings, errors
}
137 changes: 137 additions & 0 deletions azurerm/resource_arm_dashboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package azurerm

import (
"fmt"
"net/http"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
)

func TestAccResourceArmDashboard_basic(t *testing.T) {
resourceName := "azurerm_dashboard.test"
ri := tf.AccRandTimeInt()
resourceGroupName := fmt.Sprintf("acctestRG-%d", ri)
config := testResourceArmDashboard_basic(ri, testLocation())

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMDashboardDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMDashboardExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "resource_group_name", resourceGroupName),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove this assertion? instead we can use an import check (see below) which confirms the state defined locally matches the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

),
},
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved
},
})
}

func testCheckAzureRMDashboardExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Ensure we have enough information in state to look up in API
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

dashboardName := rs.Primary.Attributes["name"]
resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"]
if !hasResourceGroup {
return fmt.Errorf("Bad: no resource group found in state for Dashboard: %s", dashboardName)
}

client := testAccProvider.Meta().(*ArmClient).portal.DashboardsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

resp, err := client.Get(ctx, resourceGroup, dashboardName)
if err != nil {
return fmt.Errorf("Bad: Get on dashboardsClient: %+v", err)
}

if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("Bad: Dashboard %q (resource group: %q) does not exist", dashboardName, resourceGroup)
}

return nil
}
}

func testCheckAzureRMDashboardDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).portal.DashboardsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_dashboard" {
continue
}

name := rs.Primary.Attributes["name"]
resourceGroup := rs.Primary.Attributes["resource_group_name"]

resp, err := client.Get(ctx, resourceGroup, name)

if err != nil {
return nil
}

if resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Dashboard still exists:\n%+v", resp)
}
}

return nil
}

func testResourceArmDashboard_basic(rInt int, location string) string {
return fmt.Sprintf(`
data "azurerm_subscription" "current" {}
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
resource "azurerm_resource_group" "test-group"{
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_dashboard" "test" {
name = "my-test-dashboard"
resource_group_name = azurerm_resource_group.test-group.name
location = azurerm_resource_group.test-group.location
dashboard_properties = <<DASH
{
"lenses": {
"0": {
"order": 0,
"parts": {
"0": {
"position": {
"x": 0,
"y": 0,
"rowSpan": 2,
"colSpan": 3
},
"metadata": {
"inputs": [],
"type": "Extension/HubsExtension/PartType/MarkdownPart",
"settings": {
"content": {
"settings": {
"content": "## This is only a test :)",
"subtitle": "",
"title": "Test MD Tile"
}
}
}
}
}
}
}
}
}
DASH
}
`, rInt, location)
}
Loading