Skip to content

Commit

Permalink
add resource "azurerm_feature"
Browse files Browse the repository at this point in the history
  • Loading branch information
ms-henglu committed Jun 30, 2021
1 parent 2b36b7b commit 6e81fb3
Show file tree
Hide file tree
Showing 16 changed files with 1,663 additions and 0 deletions.
6 changes: 6 additions & 0 deletions azurerm/internal/services/resource/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package client
import (
providers "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
"github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2019-06-01-preview/templatespecs"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2015-12-01/features"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-09-01/locks"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common"
)

type Client struct {
DeploymentsClient *resources.DeploymentsClient
FeaturesClient *features.Client
GroupsClient *resources.GroupsClient
LocksClient *locks.ManagementLocksClient
ProvidersClient *providers.ProvidersClient
Expand All @@ -22,6 +24,9 @@ func NewClient(o *common.ClientOptions) *Client {
deploymentsClient := resources.NewDeploymentsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&deploymentsClient.Client, o.ResourceManagerAuthorizer)

featuresClient := features.NewClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&featuresClient.Client, o.ResourceManagerAuthorizer)

groupsClient := resources.NewGroupsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&groupsClient.Client, o.ResourceManagerAuthorizer)

Expand All @@ -45,6 +50,7 @@ func NewClient(o *common.ClientOptions) *Client {
return &Client{
GroupsClient: &groupsClient,
DeploymentsClient: &deploymentsClient,
FeaturesClient: &featuresClient,
LocksClient: &locksClient,
ProvidersClient: &providersClient,
ResourceProvidersClient: &resourceProvidersClient,
Expand Down
198 changes: 198 additions & 0 deletions azurerm/internal/services/resource/feature_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package resource

import (
"context"
"fmt"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2015-12-01/features"
"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/resource/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
)

const (
Pending = "Pending"
Registering = "Registering"
Unregistering = "Unregistering"
Registered = "Registered"
NotRegistered = "NotRegistered"
Unregistered = "Unregistered"
)

func resourceFeature() *pluginsdk.Resource {
return &pluginsdk.Resource{
Create: resourceFeatureCreate,
Read: resourceFeatureRead,
Delete: resourceFeatureDelete,
Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
_, err := parse.FeatureID(id)
return err
}),

Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Update: pluginsdk.DefaultTimeout(30 * time.Minute),
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
},

Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"provider_namespace": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
}
}

func resourceFeatureCreate(d *pluginsdk.ResourceData, meta interface{}) error {
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
client := meta.(*clients.Client).Resource.FeaturesClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
providerNamespace := d.Get("provider_namespace").(string)
id := parse.NewFeatureID(subscriptionId, providerNamespace, name)

existing, err := client.Get(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return fmt.Errorf("error checking for existing feature %q: %+v", id, err)
}

if existing.Properties != nil && existing.Properties.State != nil {
if strings.EqualFold(*existing.Properties.State, Pending) {
return fmt.Errorf("feature (%q) which requires manual approval should not be managed by terraform", id)
}
if !strings.EqualFold(*existing.Properties.State, NotRegistered) && !strings.EqualFold(*existing.Properties.State, Unregistered) {
return tf.ImportAsExistsError("azurerm_feature", id.ID())
}
}

resp, err := client.Register(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return fmt.Errorf("error registering feature %q: %+v", id, err)
}

if resp.Properties != nil && resp.Properties.State != nil {
if strings.EqualFold(*resp.Properties.State, Pending) {
return fmt.Errorf("feature (%q) which requires manual approval can not be managed by terraform", id)
}
}

deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("context had no deadline")
}
stateConf := &pluginsdk.StateChangeConf{
Pending: []string{Registering},
Target: []string{Registered},
Refresh: featureRegisteringStateRefreshFunc(ctx, client, id),
MinTimeout: 3 * time.Minute,
Timeout: time.Until(deadline),
}

if _, err = stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for feature(%q) registering to be completed: %+v", id, err)
}

d.SetId(id.ID())

return resourceFeatureRead(d, meta)
}

func resourceFeatureRead(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Resource.FeaturesClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.FeatureID(d.Id())
if err != nil {
return err
}

resp, err := client.Get(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return fmt.Errorf("error retrieving feature %q: %+v", id, err)
}
if resp.Properties != nil && resp.Properties.State != nil {
if strings.EqualFold(*resp.Properties.State, Pending) {
return fmt.Errorf("feature (%q) which requires manual approval can not be managed by terraform", id)
}
if !strings.EqualFold(*resp.Properties.State, Registered) {
return fmt.Errorf("feature (%q) is not registered", id)
}
}

d.Set("name", id.Name)
d.Set("provider_namespace", id.ProviderNamespace)
return nil
}

func resourceFeatureDelete(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Resource.FeaturesClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.FeatureID(d.Id())
if err != nil {
return err
}

resp, err := client.Unregister(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return fmt.Errorf("error unregistering feature %q: %+v", id, err)
}

if resp.Properties != nil && resp.Properties.State != nil {
if strings.EqualFold(*resp.Properties.State, Pending) {
return fmt.Errorf("feature (%q) which requires manual approval can not be managed by terraform", id)
}
}

deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("context had no deadline")
}
stateConf := &pluginsdk.StateChangeConf{
Pending: []string{Unregistering},
Target: []string{NotRegistered, Unregistered},
Refresh: featureRegisteringStateRefreshFunc(ctx, client, *id),
MinTimeout: 3 * time.Minute,
Timeout: time.Until(deadline),
}

if _, err = stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for feature(%q) registering to be completed: %+v", id, err)
}

return nil
}

func featureRegisteringStateRefreshFunc(ctx context.Context, client *features.Client, id parse.FeatureId) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return nil, "", fmt.Errorf("retrieving feature (%q): %+v", id, err)
}
if res.Properties == nil || res.Properties.State == nil {
return nil, "", fmt.Errorf("error reading feature (%q) registering status: %+v", id, err)
}

return res, *res.Properties.State, nil
}
}
104 changes: 104 additions & 0 deletions azurerm/internal/services/resource/feature_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package resource_test

import (
"context"
"fmt"
"strings"
"testing"

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

type FeatureResource struct {
}

func TestAccFeatureResource_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_feature", "test")
r := FeatureResource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: r.basic(),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
})
}

func TestAccFeatureResource_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_feature", "test")
r := FeatureResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.requiresImportBasic(),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.RequiresImportErrorStep(r.requiresImport),
})
}

func (r FeatureResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.FeatureID(state.ID)
if err != nil {
return nil, err
}
resp, err := client.Resource.FeaturesClient.Get(ctx, id.ProviderNamespace, id.Name)
if err != nil {
return nil, fmt.Errorf("error retrieving feature %q: %+v", id, err)
}
if resp.Properties != nil && resp.Properties.State != nil {
if strings.EqualFold(*resp.Properties.State, "Pending") {
return nil, fmt.Errorf("feature (%q) which requires manual approval can not be managed by terraform", id)
}
if !strings.EqualFold(*resp.Properties.State, "Registered") {
return utils.Bool(false), nil
}
}
return utils.Bool(true), nil
}

func (r FeatureResource) basic() string {
return `
provider "azurerm" {
features {}
}
resource "azurerm_feature" "test" {
name = "AutoApproveFeature"
provider_namespace = "Microsoft.CognitiveServices"
}
`
}

func (r FeatureResource) requiresImportBasic() string {
return `
provider "azurerm" {
features {}
}
resource "azurerm_feature" "test" {
name = "AllowManagedDisksReplaceOSDisk"
provider_namespace = "Microsoft.Compute"
}
`
}

func (r FeatureResource) requiresImport(data acceptance.TestData) string {
config := r.requiresImportBasic()
return fmt.Sprintf(`
%s
resource "azurerm_feature" "import" {
name = azurerm_feature.test.name
provider_namespace = azurerm_feature.test.provider_namespace
}
`, config)
}
Loading

0 comments on commit 6e81fb3

Please sign in to comment.