state/remote/swift: Enhancements to support full set of Openstack configuration options, plus SSL certs. Documentation updated to support
This commit is contained in:
parent
6c801d0386
commit
bf8612b9b7
|
@ -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 ""
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue