state/remote/swift: Enhancements to support full set of Openstack configuration options, plus SSL certs. Documentation updated to support

This commit is contained in:
Gavin Williams 2016-11-01 17:03:00 +00:00
parent 6c801d0386
commit bf8612b9b7
2 changed files with 209 additions and 39 deletions

View File

@ -4,7 +4,9 @@ import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -21,7 +23,20 @@ const TFSTATE_NAME = "tfstate.tf"
// SwiftClient implements the Client interface for an Openstack Swift server. // SwiftClient implements the Client interface for an Openstack Swift server.
type SwiftClient struct { type SwiftClient struct {
client *gophercloud.ServiceClient client *gophercloud.ServiceClient
authurl string
cacert string
cert string
domainid string
domainname string
insecure bool
key string
password string
path string path string
region string
tenantid string
tenantname string
userid string
username string
} }
func swiftFactory(conf map[string]string) (Client, error) { func swiftFactory(conf map[string]string) (Client, error) {
@ -35,30 +50,127 @@ func swiftFactory(conf map[string]string) (Client, error) {
} }
func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
if val := os.Getenv("OS_AUTH_URL"); val == "" { authUrl, ok := conf["auth_url"]
return fmt.Errorf("missing OS_AUTH_URL environment variable") if !ok {
authUrl = os.Getenv("OS_AUTH_URL")
if authUrl == "" {
return fmt.Errorf("missing 'auth_url' configuration or OS_AUTH_URL environment variable")
} }
if val := os.Getenv("OS_USERNAME"); val == "" {
return fmt.Errorf("missing OS_USERNAME environment variable")
} }
if val := os.Getenv("OS_TENANT_NAME"); val == "" { c.authurl = authUrl
return fmt.Errorf("missing OS_TENANT_NAME environment variable")
username, ok := conf["user_name"]
if !ok {
username = os.Getenv("OS_USERNAME")
} }
if val := os.Getenv("OS_PASSWORD"); val == "" { c.username = username
return fmt.Errorf("missing OS_PASSWORD environment variable")
userID, ok := conf["user_id"]
if !ok {
userID = os.Getenv("OS_USER_ID")
} }
c.userid = userID
password, ok := conf["password"]
if !ok {
password = os.Getenv("OS_PASSWORD")
if password == "" {
return fmt.Errorf("missing 'password' configuration or OS_PASSWORD environment variable")
}
}
c.password = password
region, ok := conf["region_name"]
if !ok {
region = os.Getenv("OS_REGION_NAME")
}
c.region = region
tenantID, ok := conf["tenant_id"]
if !ok {
tenantID = multiEnv([]string{
"OS_TENANT_ID",
"OS_PROJECT_ID",
})
}
c.tenantid = tenantID
tenantName, ok := conf["tenant_name"]
if !ok {
tenantName = multiEnv([]string{
"OS_TENANT_NAME",
"OS_PROJECT_NAME",
})
}
c.tenantname = tenantName
domainID, ok := conf["domain_id"]
if !ok {
domainID = multiEnv([]string{
"OS_USER_DOMAIN_ID",
"OS_PROJECT_DOMAIN_ID",
"OS_DOMAIN_ID",
})
}
c.domainid = domainID
domainName, ok := conf["domain_name"]
if !ok {
domainName = multiEnv([]string{
"OS_USER_DOMAIN_NAME",
"OS_PROJECT_DOMAIN_NAME",
"OS_DOMAIN_NAME",
"DEFAULT_DOMAIN",
})
}
c.domainname = domainName
path, ok := conf["path"] path, ok := conf["path"]
if !ok || path == "" { if !ok || path == "" {
return fmt.Errorf("missing 'path' configuration") return fmt.Errorf("missing 'path' configuration")
} }
c.path = path
c.insecure = false
raw, ok := conf["insecure"]
if !ok {
raw = os.Getenv("OS_INSECURE")
}
if raw != "" {
v, err := strconv.ParseBool(raw)
if err != nil {
return fmt.Errorf("'insecure' and 'OS_INSECURE' could not be parsed as bool: %s", err)
}
c.insecure = v
}
cacertFile, ok := conf["cacert_file"]
if !ok {
cacertFile = os.Getenv("OS_CACERT")
}
c.cacert = cacertFile
cert, ok := conf["cert"]
if !ok {
cert = os.Getenv("OS_CERT")
}
c.cert = cert
key, ok := conf["key"]
if !ok {
key = os.Getenv("OS_KEY")
}
c.key = key
ao := gophercloud.AuthOptions{ ao := gophercloud.AuthOptions{
IdentityEndpoint: os.Getenv("OS_AUTH_URL"), IdentityEndpoint: c.authurl,
Username: os.Getenv("OS_USERNAME"), UserID: c.userid,
TenantName: os.Getenv("OS_TENANT_NAME"), Username: c.username,
Password: os.Getenv("OS_PASSWORD"), TenantID: c.tenantid,
DomainName: os.Getenv("OS_DOMAIN_NAME"), TenantName: c.tenantname,
DomainID: os.Getenv("OS_DOMAIN_ID"), Password: c.password,
DomainID: c.domainid,
DomainName: c.domainname,
} }
provider, err := openstack.NewClient(ao.IdentityEndpoint) provider, err := openstack.NewClient(ao.IdentityEndpoint)
@ -67,19 +179,33 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
} }
config := &tls.Config{} config := &tls.Config{}
insecure := false
if insecure_env := os.Getenv("OS_INSECURE"); insecure_env != "" { if c.cacert != "" {
insecure, err = strconv.ParseBool(insecure_env) caCert, err := ioutil.ReadFile(c.cacert)
if err != nil { if err != nil {
return err return err
} }
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
config.RootCAs = caCertPool
} }
if insecure { if c.insecure {
log.Printf("[DEBUG] Insecure mode set") log.Printf("[DEBUG] Insecure mode set")
config.InsecureSkipVerify = true config.InsecureSkipVerify = true
} }
if c.cert != "" && c.key != "" {
cert, err := tls.LoadX509KeyPair(c.cert, c.key)
if err != nil {
return err
}
config.Certificates = []tls.Certificate{cert}
config.BuildNameToCertificate()
}
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
provider.HTTPClient.Transport = transport provider.HTTPClient.Transport = transport
@ -88,9 +214,8 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
return err return err
} }
c.path = path
c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{ c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"), Region: c.region,
}) })
return err return err
@ -149,3 +274,12 @@ func (c *SwiftClient) ensureContainerExists() error {
return nil return nil
} }
func multiEnv(ks []string) string {
for _, k := range ks {
if v := os.Getenv(k); v != "" {
return v
}
}
return ""
}

View File

@ -10,12 +10,16 @@ description: |-
Stores the state as an artifact in [Swift](http://docs.openstack.org/developer/swift/). Stores the state as an artifact in [Swift](http://docs.openstack.org/developer/swift/).
-> **Note:** Passing credentials directly via configuration options will
make them included in cleartext inside the persisted state. Use of
environment variables is recommended.
## Example Usage ## Example Usage
``` ```
terraform remote config \ terraform remote config \
-backend=swift \ -backend=swift \
-backend-config="path=random/path" -backend-config="path=terraform_state"
``` ```
## Example Referencing ## Example Referencing
@ -24,24 +28,56 @@ terraform remote config \
data "terraform_remote_state" "foo" { data "terraform_remote_state" "foo" {
backend = "swift" backend = "swift"
config { config {
path = "random/path" path = "terraform_state"
} }
} }
``` ```
## Configuration variables ## Configuration variables
The following configuration option is supported: The following configuration options are supported:
* `path` - (Required) The path where to store `terraform.tfstate` * `auth_url` - (Required) The Identity authentication URL. If omitted, the
* `insecure` - (Optional) Allow "insecure" SSL requests. Defaults to `false`. `OS_AUTH_URL` environment variable is used.
The following environment variables are supported: * `path` - (Required) The path where to store `terraform.tfstate`.
* `user_name` - (Optional) The Username to login with. If omitted, the
`OS_USERNAME` environment variable is used.
* `OS_AUTH_URL` - (Required) The identity endpoint * `user_id` - (Optional) The User ID to login with. If omitted, the
* `OS_USERNAME` - (Required) The username `OS_USER_ID` environment variable is used.
* `OS_PASSWORD` - (Required) The password
* `OS_REGION_NAME` - (Required) The region * `password` - (Optional) The Password to login with. If omitted, the
* `OS_TENANT_NAME` - (Required) The name of the tenant `OS_PASSWORD` environment variable is used.
* `OS_DOMAIN_ID` - (Optional) The ID of the domain
* `OS_DOMAIN_NAME` - (Optional) The name of the domain * `region_name` (Required) - The region in which to store `terraform.tfstate`. If
omitted, the `OS_REGION_NAME` environment variable is used.
* `tenant_id` (Optional) The ID of the Tenant (Identity v2) or Project
(Identity v3) to login with. If omitted, the `OS_TENANT_ID` or
`OS_PROJECT_ID` environment variables are used.
* `tenant_name` - (Optional) The Name of the Tenant (Identity v2) or Project
(Identity v3) to login with. If omitted, the `OS_TENANT_NAME` or
`OS_PROJECT_NAME` environment variable are used.
* `domain_id` - (Optional) The ID of the Domain to scope to (Identity v3). If
If omitted, the following environment variables are checked (in this order):
`OS_USER_DOMAIN_ID`, `OS_PROJECT_DOMAIN_ID`, `OS_DOMAIN_ID`.
* `domain_name` - (Optional) The Name of the Domain to scope to (Identity v3).
If omitted, the following environment variables are checked (in this order):
`OS_USER_DOMAIN_NAME`, `OS_PROJECT_DOMAIN_NAME`, `OS_DOMAIN_NAME`,
`DEFAULT_DOMAIN`.
* `insecure` - (Optional) Trust self-signed SSL certificates. If omitted, the
`OS_INSECURE` environment variable is used.
* `cacert_file` - (Optional) Specify a custom CA certificate when communicating
over SSL. If omitted, the `OS_CACERT` environment variable is used.
* `cert` - (Optional) Specify client certificate file for SSL client
authentication. If omitted the `OS_CERT` environment variable is used.
* `key` - (Optional) Specify client private key file for SSL client
authentication. If omitted the `OS_KEY` environment variable is used.