Skip to content

Commit 5b311ef

Browse files
authored
feat: added certificate to load-balancer (scaleway#396)
1 parent 6766e4e commit 5b311ef

6 files changed

+461
-0
lines changed

scaleway/helpers_lb.go

+29
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,39 @@ func expandLbHCHTTPS(raw interface{}) *lb.HealthCheckHTTPSConfig {
200200
if raw == nil || len(raw.([]interface{})) != 1 {
201201
return nil
202202
}
203+
203204
rawMap := raw.([]interface{})[0].(map[string]interface{})
204205
return &lb.HealthCheckHTTPSConfig{
205206
URI: rawMap["uri"].(string),
206207
Method: rawMap["method"].(string),
207208
Code: expandInt32Ptr(rawMap["code"]),
208209
}
209210
}
211+
212+
func expandLbLetsEncrypt(raw interface{}) *lb.CreateCertificateRequestLetsencryptConfig {
213+
if raw == nil || len(raw.([]interface{})) != 1 {
214+
return nil
215+
}
216+
217+
rawMap := raw.([]interface{})[0].(map[string]interface{})
218+
alternativeNames := rawMap["subject_alternative_name"].([]interface{})
219+
config := &lb.CreateCertificateRequestLetsencryptConfig{
220+
CommonName: rawMap["common_name"].(string),
221+
}
222+
for _, alternativeName := range alternativeNames {
223+
config.SubjectAlternativeName = append(config.SubjectAlternativeName, alternativeName.(string))
224+
}
225+
return config
226+
}
227+
228+
func expandLbCustomCertificate(raw interface{}) *lb.CreateCertificateRequestCustomCertificate {
229+
if raw == nil || len(raw.([]interface{})) != 1 {
230+
return nil
231+
}
232+
233+
rawMap := raw.([]interface{})[0].(map[string]interface{})
234+
config := &lb.CreateCertificateRequestCustomCertificate{
235+
CertificateChain: rawMap["certificate_chain"].(string),
236+
}
237+
return config
238+
}

scaleway/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ func Provider() terraform.ResourceProvider {
201201
"scaleway_k8s_pool_beta": resourceScalewayK8SPoolBeta(),
202202
"scaleway_lb_beta": resourceScalewayLbBeta(),
203203
"scaleway_lb_backend_beta": resourceScalewayLbBackendBeta(),
204+
"scaleway_lb_certificate_beta": resourceScalewayLbCertificateBeta(),
204205
"scaleway_lb_frontend_beta": resourceScalewayLbFrontendBeta(),
205206
"scaleway_registry_namespace_beta": resourceScalewayRegistryNamespaceBeta(),
206207
"scaleway_rdb_instance_beta": resourceScalewayRdbInstanceBeta(),
+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package scaleway
2+
3+
import (
4+
"errors"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
7+
"github.com/scaleway/scaleway-sdk-go/api/lb/v1"
8+
)
9+
10+
func resourceScalewayLbCertificateBeta() *schema.Resource {
11+
return &schema.Resource{
12+
Create: resourceScalewayLbCertificateBetaCreate,
13+
Read: resourceScalewayLbCertificateBetaRead,
14+
Update: resourceScalewayLbCertificateBetaUpdate,
15+
Delete: resourceScalewayLbCertificateBetaDelete,
16+
SchemaVersion: 0,
17+
Schema: map[string]*schema.Schema{
18+
"lb_id": {
19+
Type: schema.TypeString,
20+
Required: true,
21+
ForceNew: true,
22+
Description: "The load-balancer ID",
23+
},
24+
"name": {
25+
Type: schema.TypeString,
26+
Description: "The name of the load-balancer certificate",
27+
Optional: true,
28+
Computed: true,
29+
},
30+
"letsencrypt": {
31+
ConflictsWith: []string{"custom_certificate"},
32+
MaxItems: 1,
33+
Description: "The Let's Encrypt type certificate configuration",
34+
Type: schema.TypeList,
35+
Optional: true,
36+
ForceNew: true,
37+
Elem: &schema.Resource{
38+
Schema: map[string]*schema.Schema{
39+
"common_name": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
Description: "The main domain name of the certificate",
43+
},
44+
"subject_alternative_name": {
45+
Type: schema.TypeList,
46+
Elem: &schema.Schema{
47+
Type: schema.TypeString,
48+
},
49+
Optional: true,
50+
Description: "The alternative domain names of the certificate",
51+
},
52+
},
53+
},
54+
},
55+
"custom_certificate": {
56+
ConflictsWith: []string{"letsencrypt"},
57+
MaxItems: 1,
58+
Type: schema.TypeList,
59+
Description: "The custom type certificate type configuration",
60+
Optional: true,
61+
ForceNew: true,
62+
Elem: &schema.Resource{
63+
Schema: map[string]*schema.Schema{
64+
"certificate_chain": {
65+
Type: schema.TypeString,
66+
Required: true,
67+
Description: "The full PEM-formatted certificate chain",
68+
},
69+
},
70+
},
71+
},
72+
73+
// Readonly attributes
74+
"common_name": {
75+
Type: schema.TypeString,
76+
Computed: true,
77+
Description: "The main domain name of the certificate",
78+
},
79+
"subject_alternative_name": {
80+
Type: schema.TypeString,
81+
Computed: true,
82+
Description: "The alternative domain names of the certificate",
83+
},
84+
"fingerprint": {
85+
Type: schema.TypeString,
86+
Computed: true,
87+
Description: "The identifier (SHA-1) of the certificate",
88+
},
89+
"not_valid_before": {
90+
Type: schema.TypeString,
91+
Computed: true,
92+
Description: "The not valid before validity bound timestamp",
93+
},
94+
"not_valid_after": {
95+
Type: schema.TypeString,
96+
Computed: true,
97+
Description: "The not valid after validity bound timestamp",
98+
},
99+
"status": {
100+
Type: schema.TypeString,
101+
Computed: true,
102+
Description: "The status of certificate",
103+
},
104+
},
105+
}
106+
}
107+
108+
func resourceScalewayLbCertificateBetaCreate(d *schema.ResourceData, m interface{}) error {
109+
region, lbID, err := parseRegionalID(d.Get("lb_id").(string))
110+
if err != nil {
111+
return err
112+
}
113+
114+
createReq := &lb.CreateCertificateRequest{
115+
Region: region,
116+
LbID: lbID,
117+
Name: expandOrGenerateString(d.Get("name"), "lb-cert"),
118+
Letsencrypt: expandLbLetsEncrypt(d.Get("letsencrypt")),
119+
CustomCertificate: expandLbCustomCertificate(d.Get("custom_certificate")),
120+
}
121+
if createReq.Letsencrypt == nil && createReq.CustomCertificate == nil {
122+
return errors.New("you need to define either letsencrypt or custom_certificate configuration")
123+
}
124+
125+
lbAPI := lbAPI(m)
126+
res, err := lbAPI.CreateCertificate(createReq)
127+
if err != nil {
128+
return err
129+
}
130+
131+
d.SetId(newRegionalId(region, res.ID))
132+
133+
return resourceScalewayLbCertificateBetaRead(d, m)
134+
}
135+
136+
func resourceScalewayLbCertificateBetaRead(d *schema.ResourceData, m interface{}) error {
137+
lbAPI, region, ID, err := lbAPIWithRegionAndID(m, d.Id())
138+
if err != nil {
139+
return err
140+
}
141+
142+
res, err := lbAPI.GetCertificate(&lb.GetCertificateRequest{
143+
CertificateID: ID,
144+
Region: region,
145+
})
146+
147+
if err != nil {
148+
if is404Error(err) {
149+
d.SetId("")
150+
return nil
151+
}
152+
return err
153+
}
154+
155+
_ = d.Set("name", res.Name)
156+
_ = d.Set("common_name", res.CommonName)
157+
_ = d.Set("subject_alternative_name", res.SubjectAlternativeName)
158+
_ = d.Set("fingerprint", res.Fingerprint)
159+
_ = d.Set("not_valid_before", flattenTime(&res.NotValidBefore))
160+
_ = d.Set("not_valid_after", flattenTime(&res.NotValidAfter))
161+
_ = d.Set("status", res.Status)
162+
return nil
163+
}
164+
165+
func resourceScalewayLbCertificateBetaUpdate(d *schema.ResourceData, m interface{}) error {
166+
lbAPI, region, ID, err := lbAPIWithRegionAndID(m, d.Id())
167+
if err != nil {
168+
return err
169+
}
170+
171+
req := &lb.UpdateCertificateRequest{
172+
CertificateID: ID,
173+
Region: region,
174+
Name: d.Get("name").(string),
175+
}
176+
177+
_, err = lbAPI.UpdateCertificate(req)
178+
if err != nil {
179+
return err
180+
}
181+
182+
return resourceScalewayLbCertificateBetaRead(d, m)
183+
}
184+
185+
func resourceScalewayLbCertificateBetaDelete(d *schema.ResourceData, m interface{}) error {
186+
lbAPI, region, ID, err := lbAPIWithRegionAndID(m, d.Id())
187+
if err != nil {
188+
return err
189+
}
190+
191+
err = lbAPI.DeleteCertificate(&lb.DeleteCertificateRequest{
192+
Region: region,
193+
CertificateID: ID,
194+
})
195+
196+
if err != nil && !is404Error(err) {
197+
return err
198+
}
199+
200+
return nil
201+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package scaleway
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
7+
)
8+
9+
func TestAccScalewayLbCertificateBeta(t *testing.T) {
10+
/**
11+
* Note regarding the usage of xip.io
12+
* See the discussion on https://github.com/terraform-providers/terraform-provider-scaleway/pull/396
13+
* Long story short, scaleway API will not permit you to request a certificate in case common name is not pointed
14+
* to the load balancer IP (which is unknown before creating it). In production, this can be overcome by introducing
15+
* an additional step which creates a DNS record and depending on it, but for test purposes, xip.io is an ideal solution.
16+
*/
17+
resource.ParallelTest(t, resource.TestCase{
18+
PreCheck: func() { testAccPreCheck(t) },
19+
Providers: testAccProviders,
20+
CheckDestroy: testAccCheckScalewayLbBetaDestroy,
21+
Steps: []resource.TestStep{
22+
{
23+
Config: `
24+
resource scaleway_lb_beta lb01 {
25+
name = "test-lb"
26+
type = "lb-s"
27+
}
28+
resource scaleway_lb_certificate_beta cert01 {
29+
lb_id = scaleway_lb_beta.lb01.id
30+
name = "test-cert"
31+
letsencrypt {
32+
common_name = "${scaleway_lb_beta.lb01.ip_address}.xip.io"
33+
}
34+
}
35+
`,
36+
Check: resource.ComposeTestCheckFunc(
37+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "name", "test-cert"),
38+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "letsencrypt.#", "1"),
39+
),
40+
},
41+
{
42+
Config: `
43+
resource scaleway_lb_beta lb01 {
44+
name = "test-lb"
45+
type = "lb-s"
46+
}
47+
resource scaleway_lb_certificate_beta cert01 {
48+
lb_id = scaleway_lb_beta.lb01.id
49+
name = "test-cert-new"
50+
letsencrypt {
51+
common_name = "${scaleway_lb_beta.lb01.ip_address}.xip.io"
52+
}
53+
}
54+
`,
55+
Check: resource.ComposeTestCheckFunc(
56+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "name", "test-cert-new"),
57+
),
58+
},
59+
{
60+
Config: `
61+
resource scaleway_lb_beta lb01 {
62+
name = "test-lb"
63+
type = "lb-s"
64+
}
65+
resource scaleway_lb_certificate_beta cert01 {
66+
lb_id = scaleway_lb_beta.lb01.id
67+
name = "test-cert"
68+
letsencrypt {
69+
common_name = "${scaleway_lb_beta.lb01.ip_address}.xip.io"
70+
subject_alternative_name = [
71+
"sub1.${scaleway_lb_beta.lb01.ip_address}.xip.io",
72+
"sub2.${scaleway_lb_beta.lb01.ip_address}.xip.io"
73+
]
74+
}
75+
}
76+
`,
77+
Check: resource.ComposeTestCheckFunc(
78+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "name", "test-cert"),
79+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "letsencrypt.#", "1"),
80+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "letsencrypt.0.subject_alternative_name.#", "2"),
81+
),
82+
},
83+
{
84+
Config: `
85+
resource scaleway_lb_beta lb01 {
86+
name = "test-lb"
87+
type = "lb-s"
88+
}
89+
resource scaleway_lb_certificate_beta cert01 {
90+
lb_id = scaleway_lb_beta.lb01.id
91+
name = "test-custom-cert"
92+
custom_certificate {
93+
certificate_chain = <<EOF
94+
-----BEGIN CERTIFICATE REQUEST-----
95+
MIIBZTCBzwIBADAmMQwwCgYDVQQKDANCYXIxFjAUBgNVBAMMDSouZXhhbXBsZS5j
96+
b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKM8iE+6xpL9ps2x/W/+0wf3
97+
+5Kgcwl+AoqUTNmAT6eaLfj3PMtdAmN+IcGsYXK2F1FENAdfHd70m4mSBoMJeq+F
98+
a2ZEYp+RlFQqldTBSUBld48z9DNkbNmb0cMyVmntnKhPZ5wjNjgsYee2Leesm2lx
99+
Gw2BCpDxGLBmwKFca3bTAgMBAAGgADANBgkqhkiG9w0BAQsFAAOBgQBbelf1jYNX
100+
Y+AbCpbsySJ32iDtlrZkcIDHMlRIefnoXnO8B1RiEY0SU/n/b2tFCAcvkhq0whKj
101+
RPOH6lzT0x3lWxI+C6xpVyURlljHPygVRbD3fig1rFnH3sI2IE3fJEbGJdmmHacA
102+
BRgm3RR+HWWLKB0ygUbOtZk0BLBUqPK+WQ==
103+
-----END CERTIFICATE REQUEST-----
104+
-----BEGIN RSA PRIVATE KEY-----
105+
MIICXQIBAAKBgQCjPIhPusaS/abNsf1v/tMH9/uSoHMJfgKKlEzZgE+nmi349zzL
106+
XQJjfiHBrGFythdRRDQHXx3e9JuJkgaDCXqvhWtmRGKfkZRUKpXUwUlAZXePM/Qz
107+
ZGzZm9HDMlZp7ZyoT2ecIzY4LGHnti3nrJtpcRsNgQqQ8RiwZsChXGt20wIDAQAB
108+
AoGBAINGbhU4jvOtW9T2fGvyEgLJkp7zvC/5D9Akvbz5LJYML0aWhmTB0ubyi/E2
109+
UVQwToZDhFgdTWd9bgxvzB7bo7Z1kiKWTXQSUDTjC3fchE12UU+1/s0LGh9B1cpV
110+
YqFAu7QNG4maXdZ2h+u+T7dioHslS74PFCBExClkwDiUsdcRAkEAzufrw6zgC0R3
111+
x6OMaaPsH2oX9KfR0+Kxj2m02TnwyFPSrSlIGfoSVtU5wBkvCwTBx/IPY70vNw3f
112+
fPacJNldGwJBAMn3/2nqMJUhR3U7bZA3lI+T1z01D7dLbvCafh3oHTYRAbHO2f0z
113+
k1uhn1+2r9QICwaugZwvx7biCfT3rTqXAKkCQGbINwphWnq+bHI0AJCJ6cZBQd07
114+
cLS9LE99x2URr1cUrNdwZmzhGTMhgSq4V/I1Tr4wtQxq8oV60saVC0QS5nkCQQDI
115+
W2NfuNlVN9xhqgC4zspr3KfrqlXa6dQ2j6yJEpjX5+scby3Fh4Kpph4qn1qyJwB5
116+
MmiVfrjK7lYeVA3fT6lxAkAc1QlmGapJ3w9996v6OZ3UqNBzsHsMmze/zGemdMfc
117+
VaSSB5OL7dZ8fLoJctv9EwgIBM3LFhSgAm9ASPHHR9fp
118+
-----END RSA PRIVATE KEY-----
119+
----BEGIN TRUSTED CERTIFICATE-----
120+
MIIBmTCCAQICCQCb2NiBTIlkeTANBgkqhkiG9w0BAQUFADARMQ8wDQYDVQQKDAZG
121+
b29CYXIwHhcNMjAwMzAzMTYwODQ4WhcNMzAwMzAxMTYwODQ4WjARMQ8wDQYDVQQK
122+
DAZGb29CYXIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALba1p/3sJ3E2J0J
123+
Wa6N4Zw9aBAq4OedYszUCYqviG6XiCKZPeI1skfD9bef4pYG67R5OcccCsjwqfT4
124+
HkwKj/2SG4VsJZpvVr9S1uOeiA70KywB9GvuiLZUflrDEy4IEA0s6UBALRgXllX+
125+
QbnE8VC9brzgepQwO6tTU1Vjo9s9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAelXe
126+
TxdxZWXZFldVl7LUS39lsc9CRgIcLbmk/s23zs92zyJcYvarulx2ku4Zrp507RzT
127+
hPrE4kljWeyQnwIXeOSg5UZrsRtRxUTDLBtOrL3PmFZhBaDShSc+1DS4U/kjy/k+
128+
F+6t5ymALbasChWwC7FFgSsF5PtGP4PxZHFdXiE=
129+
-----END TRUSTED CERTIFICATE-----
130+
EOF
131+
}
132+
}
133+
`,
134+
Check: resource.ComposeTestCheckFunc(
135+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "name", "test-custom-cert"),
136+
resource.TestCheckResourceAttr("scaleway_lb_certificate_beta.cert01", "custom_certificate.#", "1"),
137+
),
138+
},
139+
},
140+
})
141+
}

0 commit comments

Comments
 (0)