digitalocean_spaces_bucket: support bucket versioning (#409)

* digitalocean_spaces_bucket: support bucket versioning

* document versioning argument

* remove debugging code

* Update digitalocean/resource_digitalocean_spaces_bucket.go

Co-Authored-By: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>

* test that removing the versioning block will disable versionin

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>
This commit is contained in:
Tom Dyas 2020-04-07 08:53:32 -07:00 committed by GitHub
parent 132f587f34
commit d7f2efda35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 205 additions and 1 deletions

View File

@ -81,6 +81,26 @@ func resourceDigitalOceanBucket() *schema.Resource {
},
},
},
// This is structured as a subobject in case Spaces supports more of s3.VersioningConfiguration
// than just enabling bucket versioning.
"versioning": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return old == "1" && new == "0"
},
},
"bucket_domain_name": {
Type: schema.TypeString,
Description: "The FQDN of the bucket",
@ -160,6 +180,12 @@ func resourceDigitalOceanBucketUpdate(d *schema.ResourceData, meta interface{})
}
}
if d.HasChange("versioning") {
if err := resourceDigitalOceanSpacesBucketVersioningUpdate(svc, d); err != nil {
return err
}
}
return resourceDigitalOceanBucketRead(d, meta)
}
@ -198,7 +224,6 @@ func resourceDigitalOceanBucketRead(d *schema.ResourceData, meta interface{}) er
d.Set("bucket_domain_name", bucketDomainName(d.Get("name").(string), d.Get("region").(string)))
// Add the region as an attribute
locationResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return svc.GetBucketLocation(
&s3.GetBucketLocationInput{
@ -218,8 +243,33 @@ func resourceDigitalOceanBucketRead(d *schema.ResourceData, meta interface{}) er
return err
}
// Read the versioning configuration
versioningResponse, err := retryOnAwsCode(s3.ErrCodeNoSuchBucket, func() (interface{}, error) {
return svc.GetBucketVersioning(&s3.GetBucketVersioningInput{
Bucket: aws.String(d.Id()),
})
})
if err != nil {
return err
}
vcl := make([]map[string]interface{}, 0, 1)
if versioning, ok := versioningResponse.(*s3.GetBucketVersioningOutput); ok {
vc := make(map[string]interface{})
if versioning.Status != nil && *versioning.Status == s3.BucketVersioningStatusEnabled {
vc["enabled"] = true
} else {
vc["enabled"] = false
}
vcl = append(vcl, vc)
}
if err := d.Set("versioning", vcl); err != nil {
return fmt.Errorf("error setting versioning: %s", err)
}
// Set the bucket's name.
d.Set("name", d.Get("name").(string))
// Set the URN attribute.
urn := fmt.Sprintf("do:space:%s", d.Get("name"))
d.Set("urn", urn)
@ -381,6 +431,39 @@ func resourceDigitalOceanBucketCorsUpdate(svc *s3.S3, d *schema.ResourceData) er
return nil
}
func resourceDigitalOceanSpacesBucketVersioningUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
v := d.Get("versioning").([]interface{})
bucket := d.Get("name").(string)
vc := &s3.VersioningConfiguration{}
if len(v) > 0 {
c := v[0].(map[string]interface{})
if c["enabled"].(bool) {
vc.Status = aws.String(s3.BucketVersioningStatusEnabled)
} else {
vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
}
} else {
vc.Status = aws.String(s3.BucketVersioningStatusSuspended)
}
i := &s3.PutBucketVersioningInput{
Bucket: aws.String(bucket),
VersioningConfiguration: vc,
}
log.Printf("[DEBUG] Spaces PUT bucket versioning: %#v", i)
_, err := retryOnAwsCode(s3.ErrCodeNoSuchBucket, func() (interface{}, error) {
return s3conn.PutBucketVersioning(i)
})
if err != nil {
return fmt.Errorf("Error putting Spaces versioning: %s", err)
}
return nil
}
func resourceDigitalOceanBucketImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
if strings.Contains(d.Id(), ",") {
s := strings.Split(d.Id(), ",")

View File

@ -225,6 +225,82 @@ func TestAccDigitalOceanBucket_shouldFailNotFound(t *testing.T) {
})
}
func TestAccDigitalOceanBucket_Versioning(t *testing.T) {
rInt := acctest.RandInt()
resourceName := "digitalocean_spaces_bucket.bucket"
makeConfig := func(includeClause, versioning bool) string {
versioningClause := ""
if includeClause {
versioningClause = fmt.Sprintf(`
versioning {
enabled = %v
}
`, versioning)
}
return fmt.Sprintf(`
resource "digitalocean_spaces_bucket" "bucket" {
name = "tf-test-bucket-%d"
region = "ams3"
%s
}
`, rInt, versioningClause)
}
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanBucketDestroy,
Steps: []resource.TestStep{
{
// No versioning configured.
Config: makeConfig(false, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanBucketExists(resourceName),
testAccCheckDigitalOceanBucketVersioning(
resourceName, ""),
),
},
{
// Enable versioning
Config: makeConfig(true, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanBucketExists(resourceName),
testAccCheckDigitalOceanBucketVersioning(
resourceName, s3.BucketVersioningStatusEnabled),
),
},
{
// Explicitly disable versioning
Config: makeConfig(true, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanBucketExists(resourceName),
testAccCheckDigitalOceanBucketVersioning(
resourceName, s3.BucketVersioningStatusSuspended),
),
},
{
// Re-enable versioning
Config: makeConfig(true, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanBucketExists(resourceName),
testAccCheckDigitalOceanBucketVersioning(
resourceName, s3.BucketVersioningStatusEnabled),
),
},
{
// Remove the clause completely. Should disable versioning.
Config: makeConfig(false, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanBucketExists(resourceName),
testAccCheckDigitalOceanBucketVersioning(
resourceName, s3.BucketVersioningStatusSuspended),
),
},
},
})
}
func testAccCheckDigitalOceanBucketDestroy(s *terraform.State) error {
return testAccCheckDigitalOceanBucketDestroyWithProvider(s, testAccProvider)
}
@ -372,6 +448,44 @@ func testAccCheckDigitalOceanBucketCors(n string, corsRules []*s3.CORSRule) reso
}
}
func testAccCheckDigitalOceanBucketVersioning(n string, versioningStatus string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs := s.RootModule().Resources[n]
sesh, err := session.NewSession(&aws.Config{
Region: aws.String(rs.Primary.Attributes["region"]),
Credentials: credentials.NewStaticCredentials(os.Getenv("SPACES_ACCESS_KEY_ID"), os.Getenv("SPACES_SECRET_ACCESS_KEY"), "")},
)
svc := s3.New(sesh, &aws.Config{
Endpoint: aws.String(fmt.Sprintf("https://%s.digitaloceanspaces.com", rs.Primary.Attributes["region"]))},
)
if err != nil {
log.Fatal(err)
}
out, err := svc.GetBucketVersioning(&s3.GetBucketVersioningInput{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
return fmt.Errorf("GetBucketVersioning error: %v", err)
}
if v := out.Status; v == nil {
if versioningStatus != "" {
return fmt.Errorf("bad error versioning status, found nil, expected: %s", versioningStatus)
}
} else {
if *v != versioningStatus {
return fmt.Errorf("bad error versioning status, expected: %s, got %s", versioningStatus, *v)
}
}
return nil
}
}
func isAWSErr(err error, code string, message string) bool {
if err, ok := err.(awserr.Error); ok {
return err.Code() == code && strings.Contains(err.Message(), message)

View File

@ -78,6 +78,8 @@ The following arguments are supported:
* `name` - (Required) The name of the bucket
* `region` - The region where the bucket resides (Defaults to `nyc3`)
* `acl` - Canned ACL applied on bucket creation (`private` or `public-read`)
* `cors_rule` - (Optional) A rule of Cross-Origin Resource Sharing (documented below).
* `versioning` - (Optional) A state of versioning (documented below)
* `force_destroy` - Unless `true`, the bucket will only be destroyed if empty (Defaults to `false`)
The `cors_rule` object supports the following:
@ -87,6 +89,11 @@ The `cors_rule` object supports the following:
* `allowed_origins` - (Required) A list of hosts from which requests using the specified methods are allowed. A host may contain one wildcard (e.g. http://*.example.com).
* `max_age_seconds` - (Optional) The time in seconds that browser can cache the response for a preflight request.
The `versioning` object supports the following:
* `enabled` - (Optional) Enable versioning. Once you version-enable a bucket, it can never return to an unversioned
state. You can, however, suspend versioning on that bucket.
## Attributes Reference
The following attributes are exported: