Skip to content

Commit a273a62

Browse files
committed
r/aws_wafv2_api_key: Tidy up.
1 parent 1886ee6 commit a273a62

File tree

7 files changed

+126
-182
lines changed

7 files changed

+126
-182
lines changed

internal/service/wafv2/api_key.go

+106-136
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ package wafv2
55

66
import (
77
"context"
8-
"errors"
9-
"fmt"
108

119
"github.com/YakDriver/regexache"
1210
"github.com/aws/aws-sdk-go-v2/aws"
@@ -22,47 +20,46 @@ import (
2220
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2321
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
2422
"github.com/hashicorp/terraform-plugin-framework/types"
25-
"github.com/hashicorp/terraform-provider-aws/internal/create"
26-
"github.com/hashicorp/terraform-provider-aws/internal/enum"
2723
"github.com/hashicorp/terraform-provider-aws/internal/errs"
2824
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
2925
intflex "github.com/hashicorp/terraform-provider-aws/internal/flex"
3026
"github.com/hashicorp/terraform-provider-aws/internal/framework"
31-
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
27+
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
28+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
29+
tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
3230
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
3331
"github.com/hashicorp/terraform-provider-aws/names"
3432
)
3533

3634
// @FrameworkResource("aws_wafv2_api_key", name="API Key")
37-
func newResourceAPIKey(_ context.Context) (resource.ResourceWithConfigure, error) {
38-
return &resourceAPIKey{}, nil
35+
func newAPIKeyResource(_ context.Context) (resource.ResourceWithConfigure, error) {
36+
return &apiKeyResource{}, nil
3937
}
4038

41-
const (
42-
ResNameAPIKey = "API Key"
43-
// Based on RFC 1034, RFC 1123, and RFC 5890
44-
// - Domain labels must start and end with alphanumeric characters
45-
// - Domain labels can contain hyphens but not at start or end
46-
// - Each domain label can be up to 63 characters
47-
// - Must contain at least one period (separating domain and TLD)
48-
RegexDNSName = `^([0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?\.)+[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?$`
49-
apiKeyIDParts = 2
50-
)
51-
52-
type resourceAPIKey struct {
39+
type apiKeyResource struct {
5340
framework.ResourceWithConfigure
41+
framework.WithNoUpdate
5442
}
5543

56-
func (r *resourceAPIKey) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
57-
resp.Schema = schema.Schema{
44+
func (r *apiKeyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
45+
response.Schema = schema.Schema{
5846
Description: "Provides a WAFv2 API Key resource.",
5947
Attributes: map[string]schema.Attribute{
6048
"api_key": schema.StringAttribute{
6149
Computed: true,
6250
Sensitive: true,
6351
Description: "The API key value. This is sensitive and not included in responses.",
6452
},
53+
names.AttrScope: schema.StringAttribute{
54+
CustomType: fwtypes.StringEnumType[awstypes.Scope](),
55+
Required: true,
56+
PlanModifiers: []planmodifier.String{
57+
stringplanmodifier.RequiresReplace(),
58+
},
59+
Description: "Specifies whether this is for an AWS CloudFront distribution or for a regional application. Valid values are CLOUDFRONT or REGIONAL.",
60+
},
6561
"token_domains": schema.SetAttribute{
62+
CustomType: fwtypes.SetOfStringType,
6663
Required: true,
6764
ElementType: types.StringType,
6865
PlanModifiers: []planmodifier.Set{
@@ -73,196 +70,169 @@ func (r *resourceAPIKey) Schema(ctx context.Context, req resource.SchemaRequest,
7370
setvalidator.SizeAtMost(5),
7471
setvalidator.ValueStringsAre(
7572
stringvalidator.RegexMatches(
76-
regexache.MustCompile(RegexDNSName),
73+
// Based on RFC 1034, RFC 1123, and RFC 5890
74+
// - Domain labels must start and end with alphanumeric characters
75+
// - Domain labels can contain hyphens but not at start or end
76+
// - Each domain label can be up to 63 characters
77+
// - Must contain at least one period (separating domain and TLD)
78+
regexache.MustCompile(`^([0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?\.)+[0-9A-Za-z]([0-9A-Za-z-]{0,61}[0-9A-Za-z])?$`),
7779
"domain names must follow DNS format with valid characters: A-Z, a-z, 0-9, -(hyphen) or . (period)",
7880
),
7981
stringvalidator.LengthAtMost(253),
8082
),
8183
},
8284
Description: "The domains that you want to be able to use the API key with, for example example.com. Maximum of 5 domains.",
8385
},
84-
names.AttrScope: schema.StringAttribute{
85-
Required: true,
86-
PlanModifiers: []planmodifier.String{
87-
stringplanmodifier.RequiresReplace(),
88-
},
89-
Validators: []validator.String{
90-
stringvalidator.OneOf(enum.Slice(awstypes.ScopeCloudfront, awstypes.ScopeRegional)...),
91-
},
92-
Description: "Specifies whether this is for an AWS CloudFront distribution or for a regional application. Valid values are CLOUDFRONT or REGIONAL.",
93-
},
9486
},
9587
}
9688
}
9789

98-
func (r *resourceAPIKey) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
99-
var plan resourceAPIKeyModel
100-
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
101-
if resp.Diagnostics.HasError() {
90+
func (r *apiKeyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
91+
var data apiKeyResourceModel
92+
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
93+
if response.Diagnostics.HasError() {
10294
return
10395
}
10496

10597
conn := r.Meta().WAFV2Client(ctx)
10698

10799
input := wafv2.CreateAPIKeyInput{
108-
Scope: awstypes.Scope(plan.Scope.ValueString()),
109-
TokenDomains: flex.ExpandFrameworkStringValueSet(ctx, plan.TokenDomains),
100+
Scope: data.Scope.ValueEnum(),
101+
TokenDomains: fwflex.ExpandFrameworkStringValueSet(ctx, data.TokenDomains),
110102
}
111103

112-
resp.Diagnostics.Append(flex.Expand(ctx, plan, &input, flex.WithFieldNamePrefix("APIKey"))...)
113-
if resp.Diagnostics.HasError() {
114-
return
115-
}
104+
output, err := conn.CreateAPIKey(ctx, &input)
116105

117-
out, err := conn.CreateAPIKey(ctx, &input)
118106
if err != nil {
119-
resp.Diagnostics.AddError(
120-
create.ProblemStandardMessage(names.WAFV2, create.ErrActionCreating, ResNameAPIKey, "", err),
121-
err.Error(),
122-
)
123-
return
124-
}
125-
if out == nil || out.APIKey == nil {
126-
resp.Diagnostics.AddError(
127-
create.ProblemStandardMessage(names.WAFV2, create.ErrActionCreating, ResNameAPIKey, "", nil),
128-
errors.New("empty output").Error(),
129-
)
107+
response.Diagnostics.AddError("creating WAFv2 API Key", err.Error())
108+
130109
return
131110
}
132111

133-
plan.APIKey = types.StringValue(*out.APIKey)
112+
// Set values for unknowns.
113+
data.APIKey = fwflex.StringToFramework(ctx, output.APIKey)
114+
115+
response.Diagnostics.Append(response.State.Set(ctx, data)...)
116+
}
134117

135-
resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...)
136-
if resp.Diagnostics.HasError() {
118+
func (r *apiKeyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
119+
var data apiKeyResourceModel
120+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
121+
if response.Diagnostics.HasError() {
137122
return
138123
}
139124

140-
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
141-
}
142-
143-
func (r *resourceAPIKey) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
144125
conn := r.Meta().WAFV2Client(ctx)
145126

146-
var state resourceAPIKeyModel
147-
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
148-
if resp.Diagnostics.HasError() {
149-
return
150-
}
127+
output, err := findAPIKeyByTwoPartKey(ctx, conn, data.APIKey.ValueString(), data.Scope.ValueEnum())
128+
129+
if tfresource.NotFound(err) {
130+
response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
131+
response.State.RemoveResource(ctx)
151132

152-
out, err := findAPIKeyByKey(ctx, conn, state.APIKey.ValueString(), state.Scope.ValueString())
153-
if errs.IsA[*tfresource.EmptyResultError](err) || tfresource.NotFound(err) {
154-
resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
155-
resp.State.RemoveResource(ctx)
156133
return
157134
}
135+
158136
if err != nil {
159-
resp.Diagnostics.AddError(
160-
create.ProblemStandardMessage(names.WAFV2, create.ErrActionReading, ResNameAPIKey, state.APIKey.String(), err),
161-
err.Error(),
162-
)
137+
response.Diagnostics.AddError("reading WAFv2 API Key", err.Error())
138+
163139
return
164140
}
165141

166-
resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...)
167-
if resp.Diagnostics.HasError() {
142+
// Set attributes for import.
143+
response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...)
144+
if response.Diagnostics.HasError() {
168145
return
169146
}
170147

171-
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
148+
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
172149
}
173150

174-
func (r *resourceAPIKey) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
175-
// WAFv2 APIKey cannot be updated - any change requires replacement so we reuse existing data
176-
var state resourceAPIKeyModel
177-
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
178-
if resp.Diagnostics.HasError() {
151+
func (r *apiKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
152+
var data apiKeyResourceModel
153+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
154+
if response.Diagnostics.HasError() {
179155
return
180156
}
181157

182-
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
183-
}
184-
185-
func (r *resourceAPIKey) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
186158
conn := r.Meta().WAFV2Client(ctx)
187159

188-
var state resourceAPIKeyModel
189-
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
190-
if resp.Diagnostics.HasError() {
191-
return
192-
}
193-
194160
input := wafv2.DeleteAPIKeyInput{
195-
APIKey: state.APIKey.ValueStringPointer(),
196-
Scope: awstypes.Scope(state.Scope.ValueString()),
161+
APIKey: fwflex.StringFromFramework(ctx, data.APIKey),
162+
Scope: data.Scope.ValueEnum(),
197163
}
198164

199165
_, err := conn.DeleteAPIKey(ctx, &input)
166+
200167
if errs.IsA[*awstypes.WAFNonexistentItemException](err) {
201168
return
202169
}
170+
203171
if err != nil {
204-
resp.Diagnostics.AddError(
205-
create.ProblemStandardMessage(names.WAFV2, create.ErrActionDeleting, ResNameAPIKey, state.APIKey.String(), err),
206-
err.Error(),
207-
)
172+
response.Diagnostics.AddError("deleting WAFv2 API Key", err.Error())
173+
208174
return
209175
}
210176
}
211177

212-
func (r *resourceAPIKey) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
213-
parts, err := intflex.ExpandResourceId(req.ID, apiKeyIDParts, true)
178+
func (r *apiKeyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
179+
const (
180+
apiKeyIDParts = 2
181+
)
182+
parts, err := intflex.ExpandResourceId(request.ID, apiKeyIDParts, true)
183+
214184
if err != nil {
215-
resp.Diagnostics.AddError(
216-
"Unexpected Import Identifier",
217-
fmt.Sprintf("Expected import identifier with format: api_key,scope. Valid scope values are CLOUDFRONT or REGIONAL. Got: %q", req.ID),
218-
)
185+
response.Diagnostics.Append(fwdiag.NewParsingResourceIDErrorDiagnostic(err))
186+
219187
return
220188
}
221189

222-
scope := parts[1]
223-
if scope != string(awstypes.ScopeCloudfront) && scope != string(awstypes.ScopeRegional) {
224-
resp.Diagnostics.AddError(
225-
"Invalid Scope Value",
226-
fmt.Sprintf("Expected scope to be one of %q or %q. Got: %q",
227-
string(awstypes.ScopeCloudfront), string(awstypes.ScopeRegional), scope),
228-
)
229-
return
190+
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("api_key"), parts[0])...)
191+
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root(names.AttrScope), parts[1])...)
192+
}
193+
194+
func findAPIKeyByTwoPartKey(ctx context.Context, conn *wafv2.Client, key string, scope awstypes.Scope) (*awstypes.APIKeySummary, error) {
195+
input := wafv2.ListAPIKeysInput{
196+
Scope: scope,
230197
}
231198

232-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("api_key"), parts[0])...)
233-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root(names.AttrScope), parts[1])...)
199+
return findAPIKey(ctx, conn, &input, func(v *awstypes.APIKeySummary) bool {
200+
return aws.ToString(v.APIKey) == key
201+
})
234202
}
235203

236-
func findAPIKeyByKey(ctx context.Context, conn *wafv2.Client, key, scope string) (*awstypes.APIKeySummary, error) {
237-
input := &wafv2.ListAPIKeysInput{
238-
Scope: awstypes.Scope(scope),
204+
func findAPIKey(ctx context.Context, conn *wafv2.Client, input *wafv2.ListAPIKeysInput, filter tfslices.Predicate[*awstypes.APIKeySummary]) (*awstypes.APIKeySummary, error) {
205+
output, err := findAPIKeys(ctx, conn, input, filter)
206+
207+
if err != nil {
208+
return nil, err
239209
}
240210

241-
for {
242-
output, err := conn.ListAPIKeys(ctx, input)
243-
if err != nil {
244-
return nil, fmt.Errorf("listing API Keys: %w", err)
211+
return tfresource.AssertSingleValueResult(output)
212+
}
213+
214+
func findAPIKeys(ctx context.Context, conn *wafv2.Client, input *wafv2.ListAPIKeysInput, filter tfslices.Predicate[*awstypes.APIKeySummary]) ([]awstypes.APIKeySummary, error) {
215+
var output []awstypes.APIKeySummary
216+
217+
err := listAPIKeysPages(ctx, conn, input, func(page *wafv2.ListAPIKeysOutput, lastPage bool) bool {
218+
if page == nil {
219+
return !lastPage
245220
}
246221

247-
for _, apiKey := range output.APIKeySummaries {
248-
if aws.ToString(apiKey.APIKey) == key {
249-
return &apiKey, nil
222+
for _, v := range page.APIKeySummaries {
223+
if filter(&v) {
224+
output = append(output, v)
250225
}
251226
}
252227

253-
if output.NextMarker == nil {
254-
break
255-
}
256-
input.NextMarker = output.NextMarker
257-
}
228+
return !lastPage
229+
})
258230

259-
return nil, &tfresource.EmptyResultError{
260-
LastRequest: input,
261-
}
231+
return output, err
262232
}
263233

264-
type resourceAPIKeyModel struct {
265-
APIKey types.String `tfsdk:"api_key"`
266-
Scope types.String `tfsdk:"scope"`
267-
TokenDomains types.Set `tfsdk:"token_domains"`
234+
type apiKeyResourceModel struct {
235+
APIKey types.String `tfsdk:"api_key"`
236+
Scope fwtypes.StringEnum[awstypes.Scope] `tfsdk:"scope"`
237+
TokenDomains fwtypes.SetOfString `tfsdk:"token_domains"`
268238
}

0 commit comments

Comments
 (0)