diff --git a/state/remote/swift.go b/state/remote/swift.go index 92d21948d..a73861147 100644 --- a/state/remote/swift.go +++ b/state/remote/swift.go @@ -11,6 +11,8 @@ import ( "net/http" "os" "strconv" + "strings" + "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" @@ -22,21 +24,24 @@ const TFSTATE_NAME = "tfstate.tf" // SwiftClient implements the Client interface for an Openstack Swift server. type SwiftClient struct { - client *gophercloud.ServiceClient - authurl string - cacert string - cert string - domainid string - domainname string - insecure bool - key string - password string - path string - region string - tenantid string - tenantname string - userid string - username string + client *gophercloud.ServiceClient + authurl string + cacert string + cert string + domainid string + domainname string + insecure bool + key string + password string + path string + region string + tenantid string + tenantname string + userid string + username string + archive bool + archivepath string + expireSecs int } func swiftFactory(conf map[string]string) (Client, error) { @@ -131,6 +136,35 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { } c.path = path + if archivepath, ok := conf["archive_path"]; ok { + log.Printf("[DEBUG] Archivepath set, enabling object versioning") + c.archive = true + c.archivepath = archivepath + } + + if expire, ok := conf["expire_after"]; ok { + log.Printf("[DEBUG] Requested that remote state expires after %s", expire) + + if strings.HasSuffix(expire, "d") { + log.Printf("[DEBUG] Got a days expire after duration. Converting to hours") + days, err := strconv.Atoi(expire[:len(expire)-1]) + if err != nil { + return fmt.Errorf("Error converting expire_after value %s to int: %s", expire, err) + } + + expire = fmt.Sprintf("%dh", days*24) + log.Printf("[DEBUG] Expire after %s hours", expire) + } + + expireDur, err := time.ParseDuration(expire) + if err != nil { + log.Printf("[DEBUG] Error parsing duration %s: %s", expire, err) + return fmt.Errorf("Error parsing expire_after duration '%s': %s", expire, err) + } + log.Printf("[DEBUG] Seconds duration = %d", int(expireDur.Seconds())) + c.expireSecs = int(expireDur.Seconds()) + } + c.insecure = false raw, ok := conf["insecure"] if !ok { @@ -252,10 +286,17 @@ func (c *SwiftClient) Put(data []byte) error { return err } + log.Printf("[DEBUG] Creating object %s at path %s", TFSTATE_NAME, c.path) reader := bytes.NewReader(data) createOpts := objects.CreateOpts{ Content: reader, } + + if c.expireSecs != 0 { + log.Printf("[DEBUG] ExpireSecs = %d", c.expireSecs) + createOpts.DeleteAfter = c.expireSecs + } + result := objects.Create(c.client, c.path, TFSTATE_NAME, createOpts) return result.Err @@ -267,7 +308,22 @@ func (c *SwiftClient) Delete() error { } func (c *SwiftClient) ensureContainerExists() error { - result := containers.Create(c.client, c.path, nil) + containerOpts := &containers.CreateOpts{} + + if c.archive { + log.Printf("[DEBUG] Creating container %s", c.archivepath) + result := containers.Create(c.client, c.archivepath, nil) + if result.Err != nil { + log.Printf("[DEBUG] Error creating container %s: %s", c.archivepath, result.Err) + return result.Err + } + + log.Printf("[DEBUG] Enabling Versioning on container %s", c.path) + containerOpts.VersionsLocation = c.archivepath + } + + log.Printf("[DEBUG] Creating container %s", c.path) + result := containers.Create(c.client, c.path, containerOpts) if result.Err != nil { return result.Err } diff --git a/website/source/docs/state/remote/swift.html.md b/website/source/docs/state/remote/swift.html.md index 8cbbe672b..e9fe7753f 100644 --- a/website/source/docs/state/remote/swift.html.md +++ b/website/source/docs/state/remote/swift.html.md @@ -80,4 +80,10 @@ The following configuration options are supported: 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. \ No newline at end of file + authentication. If omitted the `OS_KEY` environment variable is used. + + * `archive_path` - (Optional) The path to store archived copied of `terraform.tfstate`. + If specified, Swift object versioning is enabled on the container created at `path`. + + * `expire_after` - (Optional) How long should the `terraform.tfstate` created at `path` + be retained for? Supported durations: `m` - Minutes, `h` - Hours, `d` - Days.