Skip to content

Commit

Permalink
New Data Source: azurerm_virtual_desktop_application_group (#24771)
Browse files Browse the repository at this point in the history
* Applicaion Group Data Source

* Website Documentation for azurerm_virtual_desktop_application_group data source

* Amend Resource name validation

* Resolve requested changes to align the data source with the resource
  • Loading branch information
ASHR4 authored Feb 9, 2024
1 parent 2088a44 commit d347d51
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 11 deletions.
1 change: 1 addition & 0 deletions internal/services/desktopvirtualization/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (r Registration) WebsiteCategories() []string {
func (r Registration) DataSources() []sdk.DataSource {
return []sdk.DataSource{
DesktopVirtualizationWorkspaceDataSource{},
DesktopVirtualizationApplicationGroupDataSource{},
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validate

import (
"fmt"
"regexp"
"strings"
)

func ApplicationGroupName(i interface{}, k string) (warnings []string, errors []error) {
v, ok := i.(string)

if !ok {
errors = append(errors, fmt.Errorf("expected %q to be a string but it wasn't", k))
return
}

// The value must not be empty.
if strings.TrimSpace(v) == "" {
errors = append(errors, fmt.Errorf("%q must not be empty", k))
return
}

const minLength = 3
const maxLength = 64

// Application Group name can be 3-64 characters in length
if len(v) > maxLength || len(v) < minLength {
errors = append(errors, fmt.Errorf("%q must be between %d-%d characters, got %d", k, minLength, maxLength, len(v)))
}

if matched := regexp.MustCompile(`^[a-zA-Z0-9._-]+$`).Match([]byte(v)); !matched {
errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters, dots, dashes and underscores", k))
}

if matched := regexp.MustCompile(`^[a-zA-Z0-9]`).Match([]byte(v)); !matched {
errors = append(errors, fmt.Errorf("%q must begin with an alphanumeric character", k))
}

if matched := regexp.MustCompile(`\w$`).Match([]byte(v)); !matched {
errors = append(errors, fmt.Errorf("%q must end with an alphanumeric character or underscore", k))
}

return warnings, errors
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package validate_test

import (
"testing"

"github.com/hashicorp/terraform-provider-azurerm/internal/services/desktopvirtualization/validate"
)

func TestApplicationGroupName(t *testing.T) {
cases := []struct {
Input string
Valid bool
}{
{
// empty
Input: "",
Valid: false,
},
{
// basic example
Input: "hello",
Valid: true,
},
{
// 63 chars
Input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk",
Valid: true,
},
{
// 64 chars
Input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl",
Valid: true,
},
{
// 65 chars
Input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm",
Valid: false,
},
{
// may contain alphanumerics, dots, dashes and underscores
Input: "hello_world7.goodbye-world4",
Valid: true,
},
{
// must begin with an alphanumeric
Input: "_hello",
Valid: false,
},
{
// can't end with a period
Input: "hello.",
Valid: false,
},
{
// can't end with a dash
Input: "hello-",
Valid: false,
},
{
// can end with an underscore
Input: "hello_",
Valid: true,
},
{
// can't contain an exclamation mark
Input: "hello!",
Valid: false,
},
{
// can start with a number
Input: "0abc",
Valid: true,
},
{
// can contain only numbers
Input: "12345",
Valid: true,
},
{
// can start with upper case letter
Input: "Test",
Valid: true,
},
{
// can end with upper case letter
Input: "TEST",
Valid: true,
},
}

for _, tc := range cases {
_, errs := validate.ApplicationGroupName(tc.Input, "name")
valid := len(errs) == 0

if valid != tc.Valid {
t.Fatalf("expected %s to be %t, got %t", tc.Input, tc.Valid, valid)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package desktopvirtualization

import (
"context"
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-sdk/resource-manager/desktopvirtualization/2022-02-10-preview/applicationgroup"
"github.com/hashicorp/go-azure-sdk/resource-manager/desktopvirtualization/2022-02-10-preview/hostpool"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/desktopvirtualization/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

type DesktopVirtualizationApplicationGroupDataSource struct{}

type DesktopVirtualizationApplicationGroupModel struct {
ApplicationGroupName string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
Location string `tfschema:"location"`
ApplicationGroupType string `tfschema:"type"`
HostPoolId string `tfschema:"host_pool_id"`
WorkspaceId string `tfschema:"workspace_id"`
FriendlyName string `tfschema:"friendly_name"`
Description string `tfschema:"description"`
Tags map[string]string `tfschema:"tags"`
}

var _ sdk.DataSource = DesktopVirtualizationApplicationGroupDataSource{}

func (r DesktopVirtualizationApplicationGroupDataSource) ModelObject() interface{} {
return &DesktopVirtualizationApplicationGroupModel{}
}

func (r DesktopVirtualizationApplicationGroupDataSource) ResourceType() string {
return "azurerm_virtual_desktop_application_group"
}

func (r DesktopVirtualizationApplicationGroupDataSource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validate.ApplicationGroupName,
},

"resource_group_name": commonschema.ResourceGroupNameForDataSource(),
}
}

func (r DesktopVirtualizationApplicationGroupDataSource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"location": commonschema.LocationComputed(),

"type": {
Type: pluginsdk.TypeString,
Computed: true,
},

"host_pool_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"workspace_id": {
Type: pluginsdk.TypeString,
Computed: true,
},

"friendly_name": {
Type: pluginsdk.TypeString,
Computed: true,
},

"description": {
Type: pluginsdk.TypeString,
Computed: true,
},

"tags": commonschema.TagsDataSource(),
}
}

func (r DesktopVirtualizationApplicationGroupDataSource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.DesktopVirtualization.ApplicationGroupsClient
subscriptionId := metadata.Client.Account.SubscriptionId

var state DesktopVirtualizationApplicationGroupModel
if err := metadata.Decode(&state); err != nil {
return err
}

id := applicationgroup.NewApplicationGroupID(subscriptionId, state.ResourceGroupName, state.ApplicationGroupName)

resp, err := client.Get(ctx, id)
if err != nil {
if response.WasNotFound(resp.HttpResponse) {
return fmt.Errorf("%s was not found", id)
}
return fmt.Errorf("retrieving %s: %+v", id, err)
}

model := resp.Model
if model == nil {
return fmt.Errorf("retrieving %s: model was nil", id)
}

state.ApplicationGroupName = id.ApplicationGroupName
state.ResourceGroupName = id.ResourceGroupName
state.Location = location.NormalizeNilable(model.Location)
state.Tags = pointer.From(model.Tags)
state.ApplicationGroupType = string(model.Properties.ApplicationGroupType)

hostPoolId, err := hostpool.ParseHostPoolIDInsensitively(model.Properties.HostPoolArmPath)
if err != nil {
return fmt.Errorf("parsing Host Pool ID %q: %+v", model.Properties.HostPoolArmPath, err)
}
state.HostPoolId = hostPoolId.ID()

if model.Properties.WorkspaceArmPath != nil {
state.WorkspaceId = pointer.From(model.Properties.WorkspaceArmPath)
}

if model.Properties.FriendlyName != nil {
state.FriendlyName = pointer.From(model.Properties.FriendlyName)
}

if model.Properties.Description != nil {
state.Description = pointer.From(model.Properties.Description)
}

metadata.SetID(id)

return metadata.Encode(&state)
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package desktopvirtualization_test

import (
"fmt"
"testing"

"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
)

type DesktopVirtualizationApplicationGroupDataSource struct{}

func TestAccDesktopVirtualizationApplicationGroupDataSource_complete(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_virtual_desktop_application_group", "test")
d := DesktopVirtualizationApplicationGroupDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: d.complete(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("name").IsNotEmpty(),
check.That(data.ResourceName).Key("resource_group_name").IsNotEmpty(),
check.That(data.ResourceName).Key("location").HasValue(location.Normalize(data.Locations.Secondary)),
check.That(data.ResourceName).Key("type").HasValue("RemoteApp"),
check.That(data.ResourceName).Key("host_pool_id").IsNotEmpty(),
check.That(data.ResourceName).Key("friendly_name").HasValue("TestAppGroup"),
check.That(data.ResourceName).Key("description").HasValue("Acceptance Test: An application group"),
check.That(data.ResourceName).Key("tags.Purpose").HasValue("Acceptance-Testing"),
check.That(data.ResourceName).Key("tags.%").HasValue("1"),
),
},
})
}

func (DesktopVirtualizationApplicationGroupDataSource) complete(data acceptance.TestData) string {
template := VirtualDesktopApplicationResource{}.complete(data)
return fmt.Sprintf(`
%s
data "azurerm_virtual_desktop_application_group" "test" {
name = azurerm_virtual_desktop_application_group.test.name
resource_group_name = azurerm_virtual_desktop_application_group.test.resource_group_name
}
`, template)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package desktopvirtualization
import (
"fmt"
"log"
"regexp"
"time"

"github.com/hashicorp/go-azure-helpers/lang/response"
Expand All @@ -21,6 +20,7 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/desktopvirtualization/migration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/desktopvirtualization/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
Expand Down Expand Up @@ -55,16 +55,10 @@ func resourceVirtualDesktopApplicationGroup() *pluginsdk.Resource {

Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.All(
validation.StringIsNotEmpty,
validation.StringMatch(
regexp.MustCompile("^[-a-zA-Z0-9]{1,260}$"),
"Virtual desktop application group name must be 1 - 260 characters long, contain only letters, numbers and hyphens.",
),
),
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.ApplicationGroupName,
},

"location": commonschema.Location(),
Expand Down
Loading

0 comments on commit d347d51

Please sign in to comment.