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