terraform/backend/remote-state/swift/backend.go

330 lines
8.8 KiB
Go

package swift
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema"
tf_openstack "github.com/terraform-providers/terraform-provider-openstack/openstack"
)
// 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", nil),
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),
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 v, ok := data.GetOkExists("insecure"); ok {
insecure := v.(bool)
config.Insecure = &insecure
}
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
}