forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
remote/swift: Migrate Swift remote state to a backend
Move the Swift State from a legacy remote state to an official backend. Add `container` and `archive_container` configuration variables, and deprecate `path` and `archive_path` variables. Future improvements: Add support for locking and environments.
- Loading branch information
Showing
8 changed files
with
765 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
package swift | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/gophercloud/gophercloud" | ||
"github.com/gophercloud/gophercloud/openstack" | ||
|
||
"github.com/hashicorp/terraform/backend" | ||
tf_openstack "github.com/hashicorp/terraform/builtin/providers/openstack" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
// New creates a new backend for Swift remote state. | ||
func New() backend.Backend { | ||
s := &schema.Backend{ | ||
Schema: map[string]*schema.Schema{ | ||
"auth_url": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_URL", nil), | ||
Description: descriptions["auth_url"], | ||
}, | ||
|
||
"user_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_USER_ID", ""), | ||
Description: descriptions["user_name"], | ||
}, | ||
|
||
"user_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_USERNAME", ""), | ||
Description: descriptions["user_name"], | ||
}, | ||
|
||
"tenant_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{ | ||
"OS_TENANT_ID", | ||
"OS_PROJECT_ID", | ||
}, ""), | ||
Description: descriptions["tenant_id"], | ||
}, | ||
|
||
"tenant_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{ | ||
"OS_TENANT_NAME", | ||
"OS_PROJECT_NAME", | ||
}, ""), | ||
Description: descriptions["tenant_name"], | ||
}, | ||
|
||
"password": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Sensitive: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_PASSWORD", ""), | ||
Description: descriptions["password"], | ||
}, | ||
|
||
"token": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_AUTH_TOKEN", ""), | ||
Description: descriptions["token"], | ||
}, | ||
|
||
"domain_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{ | ||
"OS_USER_DOMAIN_ID", | ||
"OS_PROJECT_DOMAIN_ID", | ||
"OS_DOMAIN_ID", | ||
}, ""), | ||
Description: descriptions["domain_id"], | ||
}, | ||
|
||
"domain_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.MultiEnvDefaultFunc([]string{ | ||
"OS_USER_DOMAIN_NAME", | ||
"OS_PROJECT_DOMAIN_NAME", | ||
"OS_DOMAIN_NAME", | ||
"OS_DEFAULT_DOMAIN", | ||
}, ""), | ||
Description: descriptions["domain_name"], | ||
}, | ||
|
||
"region_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), | ||
Description: descriptions["region_name"], | ||
}, | ||
|
||
"insecure": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_INSECURE", ""), | ||
Description: descriptions["insecure"], | ||
}, | ||
|
||
"endpoint_type": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_ENDPOINT_TYPE", ""), | ||
}, | ||
|
||
"cacert_file": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_CACERT", ""), | ||
Description: descriptions["cacert_file"], | ||
}, | ||
|
||
"cert": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_CERT", ""), | ||
Description: descriptions["cert"], | ||
}, | ||
|
||
"key": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("OS_KEY", ""), | ||
Description: descriptions["key"], | ||
}, | ||
|
||
"path": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: descriptions["path"], | ||
Deprecated: "Use container instead", | ||
ConflictsWith: []string{"container"}, | ||
}, | ||
|
||
"container": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: descriptions["container"], | ||
}, | ||
|
||
"archive_path": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: descriptions["archive_path"], | ||
Deprecated: "Use archive_container instead", | ||
ConflictsWith: []string{"archive_container"}, | ||
}, | ||
|
||
"archive_container": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: descriptions["archive_container"], | ||
}, | ||
|
||
"expire_after": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Description: descriptions["expire_after"], | ||
}, | ||
}, | ||
} | ||
|
||
result := &Backend{Backend: s} | ||
result.Backend.ConfigureFunc = result.configure | ||
return result | ||
} | ||
|
||
var descriptions map[string]string | ||
|
||
func init() { | ||
descriptions = map[string]string{ | ||
"auth_url": "The Identity authentication URL.", | ||
|
||
"user_name": "Username to login with.", | ||
|
||
"user_id": "User ID to login with.", | ||
|
||
"tenant_id": "The ID of the Tenant (Identity v2) or Project (Identity v3)\n" + | ||
"to login with.", | ||
|
||
"tenant_name": "The name of the Tenant (Identity v2) or Project (Identity v3)\n" + | ||
"to login with.", | ||
|
||
"password": "Password to login with.", | ||
|
||
"token": "Authentication token to use as an alternative to username/password.", | ||
|
||
"domain_id": "The ID of the Domain to scope to (Identity v3).", | ||
|
||
"domain_name": "The name of the Domain to scope to (Identity v3).", | ||
|
||
"region_name": "The name of the Region to use.", | ||
|
||
"insecure": "Trust self-signed certificates.", | ||
|
||
"cacert_file": "A Custom CA certificate.", | ||
|
||
"endpoint_type": "The catalog endpoint type to use.", | ||
|
||
"cert": "A client certificate to authenticate with.", | ||
|
||
"key": "A client private key to authenticate with.", | ||
|
||
"path": "Swift container path to use.", | ||
|
||
"container": "Swift container to create", | ||
|
||
"archive_path": "Swift container path to archive state to.", | ||
|
||
"archive_container": "Swift container to archive state to.", | ||
|
||
"expire_after": "Archive object expiry duration.", | ||
} | ||
} | ||
|
||
type Backend struct { | ||
*schema.Backend | ||
|
||
// Fields below are set from configure | ||
client *gophercloud.ServiceClient | ||
archive bool | ||
archiveContainer string | ||
expireSecs int | ||
container string | ||
} | ||
|
||
func (b *Backend) configure(ctx context.Context) error { | ||
if b.client != nil { | ||
return nil | ||
} | ||
|
||
// Grab the resource data | ||
data := schema.FromContextBackendConfig(ctx) | ||
|
||
config := &tf_openstack.Config{ | ||
CACertFile: data.Get("cacert_file").(string), | ||
ClientCertFile: data.Get("cert").(string), | ||
ClientKeyFile: data.Get("key").(string), | ||
DomainID: data.Get("domain_id").(string), | ||
DomainName: data.Get("domain_name").(string), | ||
EndpointType: data.Get("endpoint_type").(string), | ||
IdentityEndpoint: data.Get("auth_url").(string), | ||
Insecure: data.Get("insecure").(bool), | ||
Password: data.Get("password").(string), | ||
Token: data.Get("token").(string), | ||
TenantID: data.Get("tenant_id").(string), | ||
TenantName: data.Get("tenant_name").(string), | ||
Username: data.Get("user_name").(string), | ||
UserID: data.Get("user_id").(string), | ||
} | ||
|
||
if err := config.LoadAndValidate(); err != nil { | ||
return err | ||
} | ||
|
||
// Assign Container | ||
b.container = data.Get("container").(string) | ||
if b.container == "" { | ||
// Check deprecated field | ||
b.container = data.Get("path").(string) | ||
} | ||
|
||
// Enable object archiving? | ||
if archiveContainer, ok := data.GetOk("archive_container"); ok { | ||
log.Printf("[DEBUG] Archive_container set, enabling object versioning") | ||
b.archive = true | ||
b.archiveContainer = archiveContainer.(string) | ||
} else if archivePath, ok := data.GetOk("archive_path"); ok { | ||
log.Printf("[DEBUG] Archive_path set, enabling object versioning") | ||
b.archive = true | ||
b.archiveContainer = archivePath.(string) | ||
} | ||
|
||
// Enable object expiry? | ||
if expireRaw, ok := data.GetOk("expire_after"); ok { | ||
expire := expireRaw.(string) | ||
log.Printf("[DEBUG] Requested that remote state expires after %s", expire) | ||
|
||
if strings.HasSuffix(expire, "d") { | ||
log.Printf("[DEBUG] Got a days expire after duration. Converting to hours") | ||
days, err := strconv.Atoi(expire[:len(expire)-1]) | ||
if err != nil { | ||
return fmt.Errorf("Error converting expire_after value %s to int: %s", expire, err) | ||
} | ||
|
||
expire = fmt.Sprintf("%dh", days*24) | ||
log.Printf("[DEBUG] Expire after %s hours", expire) | ||
} | ||
|
||
expireDur, err := time.ParseDuration(expire) | ||
if err != nil { | ||
log.Printf("[DEBUG] Error parsing duration %s: %s", expire, err) | ||
return fmt.Errorf("Error parsing expire_after duration '%s': %s", expire, err) | ||
} | ||
log.Printf("[DEBUG] Seconds duration = %d", int(expireDur.Seconds())) | ||
b.expireSecs = int(expireDur.Seconds()) | ||
} | ||
|
||
objClient, err := openstack.NewObjectStorageV1(config.OsClient, gophercloud.EndpointOpts{ | ||
Region: data.Get("region_name").(string), | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
b.client = objClient | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package swift | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/backend" | ||
"github.com/hashicorp/terraform/state" | ||
"github.com/hashicorp/terraform/state/remote" | ||
) | ||
|
||
func (b *Backend) States() ([]string, error) { | ||
return nil, backend.ErrNamedStatesNotSupported | ||
} | ||
|
||
func (b *Backend) DeleteState(name string) error { | ||
return backend.ErrNamedStatesNotSupported | ||
} | ||
|
||
func (b *Backend) State(name string) (state.State, error) { | ||
if name != backend.DefaultStateName { | ||
return nil, backend.ErrNamedStatesNotSupported | ||
} | ||
|
||
client := &RemoteClient{ | ||
client: b.client, | ||
container: b.container, | ||
archive: b.archive, | ||
archiveContainer: b.archiveContainer, | ||
expireSecs: b.expireSecs, | ||
} | ||
|
||
return &remote.State{Client: client}, nil | ||
} |
Oops, something went wrong.