Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.
6 changes: 6 additions & 0 deletions acceptance/openstack/compute/v2/servers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
54 changes: 40 additions & 14 deletions openstack/compute/v2/servers/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package servers

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"

Expand All @@ -14,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
Expand Down Expand Up @@ -95,6 +97,31 @@ type Network struct {
FixedIP string
}

// Personality is an array of files that are injected into the server at launch.
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
// Contents of the file. Maximum content size is 255 bytes.
Contents []byte
}

// MarshalJSON marshals the escaped file, base64 encoding the contents.
func (f *File) MarshalJSON() ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intended use case of the method? It seems like the base64 encoding process should go in the ToServerCreateMap method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file base64 encode process is used by ToServerCreateMap and ToServerRebuildMap. Having File implement MarshalJSON seemed like a good way to reduce putting the encode logic in two places and have a File know how to marshal itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see; it's implementing the Marshaler interface. Can you add a comment above File mentioning that? Something like: "File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested, json.Marshal will call File's MarshalJSON method." The only reason for explicitness is because I don't think we have this anywhere else. Also, could you add a Personality field to the acceptance test that creates a server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! 👍

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.
type CreateOpts struct {
// Name [required] is the name to assign to the newly launched server.
Expand Down Expand Up @@ -124,9 +151,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 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 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
Expand Down Expand Up @@ -154,10 +181,6 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
encoded := base64.StdEncoding.EncodeToString(opts.UserData)
server["user_data"] = &encoded
}
if opts.Personality != nil {
encoded := base64.StdEncoding.EncodeToString(opts.Personality)
server["personality"] = &encoded
}
if opts.ConfigDrive {
server["config_drive"] = "true"
}
Expand Down Expand Up @@ -202,6 +225,10 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
server["networks"] = networks
}

if len(opts.Personality) > 0 {
server["personality"] = opts.Personality
}

return map[string]interface{}{"server": server}, nil
}

Expand Down Expand Up @@ -391,9 +418,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 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 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
Expand Down Expand Up @@ -429,9 +456,8 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
server["metadata"] = opts.Metadata
}

if opts.Personality != nil {
encoded := base64.StdEncoding.EncodeToString(opts.Personality)
server["personality"] = &encoded
if len(opts.Personality) > 0 {
server["personality"] = opts.Personality
}

return map[string]interface{}{"rebuild": server}, nil
Expand Down Expand Up @@ -741,5 +767,5 @@ func CreateImage(client *gophercloud.ServiceClient, serverId string, opts Create
})
res.Err = err
res.Header = response.Header
return res
return res
}
37 changes: 37 additions & 0 deletions openstack/compute/v2/servers/requests_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package servers

import (
"encoding/base64"
"encoding/json"
"net/http"
"testing"

Expand Down Expand Up @@ -334,3 +336,38 @@ func TestCreateServerImage(t *testing.T) {
_, err := CreateImage(client.ServiceClient(), "serverimage", CreateImageOpts{Name: "test"}).ExtractImageID()
th.AssertNoErr(t, err)
}

func TestMarshalPersonality(t *testing.T) {
name := "/etc/test"
contents := []byte("asdfasdf")

personality := Personality{
&File{
Path: name,
Contents: contents,
},
}

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")
}

if actual[0]["contents"] != base64.StdEncoding.EncodeToString(contents) {
t.Fatal("file contents incorrect")
}
}
12 changes: 6 additions & 6 deletions rackspace/compute/v2/servers/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +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 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 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
Expand Down Expand Up @@ -130,9 +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 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 files to inject into the server at launch.
// Rebuild will base64-encode file contents for you.
Personality os.Personality

// Rackspace-specific stuff begins here.

Expand Down