Skip to content

Commit f0c647a

Browse files
committed
azurerm_resource_feature_registration: New resource
1 parent e17bb26 commit f0c647a

File tree

4 files changed

+484
-0
lines changed

4 files changed

+484
-0
lines changed

internal/services/resource/registration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (r Registration) Resources() []sdk.Resource {
6666
return []sdk.Resource{
6767
ResourceManagementPrivateLinkAssociationResource{},
6868
ResourceProviderRegistrationResource{},
69+
ResourceProviderFeatureRegistrationResource{},
6970
ResourceManagementPrivateLinkResource{},
7071
ResourceDeploymentScriptAzurePowerShellResource{},
7172
ResourceDeploymentScriptAzureCliResource{},
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package resource
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"log"
10+
"strings"
11+
"time"
12+
13+
"github.com/hashicorp/go-azure-helpers/lang/response"
14+
"github.com/hashicorp/go-azure-sdk/resource-manager/resources/2021-07-01/features"
15+
"github.com/hashicorp/terraform-provider-azurerm/internal/resourceproviders"
16+
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
17+
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
18+
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
19+
)
20+
21+
var _ sdk.Resource = ResourceProviderFeatureRegistrationResource{}
22+
23+
type ResourceProviderFeatureRegistrationResource struct{}
24+
25+
type ResourceProviderFeatureRegistrationModel struct {
26+
Name string `tfschema:"name"`
27+
ProviderName string `tfschema:"provider_name"`
28+
}
29+
30+
func (r ResourceProviderFeatureRegistrationResource) Arguments() map[string]*pluginsdk.Schema {
31+
return map[string]*pluginsdk.Schema{
32+
"name": {
33+
Type: pluginsdk.TypeString,
34+
Required: true,
35+
ForceNew: true,
36+
ValidateFunc: validation.StringIsNotEmpty,
37+
},
38+
39+
"provider_name": {
40+
Type: pluginsdk.TypeString,
41+
Required: true,
42+
ForceNew: true,
43+
ValidateFunc: resourceproviders.EnhancedValidate,
44+
},
45+
}
46+
}
47+
48+
func (r ResourceProviderFeatureRegistrationResource) Attributes() map[string]*pluginsdk.Schema {
49+
return map[string]*pluginsdk.Schema{}
50+
}
51+
52+
func (r ResourceProviderFeatureRegistrationResource) ModelObject() interface{} {
53+
return &ResourceProviderFeatureRegistrationModel{}
54+
}
55+
56+
func (r ResourceProviderFeatureRegistrationResource) ResourceType() string {
57+
return "azurerm_resource_feature_registration"
58+
}
59+
60+
func (r ResourceProviderFeatureRegistrationResource) Create() sdk.ResourceFunc {
61+
return sdk.ResourceFunc{
62+
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
63+
client := metadata.Client.Resource.FeaturesClient
64+
account := metadata.Client.Account
65+
66+
var obj ResourceProviderFeatureRegistrationModel
67+
if err := metadata.Decode(&obj); err != nil {
68+
return err
69+
}
70+
71+
featureId := features.NewFeatureID(account.SubscriptionId, obj.ProviderName, obj.Name)
72+
73+
provider, err := client.Get(ctx, featureId)
74+
if err != nil {
75+
if response.WasNotFound(provider.HttpResponse) {
76+
return fmt.Errorf("%s was not found", featureId)
77+
}
78+
79+
return fmt.Errorf("retrieving %q: %+v", featureId, err)
80+
}
81+
82+
registrationState := ""
83+
if model := provider.Model; model != nil && model.Properties != nil && model.Properties.State != nil {
84+
registrationState = *model.Properties.State
85+
}
86+
87+
if registrationState == "" {
88+
return fmt.Errorf("retrieving %s: `registrationState` was nil", featureId)
89+
}
90+
91+
if strings.EqualFold(registrationState, Registered) {
92+
return metadata.ResourceRequiresImport(r.ResourceType(), featureId)
93+
}
94+
95+
if strings.EqualFold(registrationState, Pending) {
96+
return fmt.Errorf("%s which requires manual approval can not be managed by terraform", featureId)
97+
}
98+
99+
log.Printf("[DEBUG] Registering %s..", featureId)
100+
resp, err := client.Register(ctx, featureId)
101+
if err != nil {
102+
return fmt.Errorf("error registering feature %q: %+v", featureId, err)
103+
}
104+
105+
if resp.Model != nil && resp.Model.Properties != nil && resp.Model.Properties.State != nil {
106+
if strings.EqualFold(*resp.Model.Properties.State, Pending) {
107+
return fmt.Errorf("%s which requires manual approval can not be managed by terraform", featureId)
108+
}
109+
}
110+
111+
deadline, ok := ctx.Deadline()
112+
if !ok {
113+
return fmt.Errorf("internal-error: context had no deadline")
114+
}
115+
stateConf := &pluginsdk.StateChangeConf{
116+
Pending: []string{Registering},
117+
Target: []string{Registered},
118+
Refresh: r.featureRegisteringStateRefreshFunc(ctx, client, featureId),
119+
MinTimeout: 3 * time.Minute,
120+
Timeout: time.Until(deadline),
121+
}
122+
123+
if _, err = stateConf.WaitForStateContext(ctx); err != nil {
124+
return fmt.Errorf("waiting for %s registering to be completed: %+v", featureId, err)
125+
}
126+
127+
log.Printf("[DEBUG] Registered Resource Provider %q.", featureId)
128+
129+
metadata.SetID(featureId)
130+
return nil
131+
},
132+
133+
Timeout: 120 * time.Minute,
134+
}
135+
}
136+
137+
func (r ResourceProviderFeatureRegistrationResource) Read() sdk.ResourceFunc {
138+
return sdk.ResourceFunc{
139+
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
140+
client := metadata.Client.Resource.FeaturesClient
141+
142+
featureId, err := features.ParseFeatureID(metadata.ResourceData.Id())
143+
if err != nil {
144+
return err
145+
}
146+
147+
resp, err := client.Get(ctx, *featureId)
148+
if err != nil {
149+
if response.WasNotFound(resp.HttpResponse) {
150+
return metadata.MarkAsGone(featureId)
151+
}
152+
153+
return fmt.Errorf("retrieving %s: %+v", featureId, err)
154+
}
155+
156+
registrationState := ""
157+
if model := resp.Model; model != nil && model.Properties != nil && model.Properties.State != nil {
158+
registrationState = *model.Properties.State
159+
}
160+
161+
if !strings.EqualFold(registrationState, Registered) {
162+
log.Printf("[WARN] %s was not registered - removing from state", *featureId)
163+
return metadata.MarkAsGone(featureId)
164+
}
165+
166+
return metadata.Encode(&ResourceProviderFeatureRegistrationModel{
167+
Name: featureId.FeatureName,
168+
ProviderName: featureId.ProviderName,
169+
})
170+
},
171+
Timeout: 5 * time.Minute,
172+
}
173+
}
174+
175+
func (r ResourceProviderFeatureRegistrationResource) Delete() sdk.ResourceFunc {
176+
return sdk.ResourceFunc{
177+
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
178+
client := metadata.Client.Resource.FeaturesClient
179+
180+
featureId, err := features.ParseFeatureID(metadata.ResourceData.Id())
181+
if err != nil {
182+
return fmt.Errorf("error checking for existing feature %q: %+v", *featureId, err)
183+
}
184+
185+
existing, err := client.Get(ctx, *featureId)
186+
if err != nil {
187+
return fmt.Errorf("get feature %q: %+v", *featureId, err)
188+
}
189+
190+
if existing.Model != nil && existing.Model.Properties != nil && existing.Model.Properties.State != nil {
191+
if strings.EqualFold(*existing.Model.Properties.State, Pending) {
192+
return fmt.Errorf("%s which requires manual approval should not be managed by terraform", *featureId)
193+
}
194+
if strings.EqualFold(*existing.Model.Properties.State, Unregistered) {
195+
return nil
196+
}
197+
}
198+
199+
log.Printf("[INFO] unregistering feature %q.", *featureId)
200+
201+
resp, err := client.Unregister(ctx, *featureId)
202+
if err != nil {
203+
return fmt.Errorf("unregistering feature %q: %+v", *featureId, err)
204+
}
205+
206+
if resp.Model != nil && resp.Model.Properties != nil && resp.Model.Properties.State != nil {
207+
if strings.EqualFold(*resp.Model.Properties.State, Pending) {
208+
return fmt.Errorf("%s requires manual registration approval and can not be managed by terraform", *featureId)
209+
}
210+
}
211+
212+
deadline, ok := ctx.Deadline()
213+
if !ok {
214+
return fmt.Errorf("internal-error: context had no deadline")
215+
}
216+
stateConf := &pluginsdk.StateChangeConf{
217+
Pending: []string{Unregistering},
218+
Target: []string{NotRegistered, Unregistered},
219+
Refresh: r.featureRegisteringStateRefreshFunc(ctx, client, *featureId),
220+
MinTimeout: 3 * time.Minute,
221+
Timeout: time.Until(deadline),
222+
}
223+
224+
if _, err = stateConf.WaitForStateContext(ctx); err != nil {
225+
return fmt.Errorf("waiting for %s to be complete unregistering: %+v", *featureId, err)
226+
}
227+
228+
log.Printf("[DEBUG] Unregistered Feature %q.", *featureId)
229+
230+
return nil
231+
},
232+
Timeout: 30 * time.Minute,
233+
}
234+
}
235+
236+
func (r ResourceProviderFeatureRegistrationResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
237+
return features.ValidateFeatureID
238+
}
239+
240+
func (r ResourceProviderFeatureRegistrationResource) featureRegisteringStateRefreshFunc(ctx context.Context, client *features.FeaturesClient, id features.FeatureId) pluginsdk.StateRefreshFunc {
241+
return func() (interface{}, string, error) {
242+
res, err := client.Get(ctx, id)
243+
if err != nil {
244+
return nil, "", fmt.Errorf("retrieving %s: %+v", id, err)
245+
}
246+
if res.Model == nil || res.Model.Properties == nil || res.Model.Properties.State == nil {
247+
return nil, "", fmt.Errorf("error reading %s registering status: %+v", id, err)
248+
}
249+
250+
return res, *res.Model.Properties.State, nil
251+
}
252+
}

0 commit comments

Comments
 (0)