Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
35 changes: 16 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# terraform-provider-minio

A [Terraform](https://terraform.io) provider for [Minio](https://min.io), a
A [Terraform](https://terraform.io) provider for [Minio](https://min.io), a
self-hosted object storage server that is compatible with S3.


Check out the documenation on the [Terraform Registry - refaktory/minio](https://registry.terraform.io/providers/refaktory/minio/latest/docs) for more information and usage examples.
Check out the documenation on the
[Terraform Registry - refaktory/minio](https://registry.terraform.io/providers/refaktory/minio/latest/docs)
for more information and usage examples.

## Features

Expand All @@ -15,7 +16,7 @@ Check out the documenation on the [Terraform Registry - refaktory/minio](https:/
- [x] Versioning config
- [ ] Encryption config
- [ ] Replication config
- [ ] Lifecycle config
- [x] Lifecycle config
- [ ] Access rules
- [x] Users
- [x] Create/delete
Expand All @@ -27,7 +28,7 @@ Check out the documenation on the [Terraform Registry - refaktory/minio](https:/
- [x] Create/delete
- [x] Assign policies
- [ ] Objects
- [ ] Create files with a given content
- [ ] Create files with a given content

### Datasources

Expand All @@ -37,16 +38,14 @@ Check out the documenation on the [Terraform Registry - refaktory/minio](https:/
- [x] User
- [ ] Serviceaccount


## Usage

Consult the
[published documenation](https://registry.terraform.io/providers/refaktory/minio/latest/docs)
on the registry for usage documenation.
Consult the
[published documenation](https://registry.terraform.io/providers/refaktory/minio/latest/docs)
on the registry for usage documenation.

Additional examples are available in the `./examples` directory.


## Local Development

### Configure Terraform to use the locally built provider
Expand All @@ -71,25 +70,23 @@ Build the provider with `make build`

### Test manually

* Start a local minio instance in a separate terminal
(and keep it running)
- Start a local minio instance in a separate terminal (and keep it running)
`docker-compose up`

* Use the provider
`cd ./examples && terraform apply`
- Use the provider `cd ./examples && terraform apply`

### Deploy

Steps to deploy:

* `make prepare-release`
* `git tag v0.X.0`
* `git push`
- `make prepare-release`
- `git tag v0.X.0`
- `git push`

Actual publishing is handled by the Github action defined in
Actual publishing is handled by the Github action defined in
`./.github/workflows/release.yml`.

The module is managed on the Terraform registry at
The module is managed on the Terraform registry at
https://registry.terraform.io/publish/provider.

## About
Expand Down
10 changes: 8 additions & 2 deletions examples/provider/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ provider "minio" {
# Create a bucket.
resource "minio_bucket" "bucket" {
name = "bucket"

lifecycle_rule {
expiration {
days = 2
}
}
}

# Create a policy.
resource "minio_canned_policy" "policy1" {
name = "policy1"
name = "policy1"
policy = <<EOT
{
"Version": "2012-10-17",
Expand All @@ -49,7 +55,7 @@ EOT

# Create a user group and assign the specified policies.
resource "minio_group" "group1" {
name = "group1"
name = "group1"
policies = [minio_canned_policy.policy1.name]
}

Expand Down
10 changes: 8 additions & 2 deletions examples/test.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ provider "minio" {
# Create a bucket.
resource "minio_bucket" "bucket" {
name = "bucket"

lifecycle_rule {
expiration {
days = 2
}
}
}

# Create a policy.
resource "minio_canned_policy" "policy1" {
name = "policy1"
name = "policy1"
policy = <<EOT
{
"Version": "2012-10-17",
Expand All @@ -48,7 +54,7 @@ EOT

# Create a user group and assign the specified policies.
resource "minio_group" "group1" {
name = "group1"
name = "group1"
policies = [minio_canned_policy.policy1.name]
}

Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.12.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8=
github.com/hashicorp/terraform-exec v0.14.0 h1:UQoUcxKTZZXhyyK68Cwn4mApT4mnFPmEXPiqaHL9r+w=
Expand Down
245 changes: 245 additions & 0 deletions internal/provider/bucket_lifecycle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package provider

import (
"context"
"fmt"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/minio/minio-go/v7/pkg/lifecycle"
)

const (
keyBucketLifecycleRule = "lifecycle_rule"
keyBucketLifecycleId = "id"
keyBucketLifecycleDays = "days"
keyBucketLifecycleDate = "date"
keyBucketLifecycleExpiration = "expiration"
keyBucketLifecycleTransition = "transition"
keyBucketLifecycleStorageClass = "storage_class"
keyBucketLifecycleEnabled = "enabled"

lifecycleStatusEnabled = "Enabled"
lifecycleStatusDisabled = "Disabled"
lifecycleDateFormat = "2006-01-02"
)

func schemaBucketLifecycle() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
keyBucketLifecycleId: {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
keyBucketLifecycleEnabled: {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
keyBucketLifecycleExpiration: {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
keyBucketLifecycleDate: {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validBucketLifecycleTimestamp,
},
keyBucketLifecycleDays: {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(0),
},
},
},
},
keyBucketLifecycleTransition: {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
keyBucketLifecycleDate: {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validBucketLifecycleTimestamp,
},
keyBucketLifecycleDays: {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(0),
},
keyBucketLifecycleStorageClass: {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
},
}
}

func resourceBucketLifecycleRead(ctx context.Context, bucketName string, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(*minioContext).api

lifecycle, err := client.GetBucketLifecycle(ctx, bucketName)
if err != nil {
if err.Error() == "The lifecycle configuration does not exist" {
lifecycle = nil
} else {
return diag.FromErr(err)
}
}

lifecycleRules := make([]map[string]interface{}, 0)

if lifecycle != nil {
for _, lifecycleRule := range lifecycle.Rules {
rule := make(map[string]interface{})

if lifecycleRule.ID != "" {
rule[keyBucketLifecycleId] = lifecycleRule.ID
}

rule[keyBucketLifecycleEnabled] = (lifecycleRule.Status == lifecycleStatusEnabled)

if !lifecycleRule.Expiration.IsNull() {
expirationList := make([]interface{}, 1)
expiration := make(map[string]interface{})
expirationList[0] = expiration

if !lifecycleRule.Expiration.IsDateNull() {
expiration[keyBucketLifecycleDate] = lifecycleRule.Expiration.Date.Format(lifecycleDateFormat)
}

if !lifecycleRule.Expiration.IsDaysNull() {
expiration[keyBucketLifecycleDays] = int(lifecycleRule.Expiration.Days)
}

rule[keyBucketLifecycleExpiration] = expirationList
}

if !lifecycleRule.Transition.IsNull() {
transitionList := make([]interface{}, 1)
transition := make(map[string]interface{})
transitionList[0] = transition

if !lifecycleRule.Transition.IsDateNull() {
transition[keyBucketLifecycleDate] = lifecycleRule.Transition.Date.Format(lifecycleDateFormat)
}

if !lifecycleRule.Transition.IsDaysNull() {
transition[keyBucketLifecycleDays] = int(lifecycleRule.Transition.Days)
}

transition[keyBucketLifecycleStorageClass] = lifecycleRule.Transition.StorageClass

rule[keyBucketLifecycleTransition] = transitionList
}

lifecycleRules = append(lifecycleRules, rule)
}
}

if err := d.Set(keyBucketLifecycleRule, lifecycleRules); err != nil {
return diag.FromErr(err)
}

return diags
}

func resourceBucketLifecycleUpdate(ctx context.Context, bucketName string, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(*minioContext).api

config := lifecycle.NewConfiguration()
lifecycleRules := d.Get(keyBucketLifecycleRule).([]interface{})

if len(lifecycleRules) > 0 && lifecycleRules[0] != nil {
for _, lifecycleRule := range lifecycleRules {
r := lifecycleRule.(map[string]interface{})
rule := lifecycle.Rule{}

if val, ok := r[keyBucketLifecycleId].(string); ok && val != "" {
rule.ID = val
} else {
rule.ID = resource.PrefixedUniqueId("tf-")
}

if val, ok := r[keyBucketLifecycleEnabled].(bool); ok && val {
rule.Status = lifecycleStatusEnabled
} else {
rule.Status = lifecycleStatusDisabled
}

if expiration, ok := r[keyBucketLifecycleExpiration].([]interface{}); ok && len(expiration) > 0 && expiration[0] != nil {
e := expiration[0].(map[string]interface{})

if val, ok := e[keyBucketLifecycleDate].(string); ok && val != "" {
if date, err := time.Parse(lifecycleDateFormat, val); err != nil {
diags = append(diags, diag.FromErr(err)...)
} else {
rule.Expiration.Date.Time = date
}
}

if val, ok := e[keyBucketLifecycleDays].(int); ok && val != 0 {
rule.Expiration.Days = lifecycle.ExpirationDays(val)
}
}

if transition, ok := r[keyBucketLifecycleTransition].([]interface{}); ok && len(transition) > 0 && transition[0] != nil {
e := transition[0].(map[string]interface{})

if val, ok := e[keyBucketLifecycleDate].(string); ok && val != "" {
if date, err := time.Parse(lifecycleDateFormat, val); err != nil {
diags = append(diags, diag.FromErr(err)...)
} else {
rule.Transition.Date.Time = date
}
}

if val, ok := e[keyBucketLifecycleDays].(int); ok && val != 0 {
rule.Transition.Days = lifecycle.ExpirationDays(val)
}

if val, ok := e[keyBucketLifecycleStorageClass].(string); ok && val != "" {
rule.Transition.StorageClass = val
}
}

config.Rules = append(config.Rules, rule)
}
}

if diags.HasError() {
return diags
}

if err := client.SetBucketLifecycle(ctx, bucketName, config); err != nil {
return diag.FromErr(err)
}

return append(diags, resourceBucketLifecycleRead(ctx, bucketName, d, m)...)
}

func validBucketLifecycleTimestamp(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if _, err := time.Parse(lifecycleDateFormat, value); err != nil {
errors = append(errors, fmt.Errorf("%q does not have a valid date format", value))
}

return
}
Loading