Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CDI-623: support s3 bucket in origin group #125

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions docs/resources/cdn_origingroup.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ resource "gcore_cdn_origingroup" "origin_group_1" {
backup = true
}
}

resource "gcore_cdn_origingroup" "amazon_s3_origin_group" {
name = "amazon_s3_origin_group"
auth {
s3_type = "amazon"
s3_access_key_id = "123*******************"
s3_secret_access_key = "123*******************"
s3_bucket_name = "bucket-name"
s3_region = "eu-south-2"
}
}

resource "gcore_cdn_origingroup" "other_s3_origin_group" {
name = "other_s3_origin_group"
auth {
s3_type = "other"
s3_storage_hostname = "s3.example.com"
s3_access_key_id = "123*******************"
s3_secret_access_key = "123*******************"
s3_bucket_name = "bucket-name"
}
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -38,17 +60,34 @@ resource "gcore_cdn_origingroup" "origin_group_1" {
### Required

- `name` (String) Name of the origin group
- `origin` (Block Set, Min: 1) Contains information about all IP address or Domain names of your origin and the port if custom (see [below for nested schema](#nestedblock--origin))
- `use_next` (Boolean) This options have two possible values: true — The option is active. In case the origin responds with 4XX or 5XX codes, use the next origin from the list. false — The option is disabled.

### Optional

- `auth` (Block List, Max: 1) Authentication configuration for S3 storage. This field is required unless `origin` is specified. `auth` and `origin` cannot both be specified simultaneously. (see [below for nested schema](#nestedblock--auth))
- `origin` (Block Set) Contains information about all IP address or Domain names of your origin and the port if custom. This field is required unless `auth` is specified. `origin` and `auth` cannot both be specified simultaneously. (see [below for nested schema](#nestedblock--origin))
- `proxy_next_upstream` (Set of String) Available values: error, timeout, invalid_header, http_403, http_404, http_429, http_500, http_502, http_503, http_504.
- `use_next` (Boolean) This options have two possible values: true — The option is active. In case the origin responds with 4XX or 5XX codes, use the next origin from the list. false — The option is disabled.

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--auth"></a>
### Nested Schema for `auth`

Required:

- `s3_access_key_id` (String, Sensitive) Access key ID for the S3 storage
- `s3_bucket_name` (String) Bucket name of the S3 storage
- `s3_secret_access_key` (String, Sensitive) Secret access key for the S3 storage
- `s3_type` (String) Type of the S3 storage, accepted values: 'other' or 'amazon'

Optional:

- `s3_region` (String) Region of the S3 storage, required if s3_type is 'amazon'
- `s3_storage_hostname` (String) Hostname of the S3 storage, required if s3_type is 'other'


<a id="nestedblock--origin"></a>
### Nested Schema for `origin`

Expand Down
22 changes: 22 additions & 0 deletions examples/resources/gcore_cdn_origingroup/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,25 @@ resource "gcore_cdn_origingroup" "origin_group_1" {
backup = true
}
}

resource "gcore_cdn_origingroup" "amazon_s3_origin_group" {
name = "amazon_s3_origin_group"
auth {
s3_type = "amazon"
s3_access_key_id = "123*******************"
s3_secret_access_key = "123*******************"
s3_bucket_name = "bucket-name"
s3_region = "eu-south-2"
}
}

resource "gcore_cdn_origingroup" "other_s3_origin_group" {
name = "other_s3_origin_group"
auth {
s3_type = "other"
s3_storage_hostname = "s3.example.com"
s3_access_key_id = "123*******************"
s3_secret_access_key = "123*******************"
s3_bucket_name = "bucket-name"
}
}
214 changes: 201 additions & 13 deletions gcore/resource_gcore_cdn_origin_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/G-Core/gcorelabscdn-go/origingroups"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceCDNOriginGroup() *schema.Resource {
Expand All @@ -27,13 +28,21 @@ func resourceCDNOriginGroup() *schema.Resource {
},
"use_next": {
Type: schema.TypeBool,
Required: true,
Optional: true,
Default: true,
Description: "This options have two possible values: true — The option is active. In case the origin responds with 4XX or 5XX codes, use the next origin from the list. false — The option is disabled.",
},
"proxy_next_upstream": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Computed: true,
Description: "Available values: error, timeout, invalid_header, http_403, http_404, http_429, http_500, http_502, http_503, http_504.",
},
"origin": {
Type: schema.TypeSet,
Required: true,
Description: "Contains information about all IP address or Domain names of your origin and the port if custom",
Optional: true,
Description: "Contains information about all IP address or Domain names of your origin and the port if custom. This field is required unless `auth` is specified. `origin` and `auth` cannot both be specified simultaneously.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source": {
Expand All @@ -56,22 +65,95 @@ func resourceCDNOriginGroup() *schema.Resource {
},
},
},
"proxy_next_upstream": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
"auth": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Description: "Available values: error, timeout, invalid_header, http_403, http_404, http_429, http_500, http_502, http_503, http_504.",
MaxItems: 1,
Description: "Authentication configuration for S3 storage. This field is required unless `origin` is specified. `auth` and `origin` cannot both be specified simultaneously.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"s3_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"other", "amazon"}, false),
Description: "Type of the S3 storage, accepted values: 'other' or 'amazon'",
},
"s3_storage_hostname": {
Type: schema.TypeString,
Optional: true,
Description: "Hostname of the S3 storage, required if s3_type is 'other'",
},
"s3_access_key_id": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "Access key ID for the S3 storage",
},
"s3_secret_access_key": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "Secret access key for the S3 storage",
},
"s3_region": {
Type: schema.TypeString,
Optional: true,
Description: "Region of the S3 storage, required if s3_type is 'amazon'",
},
"s3_bucket_name": {
Type: schema.TypeString,
Required: true,
Description: "Bucket name of the S3 storage",
},
},
},
},
},
CreateContext: resourceCDNOriginGroupCreate,
ReadContext: resourceCDNOriginGroupRead,
UpdateContext: resourceCDNOriginGroupUpdate,
DeleteContext: resourceCDNOriginGroupDelete,
Description: "Represent origin group",
CustomizeDiff: validateCDNOriginGroupConfig,
}
}

func validateCDNOriginGroupConfig(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error {
_, originExists := diff.GetOk("origin")
authRaw, authExists := diff.GetOk("auth")

if !originExists && !authExists {
return fmt.Errorf("One of `origin` or `auth` must be specified")
}

if originExists && authExists {
return fmt.Errorf("Both `origin` and `auth` cannot be specified at the same time")
}

if authExists {
authList := authRaw.([]interface{})

if len(authList) > 0 {
auth := authList[0].(map[string]interface{})
s3Type := auth["s3_type"].(string)

if s3Type == "other" {
if storageHostname, ok := auth["s3_storage_hostname"].(string); !ok || storageHostname == "" {
return fmt.Errorf("`s3_storage_hostname` is required when `s3_type` is 'other'")
}
}

if s3Type == "amazon" {
if s3Region, ok := auth["s3_region"].(string); !ok || s3Region == "" {
return fmt.Errorf("`s3_region` is required when `s3_type` is 'amazon'")
}
}
}
}

return nil
}

func resourceCDNOriginGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
log.Println("[DEBUG] Start CDN OriginGroup creating")
config := m.(*Config)
Expand All @@ -80,7 +162,15 @@ func resourceCDNOriginGroupCreate(ctx context.Context, d *schema.ResourceData, m
var req origingroups.GroupRequest
req.Name = d.Get("name").(string)
req.UseNext = d.Get("use_next").(bool)
req.Sources = setToSourceRequests(d.Get("origin").(*schema.Set))

if originSet, ok := d.GetOk("origin"); ok {
req.AuthType = "none"
req.Sources = setToSourceRequests(originSet.(*schema.Set))
} else {
req.AuthType = "awsSignatureV4"
req.Auth = listToAuthS3(d.Get("auth").([]interface{}))
req.Sources = nil
}

proxyNextUpstream, ok := d.Get("proxy_next_upstream").(*schema.Set)
if ok && proxyNextUpstream.Len() > 0 {
Expand All @@ -96,7 +186,16 @@ func resourceCDNOriginGroupCreate(ctx context.Context, d *schema.ResourceData, m
}

d.SetId(fmt.Sprintf("%d", result.ID))
resourceCDNOriginGroupRead(ctx, d, m)
diags := resourceCDNOriginGroupRead(ctx, d, m)

if diags.HasError() {
return diags
}

if _, ok := d.GetOk("auth"); ok {
d.Set("auth.0.s3_secret_access_key", req.Auth.S3SecretAccessKey)
d.Set("auth.0.s3_access_key_id", req.Auth.S3AccessKeyID)
}

log.Printf("[DEBUG] Finish CDN OriginGroup creating (id=%d)\n", result.ID)
return nil
Expand Down Expand Up @@ -125,6 +224,24 @@ func resourceCDNOriginGroupRead(ctx context.Context, d *schema.ResourceData, m i
}
d.Set("proxy_next_upstream", result.ProxyNextUpstream)

// keep s3_secret_access_key and s3_access_key_id unchanged by API response
currentSecretAccessKey, keyExists := d.GetOk("auth.0.s3_secret_access_key")
currentAccessKeyID, keyIDExists := d.GetOk("auth.0.s3_access_key_id")
if err := d.Set("auth", authToList(result.Auth)); err != nil {
return diag.FromErr(err)
}
if keyExists && keyIDExists {
authList := d.Get("auth").([]interface{})
if len(authList) > 0 {
authMap := authList[0].(map[string]interface{})
authMap["s3_secret_access_key"] = currentSecretAccessKey
authMap["s3_access_key_id"] = currentAccessKeyID
if err := d.Set("auth", []interface{}{authMap}); err != nil {
return diag.FromErr(err)
}
}
}

log.Println("[DEBUG] Finish CDN OriginGroup reading")
return nil
}
Expand All @@ -143,7 +260,15 @@ func resourceCDNOriginGroupUpdate(ctx context.Context, d *schema.ResourceData, m
var req origingroups.GroupRequest
req.Name = d.Get("name").(string)
req.UseNext = d.Get("use_next").(bool)
req.Sources = setToSourceRequests(d.Get("origin").(*schema.Set))

if originSet, ok := d.GetOk("origin"); ok {
req.AuthType = "none"
req.Sources = setToSourceRequests(originSet.(*schema.Set))
} else {
req.AuthType = "awsSignatureV4"
req.Auth = listToAuthS3(d.Get("auth").([]interface{}))
req.Sources = nil
}

if req.UseNext == true {
proxyNextUpstream, ok := d.Get("proxy_next_upstream").(*schema.Set)
Expand All @@ -159,9 +284,26 @@ func resourceCDNOriginGroupUpdate(ctx context.Context, d *schema.ResourceData, m
return diag.FromErr(err)
}

log.Println("[DEBUG] Finish CDN OriginGroup updating")
diags := resourceCDNOriginGroupRead(ctx, d, m)

if diags.HasError() {
return diags
}

if authList, ok := d.GetOk("auth"); ok {
if len(authList.([]interface{})) > 0 {
authConfig := authList.([]interface{})[0].(map[string]interface{})
if secretAccessKey, ok := authConfig["s3_secret_access_key"].(string); ok {
d.Set("s3_secret_access_key", secretAccessKey)
}
if accessKeyID, ok := authConfig["s3_access_key_id"].(string); ok {
d.Set("s3_access_key_id", accessKeyID)
}
}
}

return resourceCDNOriginGroupRead(ctx, d, m)
log.Println("[DEBUG] Finish CDN OriginGroup updating")
return nil
}

func resourceCDNOriginGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
Expand Down Expand Up @@ -232,3 +374,49 @@ func originSetIDFunc(i interface{}) int {

return int(binary.BigEndian.Uint64(h.Sum(nil)))
}

func listToAuthS3(authList []interface{}) *origingroups.AuthS3 {
if len(authList) == 0 {
return nil
}

authConfig := authList[0].(map[string]interface{})

auth := &origingroups.AuthS3{
S3Type: authConfig["s3_type"].(string),
S3AccessKeyID: authConfig["s3_access_key_id"].(string),
S3SecretAccessKey: authConfig["s3_secret_access_key"].(string),
S3BucketName: authConfig["s3_bucket_name"].(string),
}

if s3StorageHostname, ok := authConfig["s3_storage_hostname"]; ok {
auth.S3StorageHostname = s3StorageHostname.(string)
}

if s3Region, ok := authConfig["s3_region"]; ok {
auth.S3Region = s3Region.(string)
}

return auth
}

func authToList(auth *origingroups.AuthS3) []interface{} {
if auth == nil {
return nil
}

authMap := map[string]interface{}{
"s3_type": auth.S3Type,
"s3_bucket_name": auth.S3BucketName,
}

if auth.S3StorageHostname != "" {
authMap["s3_storage_hostname"] = auth.S3StorageHostname
}

if auth.S3Region != "" {
authMap["s3_region"] = auth.S3Region
}

return []interface{}{authMap}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/AlekSi/pointer v1.2.0
github.com/G-Core/gcore-dns-sdk-go v0.2.9
github.com/G-Core/gcore-storage-sdk-go v0.1.34
github.com/G-Core/gcorelabscdn-go v1.0.15
github.com/G-Core/gcorelabscdn-go v1.0.16
github.com/G-Core/gcorelabscloud-go v0.8.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ github.com/G-Core/gcorelabscdn-go v1.0.14 h1:s34XWrMeuR/TvmnN0jrb6vsC9IzQFGFhb+q
github.com/G-Core/gcorelabscdn-go v1.0.14/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpONcyLEijwh8WsXpE=
github.com/G-Core/gcorelabscdn-go v1.0.15 h1:KIsZk2gadIlX3kSQJNRHS+HMtabHsdw0f0ARJSYlcxI=
github.com/G-Core/gcorelabscdn-go v1.0.15/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpONcyLEijwh8WsXpE=
github.com/G-Core/gcorelabscdn-go v1.0.16 h1:Sxr/8krN/dMikoFd4lYQjJKGbK9LlsnKnkTpLKmSsCo=
github.com/G-Core/gcorelabscdn-go v1.0.16/go.mod h1:iSGXaTvZBzDHQW+rKFS918BgFVpONcyLEijwh8WsXpE=
github.com/G-Core/gcorelabscloud-go v0.8.0 h1:6w+Mikiz+GbHJs1PD+tPD1gIR88Xl3UPkJuvQVuG7bs=
github.com/G-Core/gcorelabscloud-go v0.8.0/go.mod h1:13Z1USxlxPbDFuYQyWqfNexlk4kUvOYTXbnvV/Z1lZo=
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
Expand Down
Loading