Skip to content

Commit

Permalink
Merge 2d925cd into 34d3d6f
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinzs2048 committed Apr 12, 2018
2 parents 34d3d6f + 2d925cd commit 5121dc3
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 6 deletions.
56 changes: 56 additions & 0 deletions acceptance/openstack/container/v1/capsules_test.go
Expand Up @@ -26,3 +26,59 @@ func TestCapsuleGet(t *testing.T) {
th.AssertEquals(t, capsule.MetaName, "template")
th.AssertEquals(t, capsule.CPU, float64(2.0))
}

func TestCapsuleCreate(t *testing.T) {
client, err := clients.NewContainerV1Client()
if err != nil {
t.Fatalf("Unable to create an container v1 client: %v", err)
}
th.AssertNoErr(t, err)
template := new(capsules.Template)
template.Bin = []byte(`{
"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {
"app": "web",
"app1": "web1"
},
"name": "template"
},
"restartPolicy": "Always",
"spec": {
"containers": [
{
"command": [
"/bin/bash"
],
"env": {
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin"
},
"image": "ubuntu",
"imagePullPolicy": "ifnotpresent",
"ports": [
{
"containerPort": 80,
"hostPort": 80,
"name": "nginx-port",
"protocol": "TCP"
}
],
"resources": {
"requests": {
"cpu": 1,
"memory": 1024
}
},
"workDir": "/root"
}
]
}
}`)
createOpts := capsules.CreateOpts{
TemplateOpts: template,
}
err = capsules.Create(client, createOpts).ExtractErr()
th.AssertNoErr(t, err)
}
Expand Up @@ -121,7 +121,7 @@ var FirstQuotaSet = quotasets.QuotaSet{

// FirstQuotaDetailsSet is the first result in ListOutput.
var FirstQuotaDetailsSet = quotasets.QuotaDetailSet{
ID: FirstTenantID,
ID: FirstTenantID,
InjectedFileContentBytes: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 10240},
InjectedFilePathBytes: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 255},
InjectedFiles: quotasets.QuotaDetail{InUse: 0, Reserved: 0, Limit: 5},
Expand Down
33 changes: 33 additions & 0 deletions openstack/container/v1/capsules/errors.go
@@ -0,0 +1,33 @@
package capsules

import (
"fmt"

"github.com/gophercloud/gophercloud"
)

type ErrInvalidEnvironment struct {
gophercloud.BaseError
Section string
}

func (e ErrInvalidEnvironment) Error() string {
return fmt.Sprintf("Environment has wrong section: %s", e.Section)
}

type ErrInvalidDataFormat struct {
gophercloud.BaseError
}

func (e ErrInvalidDataFormat) Error() string {
return fmt.Sprintf("Data in neither json nor yaml format.")
}

type ErrInvalidTemplateFormatVersion struct {
gophercloud.BaseError
Version string
}

func (e ErrInvalidTemplateFormatVersion) Error() string {
return fmt.Sprintf("Template format version not found.")
}
166 changes: 166 additions & 0 deletions openstack/container/v1/capsules/fixtures.go
@@ -0,0 +1,166 @@
package capsules

// ValidJSONTemplate is a valid OpenStack Capsule template in JSON format
const ValidJSONTemplate = `
{
"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {
"app": "web",
"app1": "web1"
},
"name": "template"
},
"restartPolicy": "Always",
"spec": {
"containers": [
{
"command": [
"/bin/bash"
],
"env": {
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin"
},
"image": "ubuntu",
"imagePullPolicy": "ifnotpresent",
"ports": [
{
"containerPort": 80,
"hostPort": 80,
"name": "nginx-port",
"protocol": "TCP"
}
],
"resources": {
"requests": {
"cpu": 1,
"memory": 1024
}
},
"workDir": "/root"
}
]
}
}
`

// ValidYAMLTemplate is a valid OpenStack Capsule template in YAML format
const ValidYAMLTemplate = `
capsuleVersion: beta
kind: capsule
metadata:
name: template
labels:
app: web
app1: web1
restartPolicy: Always
spec:
containers:
- image: ubuntu
command:
- "/bin/bash"
imagePullPolicy: ifnotpresent
workDir: /root
ports:
- name: nginx-port
containerPort: 80
hostPort: 80
protocol: TCP
resources:
requests:
cpu: 1
memory: 1024
env:
ENV1: /usr/local/bin
ENV2: /usr/bin
`

// ValidJSONTemplateParsed is the expected parsed version of ValidJSONTemplate
var ValidJSONTemplateParsed = map[string]interface{}{
"capsuleVersion": "beta",
"kind": "capsule",
"restartPolicy": "Always",
"metadata": map[string]interface{}{
"name": "template",
"labels": map[string]string{
"app": "web",
"app1": "web1",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
map[string]interface{}{
"image": "ubuntu",
"command": []interface{}{
"/bin/bash",
},
"imagePullPolicy": "ifnotpresent",
"workDir": "/root",
"ports": []interface{}{
map[string]interface{}{
"name": "nginx-port",
"containerPort": float64(80),
"hostPort": float64(80),
"protocol": "TCP",
},
},
"resources": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": float64(1),
"memory": float64(1024),
},
},
"env": map[string]interface{}{
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin",
},
},
},
},
}

// ValidYAMLTemplateParsed is the expected parsed version of ValidYAMLTemplate
var ValidYAMLTemplateParsed = map[string]interface{}{
"capsuleVersion": "beta",
"kind": "capsule",
"restartPolicy": "Always",
"metadata": map[string]interface{}{
"name": "template",
"labels": map[string]string{
"app": "web",
"app1": "web1",
},
},
"spec": map[interface{}]interface{}{
"containers": []map[interface{}]interface{}{
map[interface{}]interface{}{
"image": "ubuntu",
"command": []interface{}{
"/bin/bash",
},
"imagePullPolicy": "ifnotpresent",
"workDir": "/root",
"ports": []interface{}{
map[interface{}]interface{}{
"name": "nginx-port",
"containerPort": 80,
"hostPort": 80,
"protocol": "TCP",
},
},
"resources": map[interface{}]interface{}{
"requests": map[interface{}]interface{}{
"cpu": 1,
"memory": 1024,
},
},
"env": map[interface{}]interface{}{
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin",
},
},
},
},
}
43 changes: 43 additions & 0 deletions openstack/container/v1/capsules/requests.go
Expand Up @@ -4,10 +4,53 @@ import (
"github.com/gophercloud/gophercloud"
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
ToCapsuleCreateMap() (map[string]interface{}, error)
}

// Get requests details on a single capsule, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
return
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// A structure that contains either the template file or url. Call the
// associated methods to extract the information relevant to send in a create request.
TemplateOpts *Template `json:"-" required:"true"`
}

// ToCapsuleCreateMap assembles a request body based on the contents of
// a CreateOpts.
func (opts CreateOpts) ToCapsuleCreateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}

if err := opts.TemplateOpts.Parse(); err != nil {
return nil, err
}
b["template"] = string(opts.TemplateOpts.Bin)

return b, nil
}

// Create implements create capsule request.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToCapsuleCreateMap()
if err != nil {
r.Err = err
return r
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{202}})
return
}
6 changes: 6 additions & 0 deletions openstack/container/v1/capsules/results.go
Expand Up @@ -23,6 +23,12 @@ type GetResult struct {
commonResult
}

// CreateResult is the response from a Create operation. Call its Extract
// method to interpret it as a Server.
type CreateResult struct {
gophercloud.ErrResult
}

// Represents a Container Orchestration Engine Bay, i.e. a cluster
type Capsule struct {
// UUID for the capsule
Expand Down
26 changes: 26 additions & 0 deletions openstack/container/v1/capsules/template.go
@@ -0,0 +1,26 @@
package capsules

import (
"encoding/json"

"gopkg.in/yaml.v2"
)

type Template struct {
// Bin stores the contents of the template or environment.
Bin []byte
// Parsed contains a parsed version of Bin. Since there are 2 different
// fields referring to the same value, you must be careful when accessing
// this filed.
Parsed map[string]interface{}
}

// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
func (t *Template) Parse() error {
if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
return ErrInvalidDataFormat{}
}
}
return nil
}
28 changes: 28 additions & 0 deletions openstack/container/v1/capsules/template_test.go
@@ -0,0 +1,28 @@
package capsules

import (
"testing"

th "github.com/gophercloud/gophercloud/testhelper"
)

func TestTemplateParsing(t *testing.T) {
templateJSON := new(Template)
templateJSON.Bin = []byte(ValidJSONTemplate)
err := templateJSON.Parse()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, ValidJSONTemplateParsed, templateJSON.Parsed)

templateYAML := new(Template)
templateYAML.Bin = []byte(ValidYAMLTemplate)
err = templateYAML.Parse()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, ValidYAMLTemplateParsed, templateYAML.Parsed)

templateInvalid := new(Template)
templateInvalid.Bin = []byte("Keep Austin Weird")
err = templateInvalid.Parse()
if err == nil {
t.Error("Template parsing did not catch invalid template")
}
}

0 comments on commit 5121dc3

Please sign in to comment.