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