Skip to content

Commit

Permalink
Add VM extension command
Browse files Browse the repository at this point in the history
[#155198696]

Signed-off-by: Kalai Wei <[email protected]>
  • Loading branch information
michelleheh authored and Kalai Wei committed Feb 14, 2018
1 parent 4d86b63 commit fbba00f
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 41 deletions.
4 changes: 2 additions & 2 deletions acceptance/create_certificate_authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ c8Ltdl0ms92X6z4Qh2GiA/URKQLC7yV/kSQfgPEwyITXv4cCqm3o
}))
})

It("creates a certificate authority on the OpsMan", func() {
It("creates a certificate authority in OpsMan", func() {
command := exec.Command(pathToMain,
"--target", server.URL,
"--username", "some-username",
Expand All @@ -160,7 +160,7 @@ c8Ltdl0ms92X6z4Qh2GiA/URKQLC7yV/kSQfgPEwyITXv4cCqm3o
})

Context("when json format is requested", func() {
It("creates a certificate authority on Ops Man", func() {
It("creates a certificate authority in OpsMan", func() {
command := exec.Command(pathToMain,
"--target", server.URL,
"--username", "some-username",
Expand Down
75 changes: 75 additions & 0 deletions acceptance/create_vm_extension_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package acceptance

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/http/httputil"
"os/exec"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
)

var _ = Describe("create VM extension", func() {
var server *httptest.Server
BeforeEach(func() {
server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")

switch req.URL.Path {
case "/uaa/oauth/token":
w.Write([]byte(`{
"access_token": "some-opsman-token",
"token_type": "bearer",
"expires_in": 3600
}`))
case "/api/v0/staged/vm_extensions":
Expect(req.Method).To(Equal(http.MethodPost))

body, err := ioutil.ReadAll(req.Body)
Expect(err).NotTo(HaveOccurred())

Expect(body).To(MatchJSON(`
{
"name": "some-vm-extension",
"cloud_properties": {
"iam_instance_profile": "some-iam-profile",
"elbs": ["some-elb"]
}
}
`))

responseJSON, err := json.Marshal([]byte("{}"))
Expect(err).NotTo(HaveOccurred())

w.Write([]byte(responseJSON))
default:
out, err := httputil.DumpRequest(req, true)
Expect(err).NotTo(HaveOccurred())
Fail(fmt.Sprintf("unexpected request: %s", out))
}
}))
})

It("creates a VM extension in OpsMan", func() {
command := exec.Command(pathToMain,
"--target", server.URL,
"--username", "some-username",
"--password", "some-password",
"--skip-ssl-validation",
"create-vm-extension",
"--name", "some-vm-extension",
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
)

session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())

Eventually(session).Should(gexec.Exit(0))
Expect(string(session.Out.Contents())).To(Equal("VM Extension 'some-vm-extension' created\n"))
})
})
1 change: 1 addition & 0 deletions acceptance/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Commands:
configure-director configures the director
configure-product configures a staged product
create-certificate-authority creates a certificate authority on the Ops Manager
create-vm-extension creates a VM extension
credential-references list credential references for a deployed product
credentials fetch credentials for a deployed product
curl issues an authenticated API request
Expand Down
8 changes: 6 additions & 2 deletions api/fakes/httpclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions api/vm_extensions_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package api

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)

type VMExtensionsService struct {
client httpClient
}

type CreateVMExtension struct {
Name string `json:"name"`
CloudProperties json.RawMessage `json:"cloud_properties"`
}

func NewVMExtensionsService(client httpClient) VMExtensionsService {
return VMExtensionsService{
client: client,
}
}

type VMExtensionInput struct {
Name string `json:"name"`
CloudProperties string `json:"cloud_properties"`
}

func (v VMExtensionsService) Create(input CreateVMExtension) error {
jsonData, err := json.Marshal(&input)

if err != nil {
return fmt.Errorf("could not marshal json: %s", err)
}

verb := "POST"
endpoint := "/api/v0/staged/vm_extensions"
req, err := http.NewRequest(verb, endpoint, bytes.NewReader(jsonData))
if err != nil {
return fmt.Errorf("could not create api request %s %s: %s", verb, endpoint, err.Error())
}
req.Header.Add("Content-Type", "application/json")

resp, err := v.client.Do(req)
if err != nil {
return fmt.Errorf("could not send api request to %s %s: %s", verb, endpoint, err.Error())
}

if err = ValidateStatusOK(resp); err != nil {
return err
}

return nil
}
82 changes: 82 additions & 0 deletions api/vm_extensions_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package api_test

import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pivotal-cf/om/api"
"github.com/pivotal-cf/om/api/fakes"
)

var _ = Describe("VMExtensionsService", func() {
var (
client *fakes.HttpClient
vmExtensionsService api.VMExtensionsService
)

BeforeEach(func() {
client = &fakes.HttpClient{}
vmExtensionsService = api.NewVMExtensionsService(client)

client.DoReturns(&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, nil)
})

It("creates a VM Extension", func() {
err := vmExtensionsService.Create(api.CreateVMExtension{
Name: "some-vm-extension",
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
})

Expect(err).NotTo(HaveOccurred())

Expect(client.DoCallCount()).To(Equal(1))
req := client.DoArgsForCall(0)

Expect(req.Method).To(Equal("POST"))
Expect(req.URL.Path).To(Equal("/api/v0/staged/vm_extensions"))
Expect(req.Header.Get("Content-Type")).To(Equal("application/json"))

jsonBody, err := ioutil.ReadAll(req.Body)
Expect(err).NotTo(HaveOccurred())
Expect(jsonBody).To(MatchJSON(`{
"name": "some-vm-extension",
"cloud_properties": {"iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"]}
}`))
})

Context("failure cases", func() {
It("returns an error when the http status is non-200", func() {

client.DoReturns(&http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, nil)

err := vmExtensionsService.Create(api.CreateVMExtension{
Name: "some-vm-extension",
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
})

Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error")))
})

It("returns an error when the api endpoint fails", func() {
client.DoReturns(&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, errors.New("api endpoint failed"))

err := vmExtensionsService.Create(api.CreateVMExtension{
Name: "some-vm-extension",
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
})

Expect(err).To(MatchError("could not send api request to POST /api/v0/staged/vm_extensions: api endpoint failed"))
})
})
})
57 changes: 57 additions & 0 deletions commands/create_vm_extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package commands

import (
"encoding/json"
"fmt"

"github.com/pivotal-cf/jhanda"
"github.com/pivotal-cf/om/api"
)

//go:generate counterfeiter -o ./fakes/vm_extension_creator.go --fake-name VMExtensionCreator . vmExtensionCreator
type vmExtensionCreator interface {
Create(api.CreateVMExtension) error
}

type CreateVMExtension struct {
service vmExtensionCreator
logger logger
Options struct {
Name string `long:"name" short:"n" required:"true" description:"VM extension name"`
CloudProperties string `long:"cloud-properties" short:"cp" required:"true" description:"cloud properties in JSON format"`
}
}

func NewCreateVMExtension(service vmExtensionCreator, logger logger) CreateVMExtension {
return CreateVMExtension{
service: service,
logger: logger,
}
}

func (c CreateVMExtension) Execute(args []string) error {
if _, err := jhanda.Parse(&c.Options, args); err != nil {
return fmt.Errorf("could not parse create-vm-extension flags: %s", err)
}

err := c.service.Create(api.CreateVMExtension{
Name: c.Options.Name,
CloudProperties: json.RawMessage(c.Options.CloudProperties),
})

if err != nil {
return err
}

c.logger.Printf("VM Extension '%s' created\n", c.Options.Name)

return nil
}

func (c CreateVMExtension) Usage() jhanda.Usage {
return jhanda.Usage{
Description: "This creates a VM extension",
ShortDescription: "creates a VM extension",
Flags: c.Options,
}
}
80 changes: 80 additions & 0 deletions commands/create_vm_extension_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package commands_test

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

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pivotal-cf/jhanda"
"github.com/pivotal-cf/om/api"
"github.com/pivotal-cf/om/commands"
"github.com/pivotal-cf/om/commands/fakes"
)

var _ = Describe("CreateVMExtension", func() {
var (
fakeVMExtensionService *fakes.VMExtensionCreator
fakeLogger *fakes.Logger
command commands.CreateVMExtension
)

BeforeEach(func() {
fakeVMExtensionService = &fakes.VMExtensionCreator{}
fakeLogger = &fakes.Logger{}
command = commands.NewCreateVMExtension(fakeVMExtensionService, fakeLogger)
})

Describe("Execute", func() {
It("makes a request to the OpsMan to create a VM extension", func() {
err := command.Execute([]string{
"--name", "some-vm-extension",
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
})

Expect(err).NotTo(HaveOccurred())
Expect(fakeVMExtensionService.CreateArgsForCall(0)).To(Equal(api.CreateVMExtension{
Name: "some-vm-extension",
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
}))

Expect(fakeLogger.PrintfCallCount()).To(Equal(1))
format, content := fakeLogger.PrintfArgsForCall(0)
Expect(fmt.Sprintf(format, content...)).To(Equal("VM Extension 'some-vm-extension' created\n"))
})

Context("failure cases", func() {
Context("when the service fails to create a VM extension", func() {
It("returns an error", func() {
fakeVMExtensionService.CreateReturns(errors.New("failed to create VM extension"))

err := command.Execute([]string{
"--name", "some-vm-extension",
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
})

Expect(err).To(MatchError("failed to create VM extension"))
})
})

Context("when an unknown flag is provided", func() {
It("returns an error", func() {
err := command.Execute([]string{"--badflag"})
Expect(err).To(MatchError("could not parse create-vm-extension flags: flag provided but not defined: -badflag"))
})
})
})
})

Describe("Usage", func() {
It("returns usage information for the command", func() {
command := commands.NewCreateVMExtension(nil, nil)
Expect(command.Usage()).To(Equal(jhanda.Usage{
Description: "This creates a VM extension",
ShortDescription: "creates a VM extension",
Flags: command.Options,
}))
})
})
})
Loading

0 comments on commit fbba00f

Please sign in to comment.