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 }