From 9cfa1d5e5f0e4f32d194383b7d9cc1fd951b59c2 Mon Sep 17 00:00:00 2001 From: George Hartzell Date: Fri, 5 Jun 2015 12:05:21 -0700 Subject: [PATCH 1/3] Implement OpenStack/Swift remote Rework devcamcar's OpenStack Swift remote [pull request](https://github.com/hashicorp/terraform/pull/942) to work with Terraform's new `state/remote` and Gophercloud's current implementation. `Get()` changed up a bit from devcamcar's version (using different Gopercloud functionality resulted in less fussing around to figure out the error case). Otherwise this is a transliteration/remix of his ideas. --- state/remote/remote.go | 1 + state/remote/swift.go | 114 +++++++++++++++++++++++++++++++++++++ state/remote/swift_test.go | 25 ++++++++ 3 files changed, 140 insertions(+) create mode 100644 state/remote/swift.go create mode 100644 state/remote/swift_test.go diff --git a/state/remote/remote.go b/state/remote/remote.go index cb525e6a3..7ebea3222 100644 --- a/state/remote/remote.go +++ b/state/remote/remote.go @@ -40,6 +40,7 @@ var BuiltinClients = map[string]Factory{ "consul": consulFactory, "http": httpFactory, "s3": s3Factory, + "swift": swiftFactory, // This is used for development purposes only. "_local": fileFactory, diff --git a/state/remote/swift.go b/state/remote/swift.go new file mode 100644 index 000000000..a4293f737 --- /dev/null +++ b/state/remote/swift.go @@ -0,0 +1,114 @@ +package remote + +import ( + "bytes" + "crypto/md5" + "fmt" + "os" + "strings" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects" +) + +const TFSTATE_NAME = "tfstate.tf" + +// SwiftClient implements the Client interface for an Openstack Swift server. +type SwiftClient struct { + client *gophercloud.ServiceClient + path string +} + +func swiftFactory(conf map[string]string) (Client, error) { + client := &SwiftClient{} + + if err := client.validateConfig(conf); err != nil { + return nil, err + } + + return client, nil +} + +func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { + if val := os.Getenv("OS_AUTH_URL"); val == "" { + return fmt.Errorf("missing OS_AUTH_URL environment variable") + } + if val := os.Getenv("OS_USERNAME"); val == "" { + return fmt.Errorf("missing OS_USERNAME environment variable") + } + if val := os.Getenv("OS_TENANT_NAME"); val == "" { + return fmt.Errorf("missing OS_TENANT_NAME environment variable") + } + if val := os.Getenv("OS_PASSWORD"); val == "" { + return fmt.Errorf("missing OS_PASSWORD environment variable") + } + path, ok := conf["path"] + if !ok || path == "" { + return fmt.Errorf("missing 'path' configuration") + } + + provider, err := openstack.AuthenticatedClient(gophercloud.AuthOptions{ + IdentityEndpoint: os.Getenv("OS_AUTH_URL"), + Username: os.Getenv("OS_USERNAME"), + TenantName: os.Getenv("OS_TENANT_NAME"), + Password: os.Getenv("OS_PASSWORD"), + }) + + if err != nil { + return err + } + + c.path = path + c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + + return err +} + +func (c *SwiftClient) Get() (*Payload, error) { + result := objects.Download(c.client, c.path, TFSTATE_NAME, nil) + bytes, err := result.ExtractContent() + + if err != nil { + if strings.Contains(err.Error(), "but got 404 instead") { + return nil, nil + } + return nil, err + } + + hash := md5.Sum(bytes) + payload := &Payload{ + Data: bytes, + MD5: hash[:md5.Size], + } + + return payload, nil +} + +func (c *SwiftClient) Put(data []byte) error { + if err := c.ensureContainerExists(); err != nil { + return err + } + + reader := bytes.NewReader(data) + result := objects.Create(c.client, c.path, TFSTATE_NAME, reader, nil) + + return result.Err +} + +func (c *SwiftClient) Delete() error { + result := objects.Delete(c.client, c.path, TFSTATE_NAME, nil) + return result.Err +} + +func (c *SwiftClient) ensureContainerExists() error { + result := containers.Create(c.client, c.path, nil) + if result.Err != nil { + return result.Err + } + + return nil +} diff --git a/state/remote/swift_test.go b/state/remote/swift_test.go new file mode 100644 index 000000000..54666ca29 --- /dev/null +++ b/state/remote/swift_test.go @@ -0,0 +1,25 @@ +package remote + +import ( + "net/http" + "testing" +) + +func TestSwiftClient_impl(t *testing.T) { + var _ Client = new(SwiftClient) +} + +func TestSwiftClient(t *testing.T) { + if _, err := http.Get("http://google.com"); err != nil { + t.Skipf("skipping, internet seems to not be available: %s", err) + } + + client, err := swiftFactory(map[string]string{ + "path": "swift_test", + }) + if err != nil { + t.Fatalf("bad: %s", err) + } + + testClient(t, client) +} From 258b1a426321dbfe31089579a8943b217e4e966e Mon Sep 17 00:00:00 2001 From: George Hartzell Date: Fri, 5 Jun 2015 12:05:21 -0700 Subject: [PATCH 2/3] Implement OpenStack/Swift remote Rework devcamcar's OpenStack Swift remote [pull request](https://github.com/hashicorp/terraform/pull/942) to work with Terraform's new `state/remote` and Gophercloud's current implementation. `Get()` changed up a bit from devcamcar's version (using different Gopercloud functionality resulted in less fussing around to figure out the error case). Otherwise this is a transliteration/remix of his ideas. --- state/remote/remote.go | 1 + state/remote/swift.go | 114 +++++++++++++++++++++++++++++++++++++ state/remote/swift_test.go | 25 ++++++++ 3 files changed, 140 insertions(+) create mode 100644 state/remote/swift.go create mode 100644 state/remote/swift_test.go diff --git a/state/remote/remote.go b/state/remote/remote.go index cb525e6a3..7ebea3222 100644 --- a/state/remote/remote.go +++ b/state/remote/remote.go @@ -40,6 +40,7 @@ var BuiltinClients = map[string]Factory{ "consul": consulFactory, "http": httpFactory, "s3": s3Factory, + "swift": swiftFactory, // This is used for development purposes only. "_local": fileFactory, diff --git a/state/remote/swift.go b/state/remote/swift.go new file mode 100644 index 000000000..a4293f737 --- /dev/null +++ b/state/remote/swift.go @@ -0,0 +1,114 @@ +package remote + +import ( + "bytes" + "crypto/md5" + "fmt" + "os" + "strings" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects" +) + +const TFSTATE_NAME = "tfstate.tf" + +// SwiftClient implements the Client interface for an Openstack Swift server. +type SwiftClient struct { + client *gophercloud.ServiceClient + path string +} + +func swiftFactory(conf map[string]string) (Client, error) { + client := &SwiftClient{} + + if err := client.validateConfig(conf); err != nil { + return nil, err + } + + return client, nil +} + +func (c *SwiftClient) validateConfig(conf map[string]string) (err error) { + if val := os.Getenv("OS_AUTH_URL"); val == "" { + return fmt.Errorf("missing OS_AUTH_URL environment variable") + } + if val := os.Getenv("OS_USERNAME"); val == "" { + return fmt.Errorf("missing OS_USERNAME environment variable") + } + if val := os.Getenv("OS_TENANT_NAME"); val == "" { + return fmt.Errorf("missing OS_TENANT_NAME environment variable") + } + if val := os.Getenv("OS_PASSWORD"); val == "" { + return fmt.Errorf("missing OS_PASSWORD environment variable") + } + path, ok := conf["path"] + if !ok || path == "" { + return fmt.Errorf("missing 'path' configuration") + } + + provider, err := openstack.AuthenticatedClient(gophercloud.AuthOptions{ + IdentityEndpoint: os.Getenv("OS_AUTH_URL"), + Username: os.Getenv("OS_USERNAME"), + TenantName: os.Getenv("OS_TENANT_NAME"), + Password: os.Getenv("OS_PASSWORD"), + }) + + if err != nil { + return err + } + + c.path = path + c.client, err = openstack.NewObjectStorageV1(provider, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) + + return err +} + +func (c *SwiftClient) Get() (*Payload, error) { + result := objects.Download(c.client, c.path, TFSTATE_NAME, nil) + bytes, err := result.ExtractContent() + + if err != nil { + if strings.Contains(err.Error(), "but got 404 instead") { + return nil, nil + } + return nil, err + } + + hash := md5.Sum(bytes) + payload := &Payload{ + Data: bytes, + MD5: hash[:md5.Size], + } + + return payload, nil +} + +func (c *SwiftClient) Put(data []byte) error { + if err := c.ensureContainerExists(); err != nil { + return err + } + + reader := bytes.NewReader(data) + result := objects.Create(c.client, c.path, TFSTATE_NAME, reader, nil) + + return result.Err +} + +func (c *SwiftClient) Delete() error { + result := objects.Delete(c.client, c.path, TFSTATE_NAME, nil) + return result.Err +} + +func (c *SwiftClient) ensureContainerExists() error { + result := containers.Create(c.client, c.path, nil) + if result.Err != nil { + return result.Err + } + + return nil +} diff --git a/state/remote/swift_test.go b/state/remote/swift_test.go new file mode 100644 index 000000000..54666ca29 --- /dev/null +++ b/state/remote/swift_test.go @@ -0,0 +1,25 @@ +package remote + +import ( + "net/http" + "testing" +) + +func TestSwiftClient_impl(t *testing.T) { + var _ Client = new(SwiftClient) +} + +func TestSwiftClient(t *testing.T) { + if _, err := http.Get("http://google.com"); err != nil { + t.Skipf("skipping, internet seems to not be available: %s", err) + } + + client, err := swiftFactory(map[string]string{ + "path": "swift_test", + }) + if err != nil { + t.Fatalf("bad: %s", err) + } + + testClient(t, client) +} From 4fc6dd0141f6d482866b66c9a057c894477f78a0 Mon Sep 17 00:00:00 2001 From: George Hartzell Date: Sat, 6 Jun 2015 10:19:25 -0700 Subject: [PATCH 3/3] Only run Swift tests when Swift is available Only run the Swift remote tests when OpenStack seems to have been set up and when the autho host is reachable. --- state/remote/swift_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/state/remote/swift_test.go b/state/remote/swift_test.go index 54666ca29..6ed4fd596 100644 --- a/state/remote/swift_test.go +++ b/state/remote/swift_test.go @@ -3,6 +3,7 @@ package remote import ( "net/http" "testing" + "os" ) func TestSwiftClient_impl(t *testing.T) { @@ -10,8 +11,13 @@ func TestSwiftClient_impl(t *testing.T) { } func TestSwiftClient(t *testing.T) { - if _, err := http.Get("http://google.com"); err != nil { - t.Skipf("skipping, internet seems to not be available: %s", err) + os_auth_url := os.Getenv("OS_AUTH_URL") + if os_auth_url == "" { + t.Skipf("skipping, OS_AUTH_URL and friends must be set") + } + + if _, err := http.Get(os_auth_url); err != nil { + t.Skipf("skipping, unable to reach %s: %s", os_auth_url, err) } client, err := swiftFactory(map[string]string{