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