From 7bf54c546dd62115ad81f35135f0a1e8f13e49e4 Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Thu, 9 Apr 2015 11:27:29 -0700 Subject: [PATCH 1/6] updates personality to []map[string]string where map has path and contents --- openstack/compute/v2/servers/requests.go | 20 ++++++++++---------- rackspace/compute/v2/servers/requests.go | 14 ++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index b7c1611d..c740dfbb 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -125,9 +125,10 @@ type CreateOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes the path and contents of a file to inject into the server at launch. - // The maximum size of the file is 255 bytes (decoded). - Personality []byte + // Personality [optional] includes a list of maps with the path and contents + // of a file to inject into the server at launch. Contents should be + // base64 encoded. The maximum size of the file is 255 bytes (decoded). + Personality []map[string]string // ConfigDrive [optional] enables metadata injection through a configuration drive. ConfigDrive bool @@ -156,8 +157,7 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { server["user_data"] = &encoded } if opts.Personality != nil { - encoded := base64.StdEncoding.EncodeToString(opts.Personality) - server["personality"] = &encoded + server["personality"] = opts.Personality } if opts.ConfigDrive { server["config_drive"] = "true" @@ -406,9 +406,10 @@ type RebuildOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes the path and contents of a file to inject into the server at launch. - // The maximum size of the file is 255 bytes (decoded). - Personality []byte + // Personality [optional] includes a list of maps with the path and contents + // of a file to inject into the server at launch. Contents should be + // base64 encoded. The maximum size of the file is 255 bytes (decoded). + Personality []map[string]string } // ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON @@ -445,8 +446,7 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { } if opts.Personality != nil { - encoded := base64.StdEncoding.EncodeToString(opts.Personality) - server["personality"] = &encoded + server["personality"] = opts.Personality } return map[string]interface{}{"rebuild": server}, nil diff --git a/rackspace/compute/v2/servers/requests.go b/rackspace/compute/v2/servers/requests.go index 809183ec..96dc931d 100644 --- a/rackspace/compute/v2/servers/requests.go +++ b/rackspace/compute/v2/servers/requests.go @@ -36,9 +36,10 @@ type CreateOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes the path and contents of a file to inject into the server at launch. - // The maximum size of the file is 255 bytes (decoded). - Personality []byte + // Personality [optional] includes a list of maps with the path and contents + // of a file to inject into the server at launch. Contents should be + // base64 encoded. The maximum size of the file is 255 bytes (decoded). + Personality []map[string]string // ConfigDrive [optional] enables metadata injection through a configuration drive. ConfigDrive bool @@ -130,9 +131,10 @@ type RebuildOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes the path and contents of a file to inject into the server at launch. - // The maximum size of the file is 255 bytes (decoded). - Personality []byte + // Personality [optional] includes a list of maps with the path and contents + // of a file to inject into the server at launch. Contents should be + // base64 encoded. The maximum size of the file is 255 bytes (decoded). + Personality []map[string]string // Rackspace-specific stuff begins here. From 92e10b5121e95c71bd15fbd6e41588f89e9c1e90 Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Fri, 10 Apr 2015 15:16:57 -0700 Subject: [PATCH 2/6] Encapsulate Personality. Encode contents for user --- openstack/compute/v2/servers/requests.go | 54 ++++++++++++++----- openstack/compute/v2/servers/requests_test.go | 23 ++++++++ rackspace/compute/v2/servers/requests.go | 14 +++-- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index d161a179..18a2237f 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -96,6 +96,35 @@ type Network struct { FixedIP string } +// Personality is an array of files that are injected into the server at launch. +type Personality []File + +// Marshal marshals the personality, marshalling each of the files. +func (p Personality) Marshal() []map[string]string { + personality := make([]map[string]string, len(p)) + for i, file := range p { + personality[i] = file.Marshal() + } + + return personality +} + +// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. +type File struct { + // Path of the file + Path string `json:"path"` + // Contents of the file. Maximum content size is 255 bytes. + Contents []byte `json:"contents"` +} + +// Marshal marshals the file, base64 encoding the contents. +func (f File) Marshal() map[string]string { + return map[string]string{ + "path": f.Path, + "contents": base64.StdEncoding.EncodeToString(f.Contents), + } +} + // CreateOpts specifies server creation parameters. type CreateOpts struct { // Name [required] is the name to assign to the newly launched server. @@ -125,10 +154,9 @@ type CreateOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes a list of maps with the path and contents - // of a file to inject into the server at launch. Contents should be - // base64 encoded. The maximum size of the file is 255 bytes (decoded). - Personality []map[string]string + // Personality [optional] includes files to inject into the server at launch. + // Create will base64-encode file contents for you. + Personality Personality // ConfigDrive [optional] enables metadata injection through a configuration drive. ConfigDrive bool @@ -156,9 +184,6 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { encoded := base64.StdEncoding.EncodeToString(opts.UserData) server["user_data"] = &encoded } - if opts.Personality != nil { - server["personality"] = opts.Personality - } if opts.ConfigDrive { server["config_drive"] = "true" } @@ -203,6 +228,10 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { server["networks"] = networks } + if len(opts.Personality) > 0 { + server["personality"] = opts.Personality.Marshal() + } + return map[string]interface{}{"server": server}, nil } @@ -392,10 +421,9 @@ type RebuildOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes a list of maps with the path and contents - // of a file to inject into the server at launch. Contents should be - // base64 encoded. The maximum size of the file is 255 bytes (decoded). - Personality []map[string]string + // Personality [optional] includes files to inject into the server at launch. + // Rebuild will base64-encode file contents for you. + Personality Personality } // ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON @@ -431,8 +459,8 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { server["metadata"] = opts.Metadata } - if opts.Personality != nil { - server["personality"] = opts.Personality + if len(opts.Personality) > 0 { + server["personality"] = opts.Personality.Marshal() } return map[string]interface{}{"rebuild": server}, nil diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go index 62b89e0c..83fcdb0e 100644 --- a/openstack/compute/v2/servers/requests_test.go +++ b/openstack/compute/v2/servers/requests_test.go @@ -1,6 +1,7 @@ package servers import ( + "encoding/base64" "net/http" "testing" @@ -325,3 +326,25 @@ func TestListAddressesByNetwork(t *testing.T) { th.AssertNoErr(t, err) th.CheckEquals(t, 1, pages) } + +func TestMarshalPersonality(t *testing.T) { + name := "test" + contents := []byte("asdfasdf") + + personality := Personality{ + File{ + Path: name, + Contents: contents, + }, + } + + actual := personality.Marshal() + + if actual[0]["path"] != name { + t.Fatal("file path incorrect") + } + + if actual[0]["contents"] != base64.StdEncoding.EncodeToString(contents) { + t.Fatal("file contents incorrect") + } +} diff --git a/rackspace/compute/v2/servers/requests.go b/rackspace/compute/v2/servers/requests.go index 96dc931d..1ebb8971 100644 --- a/rackspace/compute/v2/servers/requests.go +++ b/rackspace/compute/v2/servers/requests.go @@ -36,10 +36,9 @@ type CreateOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes a list of maps with the path and contents - // of a file to inject into the server at launch. Contents should be - // base64 encoded. The maximum size of the file is 255 bytes (decoded). - Personality []map[string]string + // Personality [optional] includes files to inject into the server at launch. + // Create will base64-encode file contents for you. + Personality os.Personality // ConfigDrive [optional] enables metadata injection through a configuration drive. ConfigDrive bool @@ -131,10 +130,9 @@ type RebuildOpts struct { // Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server. Metadata map[string]string - // Personality [optional] includes a list of maps with the path and contents - // of a file to inject into the server at launch. Contents should be - // base64 encoded. The maximum size of the file is 255 bytes (decoded). - Personality []map[string]string + // Personality [optional] includes files to inject into the server at launch. + // Rebuild will base64-encode file contents for you. + Personality os.Personality // Rackspace-specific stuff begins here. From a2bfaeafd64f3f6079970a8277a90917eaa84a6c Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Tue, 21 Apr 2015 11:45:59 -0700 Subject: [PATCH 3/6] use MarshalJSON --- openstack/compute/v2/servers/requests.go | 35 ++++++++----------- openstack/compute/v2/servers/requests_test.go | 20 +++++++++-- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index 18a2237f..da0c737b 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -2,6 +2,7 @@ package servers import ( "encoding/base64" + "encoding/json" "errors" "fmt" @@ -97,32 +98,26 @@ type Network struct { } // Personality is an array of files that are injected into the server at launch. -type Personality []File - -// Marshal marshals the personality, marshalling each of the files. -func (p Personality) Marshal() []map[string]string { - personality := make([]map[string]string, len(p)) - for i, file := range p { - personality[i] = file.Marshal() - } - - return personality -} +type Personality []*File // File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. type File struct { // Path of the file - Path string `json:"path"` + Path string // Contents of the file. Maximum content size is 255 bytes. - Contents []byte `json:"contents"` + Contents []byte } -// Marshal marshals the file, base64 encoding the contents. -func (f File) Marshal() map[string]string { - return map[string]string{ - "path": f.Path, - "contents": base64.StdEncoding.EncodeToString(f.Contents), +// MarshalJSON marshals the escaped file, base64 encoding the contents. +func (f *File) MarshalJSON() ([]byte, error) { + file := struct { + Path string `json:"path"` + Contents string `json:"contents"` + }{ + Path: f.Path, + Contents: base64.StdEncoding.EncodeToString(f.Contents), } + return json.Marshal(file) } // CreateOpts specifies server creation parameters. @@ -229,7 +224,7 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { } if len(opts.Personality) > 0 { - server["personality"] = opts.Personality.Marshal() + server["personality"] = opts.Personality } return map[string]interface{}{"server": server}, nil @@ -460,7 +455,7 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { } if len(opts.Personality) > 0 { - server["personality"] = opts.Personality.Marshal() + server["personality"] = opts.Personality } return map[string]interface{}{"rebuild": server}, nil diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go index 83fcdb0e..d878bd04 100644 --- a/openstack/compute/v2/servers/requests_test.go +++ b/openstack/compute/v2/servers/requests_test.go @@ -2,6 +2,7 @@ package servers import ( "encoding/base64" + "encoding/json" "net/http" "testing" @@ -328,17 +329,30 @@ func TestListAddressesByNetwork(t *testing.T) { } func TestMarshalPersonality(t *testing.T) { - name := "test" + name := "/etc/test" contents := []byte("asdfasdf") personality := Personality{ - File{ + &File{ Path: name, Contents: contents, }, } - actual := personality.Marshal() + data, err := json.Marshal(personality) + if err != nil { + t.Fatal(err) + } + + var actual []map[string]string + err = json.Unmarshal(data, &actual) + if err != nil { + t.Fatal(err) + } + + if len(actual) != 1 { + t.Fatal("expected personality length 1") + } if actual[0]["path"] != name { t.Fatal("file path incorrect") From 9748b7b31b1ffaf151585efddc604b4c2e568d55 Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Tue, 5 May 2015 07:34:07 -0700 Subject: [PATCH 4/6] improves File documentation --- openstack/compute/v2/servers/requests.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go index 93a340ab..af77546e 100644 --- a/openstack/compute/v2/servers/requests.go +++ b/openstack/compute/v2/servers/requests.go @@ -15,6 +15,7 @@ import ( type ListOptsBuilder interface { ToServerListQuery() (string, error) } + // ListOpts allows the filtering and sorting of paginated collections through // the API. Filtering is achieved by passing in struct field values that map to // the server attributes you want to see returned. Marker and Limit are used @@ -100,6 +101,8 @@ type Network struct { type Personality []*File // File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. +// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested, +// json.Marshal will call File's MarshalJSON method. type File struct { // Path of the file Path string @@ -764,5 +767,5 @@ func CreateImage(client *gophercloud.ServiceClient, serverId string, opts Create }) res.Err = err res.Header = response.Header - return res + return res } From 60c1e89989c02563a8ec2d154897c9f2888e6893 Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Tue, 5 May 2015 07:35:02 -0700 Subject: [PATCH 5/6] adds personality to server created in acceptance tests --- acceptance/openstack/compute/v2/servers_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go index 7b928e9e..f6c7c052 100644 --- a/acceptance/openstack/compute/v2/servers_test.go +++ b/acceptance/openstack/compute/v2/servers_test.go @@ -107,6 +107,12 @@ func createServer(t *testing.T, client *gophercloud.ServiceClient, choices *Comp servers.Network{UUID: network.ID}, }, AdminPass: pwd, + Personality: servers.Personality{ + &servers.File{ + Path: "/etc/test", + Contents: []byte("hello world"), + }, + }, }).Extract() if err != nil { t.Fatalf("Unable to create server: %v", err) From 4d6c6e7448c7ad1fcad4a47163d859effda8751c Mon Sep 17 00:00:00 2001 From: Kevin Pike Date: Tue, 5 May 2015 17:09:27 -0700 Subject: [PATCH 6/6] remove duplicate test from bad merge --- openstack/compute/v2/servers/requests_test.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openstack/compute/v2/servers/requests_test.go b/openstack/compute/v2/servers/requests_test.go index 15bf9d34..88cb54dd 100644 --- a/openstack/compute/v2/servers/requests_test.go +++ b/openstack/compute/v2/servers/requests_test.go @@ -371,12 +371,3 @@ func TestMarshalPersonality(t *testing.T) { t.Fatal("file contents incorrect") } } - -func TestCreateServerImage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleCreateServerImageSuccessfully(t) - - _, err := CreateImage(client.ServiceClient(), "serverimage", CreateImageOpts{Name: "test"}).ExtractImageID() - th.AssertNoErr(t, err) -}