Skip to content

Commit 5a94b82

Browse files
authored
Merge pull request #25073 from DrFaust92/redshift-endpoint
r/redshift_endpoint_access - new resource
2 parents 00e13dd + 018219d commit 5a94b82

File tree

9 files changed

+709
-1
lines changed

9 files changed

+709
-1
lines changed

.changelog/25073.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
aws_redshift_endpoint_access
3+
```

internal/provider/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,7 @@ func Provider() *schema.Provider {
17661766

17671767
"aws_redshift_authentication_profile": redshift.ResourceAuthenticationProfile(),
17681768
"aws_redshift_cluster": redshift.ResourceCluster(),
1769+
"aws_redshift_endpoint_access": redshift.ResourceEndpointAccess(),
17691770
"aws_redshift_event_subscription": redshift.ResourceEventSubscription(),
17701771
"aws_redshift_hsm_client_certificate": redshift.ResourceHSMClientCertificate(),
17711772
"aws_redshift_parameter_group": redshift.ResourceParameterGroup(),

internal/service/redshift/consts.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,15 @@ func clusterAvailabilityZoneRelocationStatus_PendingValues() []string {
7070
clusterAvailabilityZoneRelocationStatusPendingEnabling,
7171
clusterAvailabilityZoneRelocationStatusPendingDisabling,
7272
}
73-
7473
}
7574

75+
const (
76+
endpointAccessStatusActive = "active"
77+
endpointAccessStatusCreating = "creating"
78+
endpointAccessStatusDeleting = "deleting"
79+
endpointAccessStatusModifying = "modifying"
80+
)
81+
7682
const (
7783
propagationTimeout = 2 * time.Minute
7884
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
package redshift
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"regexp"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/service/redshift"
10+
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13+
"github.com/hashicorp/terraform-provider-aws/internal/conns"
14+
"github.com/hashicorp/terraform-provider-aws/internal/flex"
15+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
16+
)
17+
18+
func ResourceEndpointAccess() *schema.Resource {
19+
return &schema.Resource{
20+
Create: resourceEndpointAccessCreate,
21+
Read: resourceEndpointAccessRead,
22+
Update: resourceEndpointAccessUpdate,
23+
Delete: resourceEndpointAccessDelete,
24+
Importer: &schema.ResourceImporter{
25+
State: schema.ImportStatePassthrough,
26+
},
27+
28+
Schema: map[string]*schema.Schema{
29+
"address": {
30+
Type: schema.TypeString,
31+
Computed: true,
32+
},
33+
"cluster_identifier": {
34+
Type: schema.TypeString,
35+
ForceNew: true,
36+
Required: true,
37+
},
38+
"endpoint_name": {
39+
Type: schema.TypeString,
40+
ForceNew: true,
41+
Required: true,
42+
ValidateFunc: validation.All(
43+
validation.StringLenBetween(1, 30),
44+
validation.StringMatch(regexp.MustCompile(`^[0-9a-z-]+$`), "must contain only lowercase alphanumeric characters and hyphens"),
45+
),
46+
},
47+
"port": {
48+
Type: schema.TypeInt,
49+
Computed: true,
50+
},
51+
"resource_owner": {
52+
Type: schema.TypeString,
53+
ForceNew: true,
54+
Optional: true,
55+
Computed: true,
56+
},
57+
"subnet_group_name": {
58+
Type: schema.TypeString,
59+
ForceNew: true,
60+
Required: true,
61+
},
62+
"vpc_endpoint": {
63+
Type: schema.TypeList,
64+
Computed: true,
65+
Elem: &schema.Resource{
66+
Schema: map[string]*schema.Schema{
67+
"network_interface": {
68+
Type: schema.TypeList,
69+
Computed: true,
70+
Elem: &schema.Resource{
71+
Schema: map[string]*schema.Schema{
72+
"availability_zone": {
73+
Type: schema.TypeString,
74+
Computed: true,
75+
},
76+
"network_interface_id": {
77+
Type: schema.TypeString,
78+
Computed: true,
79+
},
80+
"private_ip_address": {
81+
Type: schema.TypeString,
82+
Computed: true,
83+
},
84+
"subnet_id": {
85+
Type: schema.TypeString,
86+
Computed: true,
87+
},
88+
},
89+
},
90+
},
91+
"vpc_endpoint_id": {
92+
Type: schema.TypeString,
93+
Computed: true,
94+
},
95+
"vpc_id": {
96+
Type: schema.TypeString,
97+
Computed: true,
98+
},
99+
},
100+
},
101+
},
102+
"vpc_security_group_ids": {
103+
Type: schema.TypeSet,
104+
Optional: true,
105+
Computed: true,
106+
Elem: &schema.Schema{Type: schema.TypeString},
107+
},
108+
},
109+
}
110+
}
111+
112+
func resourceEndpointAccessCreate(d *schema.ResourceData, meta interface{}) error {
113+
conn := meta.(*conns.AWSClient).RedshiftConn
114+
115+
createOpts := redshift.CreateEndpointAccessInput{
116+
EndpointName: aws.String(d.Get("endpoint_name").(string)),
117+
SubnetGroupName: aws.String(d.Get("subnet_group_name").(string)),
118+
}
119+
120+
if v, ok := d.GetOk("vpc_security_group_ids"); ok && v.(*schema.Set).Len() > 0 {
121+
createOpts.VpcSecurityGroupIds = flex.ExpandStringSet(v.(*schema.Set))
122+
}
123+
124+
if v, ok := d.GetOk("cluster_identifier"); ok {
125+
createOpts.ClusterIdentifier = aws.String(v.(string))
126+
}
127+
128+
if v, ok := d.GetOk("resource_owner"); ok {
129+
createOpts.ResourceOwner = aws.String(v.(string))
130+
}
131+
132+
_, err := conn.CreateEndpointAccess(&createOpts)
133+
if err != nil {
134+
return fmt.Errorf("error creating Redshift endpoint access: %w", err)
135+
}
136+
137+
d.SetId(aws.StringValue(createOpts.EndpointName))
138+
log.Printf("[INFO] Redshift endpoint access ID: %s", d.Id())
139+
140+
if _, err := waitEndpointAccessActive(conn, d.Id()); err != nil {
141+
return fmt.Errorf("error waiting for Redshift Endpoint Access (%s) to be active: %w", d.Id(), err)
142+
}
143+
144+
return resourceEndpointAccessRead(d, meta)
145+
}
146+
147+
func resourceEndpointAccessRead(d *schema.ResourceData, meta interface{}) error {
148+
conn := meta.(*conns.AWSClient).RedshiftConn
149+
150+
endpoint, err := FindEndpointAccessByName(conn, d.Id())
151+
152+
if !d.IsNewResource() && tfresource.NotFound(err) {
153+
log.Printf("[WARN] Redshift endpoint access (%s) not found, removing from state", d.Id())
154+
d.SetId("")
155+
return nil
156+
}
157+
158+
if err != nil {
159+
return fmt.Errorf("error reading Redshift endpoint access (%s): %w", d.Id(), err)
160+
}
161+
162+
d.Set("endpoint_name", endpoint.EndpointName)
163+
d.Set("subnet_group_name", endpoint.SubnetGroupName)
164+
d.Set("vpc_security_group_ids", vpcSgsIdsToSlice(endpoint.VpcSecurityGroups))
165+
d.Set("resource_owner", endpoint.ResourceOwner)
166+
d.Set("cluster_identifier", endpoint.ClusterIdentifier)
167+
d.Set("port", endpoint.Port)
168+
d.Set("address", endpoint.Address)
169+
170+
if err := d.Set("vpc_endpoint", flattenVPCEndpoint(endpoint.VpcEndpoint)); err != nil {
171+
return fmt.Errorf("setting vpc_endpoint: %w", err)
172+
}
173+
174+
return nil
175+
}
176+
177+
func resourceEndpointAccessUpdate(d *schema.ResourceData, meta interface{}) error {
178+
conn := meta.(*conns.AWSClient).RedshiftConn
179+
180+
if d.HasChanges("vpc_security_group_ids") {
181+
_, n := d.GetChange("vpc_security_group_ids")
182+
if n == nil {
183+
n = new(schema.Set)
184+
}
185+
ns := n.(*schema.Set)
186+
187+
var sIds []*string
188+
for _, s := range ns.List() {
189+
sIds = append(sIds, aws.String(s.(string)))
190+
}
191+
192+
_, err := conn.ModifyEndpointAccess(&redshift.ModifyEndpointAccessInput{
193+
EndpointName: aws.String(d.Id()),
194+
VpcSecurityGroupIds: sIds,
195+
})
196+
197+
if err != nil {
198+
return fmt.Errorf("error updating Redshift endpoint access (%s): %w", d.Id(), err)
199+
}
200+
201+
if _, err := waitEndpointAccessActive(conn, d.Id()); err != nil {
202+
return fmt.Errorf("error waiting for Redshift Endpoint Access (%s) to be active: %w", d.Id(), err)
203+
}
204+
}
205+
206+
return resourceEndpointAccessRead(d, meta)
207+
}
208+
209+
func resourceEndpointAccessDelete(d *schema.ResourceData, meta interface{}) error {
210+
conn := meta.(*conns.AWSClient).RedshiftConn
211+
212+
_, err := conn.DeleteEndpointAccess(&redshift.DeleteEndpointAccessInput{
213+
EndpointName: aws.String(d.Id()),
214+
})
215+
216+
if err != nil {
217+
if tfawserr.ErrCodeEquals(err, redshift.ErrCodeEndpointNotFoundFault) {
218+
return nil
219+
}
220+
return err
221+
}
222+
223+
if _, err := waitEndpointAccessDeleted(conn, d.Id()); err != nil {
224+
return fmt.Errorf("error waiting for Redshift Endpoint Access (%s) to be deleted: %w", d.Id(), err)
225+
}
226+
227+
return nil
228+
}
229+
230+
func vpcSgsIdsToSlice(vpsSgsIds []*redshift.VpcSecurityGroupMembership) []string {
231+
VpcSgsSlice := make([]string, 0, len(vpsSgsIds))
232+
for _, s := range vpsSgsIds {
233+
VpcSgsSlice = append(VpcSgsSlice, *s.VpcSecurityGroupId)
234+
}
235+
return VpcSgsSlice
236+
}
237+
238+
func flattenVPCEndpoint(apiObject *redshift.VpcEndpoint) []interface{} {
239+
if apiObject == nil {
240+
return nil
241+
}
242+
243+
tfMap := map[string]interface{}{}
244+
245+
if v := apiObject.NetworkInterfaces; v != nil {
246+
tfMap["network_interface"] = flattenNetworkInterfaces(v)
247+
}
248+
249+
if v := apiObject.VpcEndpointId; v != nil {
250+
tfMap["vpc_endpoint_id"] = aws.StringValue(v)
251+
}
252+
253+
if v := apiObject.VpcId; v != nil {
254+
tfMap["vpc_id"] = aws.StringValue(v)
255+
}
256+
257+
return []interface{}{tfMap}
258+
}
259+
260+
func flattenNetworkInterface(apiObject *redshift.NetworkInterface) map[string]interface{} {
261+
if apiObject == nil {
262+
return nil
263+
}
264+
265+
tfMap := map[string]interface{}{}
266+
267+
if v := apiObject.AvailabilityZone; v != nil {
268+
tfMap["availability_zone"] = aws.StringValue(v)
269+
}
270+
271+
if v := apiObject.NetworkInterfaceId; v != nil {
272+
tfMap["network_interface_id"] = aws.StringValue(v)
273+
}
274+
275+
if v := apiObject.PrivateIpAddress; v != nil {
276+
tfMap["private_ip_address"] = aws.StringValue(v)
277+
}
278+
279+
if v := apiObject.SubnetId; v != nil {
280+
tfMap["subnet_id"] = aws.StringValue(v)
281+
}
282+
283+
return tfMap
284+
}
285+
286+
func flattenNetworkInterfaces(apiObjects []*redshift.NetworkInterface) []interface{} {
287+
if len(apiObjects) == 0 {
288+
return nil
289+
}
290+
291+
var tfList []interface{}
292+
293+
for _, apiObject := range apiObjects {
294+
if apiObject == nil {
295+
continue
296+
}
297+
298+
tfList = append(tfList, flattenNetworkInterface(apiObject))
299+
}
300+
301+
return tfList
302+
}

0 commit comments

Comments
 (0)