@@ -5,8 +5,6 @@ package wafv2
5
5
6
6
import (
7
7
"context"
8
- "errors"
9
- "fmt"
10
8
11
9
"github.com/YakDriver/regexache"
12
10
"github.com/aws/aws-sdk-go-v2/aws"
@@ -22,47 +20,46 @@ import (
22
20
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
23
21
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
24
22
"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"
27
23
"github.com/hashicorp/terraform-provider-aws/internal/errs"
28
24
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
29
25
intflex "github.com/hashicorp/terraform-provider-aws/internal/flex"
30
26
"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"
32
30
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
33
31
"github.com/hashicorp/terraform-provider-aws/names"
34
32
)
35
33
36
34
// @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
39
37
}
40
38
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 {
53
40
framework.ResourceWithConfigure
41
+ framework.WithNoUpdate
54
42
}
55
43
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 {
58
46
Description : "Provides a WAFv2 API Key resource." ,
59
47
Attributes : map [string ]schema.Attribute {
60
48
"api_key" : schema.StringAttribute {
61
49
Computed : true ,
62
50
Sensitive : true ,
63
51
Description : "The API key value. This is sensitive and not included in responses." ,
64
52
},
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
+ },
65
61
"token_domains" : schema.SetAttribute {
62
+ CustomType : fwtypes .SetOfStringType ,
66
63
Required : true ,
67
64
ElementType : types .StringType ,
68
65
PlanModifiers : []planmodifier.Set {
@@ -73,196 +70,169 @@ func (r *resourceAPIKey) Schema(ctx context.Context, req resource.SchemaRequest,
73
70
setvalidator .SizeAtMost (5 ),
74
71
setvalidator .ValueStringsAre (
75
72
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])?$` ),
77
79
"domain names must follow DNS format with valid characters: A-Z, a-z, 0-9, -(hyphen) or . (period)" ,
78
80
),
79
81
stringvalidator .LengthAtMost (253 ),
80
82
),
81
83
},
82
84
Description : "The domains that you want to be able to use the API key with, for example example.com. Maximum of 5 domains." ,
83
85
},
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
- },
94
86
},
95
87
}
96
88
}
97
89
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 () {
102
94
return
103
95
}
104
96
105
97
conn := r .Meta ().WAFV2Client (ctx )
106
98
107
99
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 ),
110
102
}
111
103
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 )
116
105
117
- out , err := conn .CreateAPIKey (ctx , & input )
118
106
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
+
130
109
return
131
110
}
132
111
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
+ }
134
117
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 () {
137
122
return
138
123
}
139
124
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 ) {
144
125
conn := r .Meta ().WAFV2Client (ctx )
145
126
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 )
151
132
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 )
156
133
return
157
134
}
135
+
158
136
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
+
163
139
return
164
140
}
165
141
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 () {
168
145
return
169
146
}
170
147
171
- resp .Diagnostics .Append (resp .State .Set (ctx , & state )... )
148
+ response .Diagnostics .Append (response .State .Set (ctx , & data )... )
172
149
}
173
150
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 () {
179
155
return
180
156
}
181
157
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 ) {
186
158
conn := r .Meta ().WAFV2Client (ctx )
187
159
188
- var state resourceAPIKeyModel
189
- resp .Diagnostics .Append (req .State .Get (ctx , & state )... )
190
- if resp .Diagnostics .HasError () {
191
- return
192
- }
193
-
194
160
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 ( ),
197
163
}
198
164
199
165
_ , err := conn .DeleteAPIKey (ctx , & input )
166
+
200
167
if errs.IsA [* awstypes.WAFNonexistentItemException ](err ) {
201
168
return
202
169
}
170
+
203
171
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
+
208
174
return
209
175
}
210
176
}
211
177
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
+
214
184
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
+
219
187
return
220
188
}
221
189
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 ,
230
197
}
231
198
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
+ })
234
202
}
235
203
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
239
209
}
240
210
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
245
220
}
246
221
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 )
250
225
}
251
226
}
252
227
253
- if output .NextMarker == nil {
254
- break
255
- }
256
- input .NextMarker = output .NextMarker
257
- }
228
+ return ! lastPage
229
+ })
258
230
259
- return nil , & tfresource.EmptyResultError {
260
- LastRequest : input ,
261
- }
231
+ return output , err
262
232
}
263
233
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"`
268
238
}
0 commit comments