github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/state/remote/swift.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"net/http"
    12  	"os"
    13  	"strconv"
    14  
    15  	"github.com/gophercloud/gophercloud"
    16  	"github.com/gophercloud/gophercloud/openstack"
    17  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
    18  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
    19  )
    20  
    21  const TFSTATE_NAME = "tfstate.tf"
    22  
    23  // SwiftClient implements the Client interface for an Openstack Swift server.
    24  type SwiftClient struct {
    25  	client     *gophercloud.ServiceClient
    26  	authurl    string
    27  	cacert     string
    28  	cert       string
    29  	domainid   string
    30  	domainname string
    31  	insecure   bool
    32  	key        string
    33  	password   string
    34  	path       string
    35  	region     string
    36  	tenantid   string
    37  	tenantname string
    38  	userid     string
    39  	username   string
    40  }
    41  
    42  func swiftFactory(conf map[string]string) (Client, error) {
    43  	client := &SwiftClient{}
    44  
    45  	if err := client.validateConfig(conf); err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	return client, nil
    50  }
    51  
    52  func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
    53  	authUrl, ok := conf["auth_url"]
    54  	if !ok {
    55  		authUrl = os.Getenv("OS_AUTH_URL")
    56  		if authUrl == "" {
    57  			return fmt.Errorf("missing 'auth_url' configuration or OS_AUTH_URL environment variable")
    58  		}
    59  	}
    60  	c.authurl = authUrl
    61  
    62  	username, ok := conf["user_name"]
    63  	if !ok {
    64  		username = os.Getenv("OS_USERNAME")
    65  	}
    66  	c.username = username
    67  
    68  	userID, ok := conf["user_id"]
    69  	if !ok {
    70  		userID = os.Getenv("OS_USER_ID")
    71  	}
    72  	c.userid = userID
    73  
    74  	password, ok := conf["password"]
    75  	if !ok {
    76  		password = os.Getenv("OS_PASSWORD")
    77  		if password == "" {
    78  			return fmt.Errorf("missing 'password' configuration or OS_PASSWORD environment variable")
    79  		}
    80  	}
    81  	c.password = password
    82  
    83  	region, ok := conf["region_name"]
    84  	if !ok {
    85  		region = os.Getenv("OS_REGION_NAME")
    86  	}
    87  	c.region = region
    88  
    89  	tenantID, ok := conf["tenant_id"]
    90  	if !ok {
    91  		tenantID = multiEnv([]string{
    92  			"OS_TENANT_ID",
    93  			"OS_PROJECT_ID",
    94  		})
    95  	}
    96  	c.tenantid = tenantID
    97  
    98  	tenantName, ok := conf["tenant_name"]
    99  	if !ok {
   100  		tenantName = multiEnv([]string{
   101  			"OS_TENANT_NAME",
   102  			"OS_PROJECT_NAME",
   103  		})
   104  	}
   105  	c.tenantname = tenantName
   106  
   107  	domainID, ok := conf["domain_id"]
   108  	if !ok {
   109  		domainID = multiEnv([]string{
   110  			"OS_USER_DOMAIN_ID",
   111  			"OS_PROJECT_DOMAIN_ID",
   112  			"OS_DOMAIN_ID",
   113  		})
   114  	}
   115  	c.domainid = domainID
   116  
   117  	domainName, ok := conf["domain_name"]
   118  	if !ok {
   119  		domainName = multiEnv([]string{
   120  			"OS_USER_DOMAIN_NAME",
   121  			"OS_PROJECT_DOMAIN_NAME",
   122  			"OS_DOMAIN_NAME",
   123  			"DEFAULT_DOMAIN",
   124  		})
   125  	}
   126  	c.domainname = domainName
   127  
   128  	path, ok := conf["path"]
   129  	if !ok || path == "" {
   130  		return fmt.Errorf("missing 'path' configuration")
   131  	}
   132  	c.path = path
   133  
   134  	c.insecure = false
   135  	raw, ok := conf["insecure"]
   136  	if !ok {
   137  		raw = os.Getenv("OS_INSECURE")
   138  	}
   139  	if raw != "" {
   140  		v, err := strconv.ParseBool(raw)
   141  		if err != nil {
   142  			return fmt.Errorf("'insecure' and 'OS_INSECURE' could not be parsed as bool: %s", err)
   143  		}
   144  		c.insecure = v
   145  	}
   146  
   147  	cacertFile, ok := conf["cacert_file"]
   148  	if !ok {
   149  		cacertFile = os.Getenv("OS_CACERT")
   150  	}
   151  	c.cacert = cacertFile
   152  
   153  	cert, ok := conf["cert"]
   154  	if !ok {
   155  		cert = os.Getenv("OS_CERT")
   156  	}
   157  	c.cert = cert
   158  
   159  	key, ok := conf["key"]
   160  	if !ok {
   161  		key = os.Getenv("OS_KEY")
   162  	}
   163  	c.key = key
   164  
   165  	ao := gophercloud.AuthOptions{
   166  		IdentityEndpoint: c.authurl,
   167  		UserID:           c.userid,
   168  		Username:         c.username,
   169  		TenantID:         c.tenantid,
   170  		TenantName:       c.tenantname,
   171  		Password:         c.password,
   172  		DomainID:         c.domainid,
   173  		DomainName:       c.domainname,
   174  	}
   175  
   176  	provider, err := openstack.NewClient(ao.IdentityEndpoint)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	config := &tls.Config{}
   182  
   183  	if c.cacert != "" {
   184  		caCert, err := ioutil.ReadFile(c.cacert)
   185  		if err != nil {
   186  			return err
   187  		}
   188  
   189  		caCertPool := x509.NewCertPool()
   190  		caCertPool.AppendCertsFromPEM(caCert)
   191  		config.RootCAs = caCertPool
   192  	}
   193  
   194  	if c.insecure {
   195  		log.Printf("[DEBUG] Insecure mode set")
   196  		config.InsecureSkipVerify = true
   197  	}
   198  
   199  	if c.cert != "" && c.key != "" {
   200  		cert, err := tls.LoadX509KeyPair(c.cert, c.key)
   201  		if err != nil {
   202  			return err
   203  		}
   204  
   205  		config.Certificates = []tls.Certificate{cert}
   206  		config.BuildNameToCertificate()
   207  	}
   208  
   209  	transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
   210  	provider.HTTPClient.Transport = transport
   211  
   212  	err = openstack.Authenticate(provider, ao)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{
   218  		Region: c.region,
   219  	})
   220  
   221  	return err
   222  }
   223  
   224  func (c *SwiftClient) Get() (*Payload, error) {
   225  	result := objects.Download(c.client, c.path, TFSTATE_NAME, nil)
   226  
   227  	// Extract any errors from result
   228  	_, err := result.Extract()
   229  
   230  	// 404 response is to be expected if the object doesn't already exist!
   231  	if _, ok := err.(gophercloud.ErrDefault404); ok {
   232  		log.Printf("[DEBUG] Container doesn't exist to download.")
   233  		return nil, nil
   234  	}
   235  
   236  	bytes, err := result.ExtractContent()
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	hash := md5.Sum(bytes)
   242  	payload := &Payload{
   243  		Data: bytes,
   244  		MD5:  hash[:md5.Size],
   245  	}
   246  
   247  	return payload, nil
   248  }
   249  
   250  func (c *SwiftClient) Put(data []byte) error {
   251  	if err := c.ensureContainerExists(); err != nil {
   252  		return err
   253  	}
   254  
   255  	reader := bytes.NewReader(data)
   256  	createOpts := objects.CreateOpts{
   257  		Content: reader,
   258  	}
   259  	result := objects.Create(c.client, c.path, TFSTATE_NAME, createOpts)
   260  
   261  	return result.Err
   262  }
   263  
   264  func (c *SwiftClient) Delete() error {
   265  	result := objects.Delete(c.client, c.path, TFSTATE_NAME, nil)
   266  	return result.Err
   267  }
   268  
   269  func (c *SwiftClient) ensureContainerExists() error {
   270  	result := containers.Create(c.client, c.path, nil)
   271  	if result.Err != nil {
   272  		return result.Err
   273  	}
   274  
   275  	return nil
   276  }
   277  
   278  func multiEnv(ks []string) string {
   279  	for _, k := range ks {
   280  		if v := os.Getenv(k); v != "" {
   281  			return v
   282  		}
   283  	}
   284  	return ""
   285  }