github.com/ffrizzo/terraform@v0.8.2-0.20161219200057-992e12335f3d/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  	"strings"
    15  	"time"
    16  
    17  	"github.com/gophercloud/gophercloud"
    18  	"github.com/gophercloud/gophercloud/openstack"
    19  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
    20  	"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
    21  )
    22  
    23  const TFSTATE_NAME = "tfstate.tf"
    24  
    25  // SwiftClient implements the Client interface for an Openstack Swift server.
    26  type SwiftClient struct {
    27  	client      *gophercloud.ServiceClient
    28  	authurl     string
    29  	cacert      string
    30  	cert        string
    31  	domainid    string
    32  	domainname  string
    33  	insecure    bool
    34  	key         string
    35  	password    string
    36  	path        string
    37  	region      string
    38  	tenantid    string
    39  	tenantname  string
    40  	userid      string
    41  	username    string
    42  	archive     bool
    43  	archivepath string
    44  	expireSecs  int
    45  }
    46  
    47  func swiftFactory(conf map[string]string) (Client, error) {
    48  	client := &SwiftClient{}
    49  
    50  	if err := client.validateConfig(conf); err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	return client, nil
    55  }
    56  
    57  func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
    58  	authUrl, ok := conf["auth_url"]
    59  	if !ok {
    60  		authUrl = os.Getenv("OS_AUTH_URL")
    61  		if authUrl == "" {
    62  			return fmt.Errorf("missing 'auth_url' configuration or OS_AUTH_URL environment variable")
    63  		}
    64  	}
    65  	c.authurl = authUrl
    66  
    67  	username, ok := conf["user_name"]
    68  	if !ok {
    69  		username = os.Getenv("OS_USERNAME")
    70  	}
    71  	c.username = username
    72  
    73  	userID, ok := conf["user_id"]
    74  	if !ok {
    75  		userID = os.Getenv("OS_USER_ID")
    76  	}
    77  	c.userid = userID
    78  
    79  	password, ok := conf["password"]
    80  	if !ok {
    81  		password = os.Getenv("OS_PASSWORD")
    82  		if password == "" {
    83  			return fmt.Errorf("missing 'password' configuration or OS_PASSWORD environment variable")
    84  		}
    85  	}
    86  	c.password = password
    87  
    88  	region, ok := conf["region_name"]
    89  	if !ok {
    90  		region = os.Getenv("OS_REGION_NAME")
    91  	}
    92  	c.region = region
    93  
    94  	tenantID, ok := conf["tenant_id"]
    95  	if !ok {
    96  		tenantID = multiEnv([]string{
    97  			"OS_TENANT_ID",
    98  			"OS_PROJECT_ID",
    99  		})
   100  	}
   101  	c.tenantid = tenantID
   102  
   103  	tenantName, ok := conf["tenant_name"]
   104  	if !ok {
   105  		tenantName = multiEnv([]string{
   106  			"OS_TENANT_NAME",
   107  			"OS_PROJECT_NAME",
   108  		})
   109  	}
   110  	c.tenantname = tenantName
   111  
   112  	domainID, ok := conf["domain_id"]
   113  	if !ok {
   114  		domainID = multiEnv([]string{
   115  			"OS_USER_DOMAIN_ID",
   116  			"OS_PROJECT_DOMAIN_ID",
   117  			"OS_DOMAIN_ID",
   118  		})
   119  	}
   120  	c.domainid = domainID
   121  
   122  	domainName, ok := conf["domain_name"]
   123  	if !ok {
   124  		domainName = multiEnv([]string{
   125  			"OS_USER_DOMAIN_NAME",
   126  			"OS_PROJECT_DOMAIN_NAME",
   127  			"OS_DOMAIN_NAME",
   128  			"DEFAULT_DOMAIN",
   129  		})
   130  	}
   131  	c.domainname = domainName
   132  
   133  	path, ok := conf["path"]
   134  	if !ok || path == "" {
   135  		return fmt.Errorf("missing 'path' configuration")
   136  	}
   137  	c.path = path
   138  
   139  	if archivepath, ok := conf["archive_path"]; ok {
   140  		log.Printf("[DEBUG] Archivepath set, enabling object versioning")
   141  		c.archive = true
   142  		c.archivepath = archivepath
   143  	}
   144  
   145  	if expire, ok := conf["expire_after"]; ok {
   146  		log.Printf("[DEBUG] Requested that remote state expires after %s", expire)
   147  
   148  		if strings.HasSuffix(expire, "d") {
   149  			log.Printf("[DEBUG] Got a days expire after duration. Converting to hours")
   150  			days, err := strconv.Atoi(expire[:len(expire)-1])
   151  			if err != nil {
   152  				return fmt.Errorf("Error converting expire_after value %s to int: %s", expire, err)
   153  			}
   154  
   155  			expire = fmt.Sprintf("%dh", days*24)
   156  			log.Printf("[DEBUG] Expire after %s hours", expire)
   157  		}
   158  
   159  		expireDur, err := time.ParseDuration(expire)
   160  		if err != nil {
   161  			log.Printf("[DEBUG] Error parsing duration %s: %s", expire, err)
   162  			return fmt.Errorf("Error parsing expire_after duration '%s': %s", expire, err)
   163  		}
   164  		log.Printf("[DEBUG] Seconds duration = %d", int(expireDur.Seconds()))
   165  		c.expireSecs = int(expireDur.Seconds())
   166  	}
   167  
   168  	c.insecure = false
   169  	raw, ok := conf["insecure"]
   170  	if !ok {
   171  		raw = os.Getenv("OS_INSECURE")
   172  	}
   173  	if raw != "" {
   174  		v, err := strconv.ParseBool(raw)
   175  		if err != nil {
   176  			return fmt.Errorf("'insecure' and 'OS_INSECURE' could not be parsed as bool: %s", err)
   177  		}
   178  		c.insecure = v
   179  	}
   180  
   181  	cacertFile, ok := conf["cacert_file"]
   182  	if !ok {
   183  		cacertFile = os.Getenv("OS_CACERT")
   184  	}
   185  	c.cacert = cacertFile
   186  
   187  	cert, ok := conf["cert"]
   188  	if !ok {
   189  		cert = os.Getenv("OS_CERT")
   190  	}
   191  	c.cert = cert
   192  
   193  	key, ok := conf["key"]
   194  	if !ok {
   195  		key = os.Getenv("OS_KEY")
   196  	}
   197  	c.key = key
   198  
   199  	ao := gophercloud.AuthOptions{
   200  		IdentityEndpoint: c.authurl,
   201  		UserID:           c.userid,
   202  		Username:         c.username,
   203  		TenantID:         c.tenantid,
   204  		TenantName:       c.tenantname,
   205  		Password:         c.password,
   206  		DomainID:         c.domainid,
   207  		DomainName:       c.domainname,
   208  	}
   209  
   210  	provider, err := openstack.NewClient(ao.IdentityEndpoint)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	config := &tls.Config{}
   216  
   217  	if c.cacert != "" {
   218  		caCert, err := ioutil.ReadFile(c.cacert)
   219  		if err != nil {
   220  			return err
   221  		}
   222  
   223  		caCertPool := x509.NewCertPool()
   224  		caCertPool.AppendCertsFromPEM(caCert)
   225  		config.RootCAs = caCertPool
   226  	}
   227  
   228  	if c.insecure {
   229  		log.Printf("[DEBUG] Insecure mode set")
   230  		config.InsecureSkipVerify = true
   231  	}
   232  
   233  	if c.cert != "" && c.key != "" {
   234  		cert, err := tls.LoadX509KeyPair(c.cert, c.key)
   235  		if err != nil {
   236  			return err
   237  		}
   238  
   239  		config.Certificates = []tls.Certificate{cert}
   240  		config.BuildNameToCertificate()
   241  	}
   242  
   243  	transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
   244  	provider.HTTPClient.Transport = transport
   245  
   246  	err = openstack.Authenticate(provider, ao)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{
   252  		Region: c.region,
   253  	})
   254  
   255  	return err
   256  }
   257  
   258  func (c *SwiftClient) Get() (*Payload, error) {
   259  	result := objects.Download(c.client, c.path, TFSTATE_NAME, nil)
   260  
   261  	// Extract any errors from result
   262  	_, err := result.Extract()
   263  
   264  	// 404 response is to be expected if the object doesn't already exist!
   265  	if _, ok := err.(gophercloud.ErrDefault404); ok {
   266  		log.Printf("[DEBUG] Container doesn't exist to download.")
   267  		return nil, nil
   268  	}
   269  
   270  	bytes, err := result.ExtractContent()
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	hash := md5.Sum(bytes)
   276  	payload := &Payload{
   277  		Data: bytes,
   278  		MD5:  hash[:md5.Size],
   279  	}
   280  
   281  	return payload, nil
   282  }
   283  
   284  func (c *SwiftClient) Put(data []byte) error {
   285  	if err := c.ensureContainerExists(); err != nil {
   286  		return err
   287  	}
   288  
   289  	log.Printf("[DEBUG] Creating object %s at path %s", TFSTATE_NAME, c.path)
   290  	reader := bytes.NewReader(data)
   291  	createOpts := objects.CreateOpts{
   292  		Content: reader,
   293  	}
   294  
   295  	if c.expireSecs != 0 {
   296  		log.Printf("[DEBUG] ExpireSecs = %d", c.expireSecs)
   297  		createOpts.DeleteAfter = c.expireSecs
   298  	}
   299  
   300  	result := objects.Create(c.client, c.path, TFSTATE_NAME, createOpts)
   301  
   302  	return result.Err
   303  }
   304  
   305  func (c *SwiftClient) Delete() error {
   306  	result := objects.Delete(c.client, c.path, TFSTATE_NAME, nil)
   307  	return result.Err
   308  }
   309  
   310  func (c *SwiftClient) ensureContainerExists() error {
   311  	containerOpts := &containers.CreateOpts{}
   312  
   313  	if c.archive {
   314  		log.Printf("[DEBUG] Creating container %s", c.archivepath)
   315  		result := containers.Create(c.client, c.archivepath, nil)
   316  		if result.Err != nil {
   317  			log.Printf("[DEBUG] Error creating container %s: %s", c.archivepath, result.Err)
   318  			return result.Err
   319  		}
   320  
   321  		log.Printf("[DEBUG] Enabling Versioning on container %s", c.path)
   322  		containerOpts.VersionsLocation = c.archivepath
   323  	}
   324  
   325  	log.Printf("[DEBUG] Creating container %s", c.path)
   326  	result := containers.Create(c.client, c.path, containerOpts)
   327  	if result.Err != nil {
   328  		return result.Err
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  func multiEnv(ks []string) string {
   335  	for _, k := range ks {
   336  		if v := os.Getenv(k); v != "" {
   337  			return v
   338  		}
   339  	}
   340  	return ""
   341  }