github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 }