Skip to content

Commit fbba00f

Browse files
michellehehKalai Wei
authored and
Kalai Wei
committed
Add VM extension command
[#155198696] Signed-off-by: Kalai Wei <[email protected]>
1 parent 4d86b63 commit fbba00f

13 files changed

+505
-41
lines changed

Diff for: acceptance/create_certificate_authority_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ c8Ltdl0ms92X6z4Qh2GiA/URKQLC7yV/kSQfgPEwyITXv4cCqm3o
141141
}))
142142
})
143143

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

162162
Context("when json format is requested", func() {
163-
It("creates a certificate authority on Ops Man", func() {
163+
It("creates a certificate authority in OpsMan", func() {
164164
command := exec.Command(pathToMain,
165165
"--target", server.URL,
166166
"--username", "some-username",

Diff for: acceptance/create_vm_extension_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package acceptance
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"net/http/httptest"
9+
"net/http/httputil"
10+
"os/exec"
11+
12+
. "github.com/onsi/ginkgo"
13+
. "github.com/onsi/gomega"
14+
"github.com/onsi/gomega/gexec"
15+
)
16+
17+
var _ = Describe("create VM extension", func() {
18+
var server *httptest.Server
19+
BeforeEach(func() {
20+
server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
21+
w.Header().Set("Content-Type", "application/json")
22+
23+
switch req.URL.Path {
24+
case "/uaa/oauth/token":
25+
w.Write([]byte(`{
26+
"access_token": "some-opsman-token",
27+
"token_type": "bearer",
28+
"expires_in": 3600
29+
}`))
30+
case "/api/v0/staged/vm_extensions":
31+
Expect(req.Method).To(Equal(http.MethodPost))
32+
33+
body, err := ioutil.ReadAll(req.Body)
34+
Expect(err).NotTo(HaveOccurred())
35+
36+
Expect(body).To(MatchJSON(`
37+
{
38+
"name": "some-vm-extension",
39+
"cloud_properties": {
40+
"iam_instance_profile": "some-iam-profile",
41+
"elbs": ["some-elb"]
42+
}
43+
}
44+
`))
45+
46+
responseJSON, err := json.Marshal([]byte("{}"))
47+
Expect(err).NotTo(HaveOccurred())
48+
49+
w.Write([]byte(responseJSON))
50+
default:
51+
out, err := httputil.DumpRequest(req, true)
52+
Expect(err).NotTo(HaveOccurred())
53+
Fail(fmt.Sprintf("unexpected request: %s", out))
54+
}
55+
}))
56+
})
57+
58+
It("creates a VM extension in OpsMan", func() {
59+
command := exec.Command(pathToMain,
60+
"--target", server.URL,
61+
"--username", "some-username",
62+
"--password", "some-password",
63+
"--skip-ssl-validation",
64+
"create-vm-extension",
65+
"--name", "some-vm-extension",
66+
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
67+
)
68+
69+
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
70+
Expect(err).NotTo(HaveOccurred())
71+
72+
Eventually(session).Should(gexec.Exit(0))
73+
Expect(string(session.Out.Contents())).To(Equal("VM Extension 'some-vm-extension' created\n"))
74+
})
75+
})

Diff for: acceptance/help_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Commands:
3636
configure-director configures the director
3737
configure-product configures a staged product
3838
create-certificate-authority creates a certificate authority on the Ops Manager
39+
create-vm-extension creates a VM extension
3940
credential-references list credential references for a deployed product
4041
credentials fetch credentials for a deployed product
4142
curl issues an authenticated API request

Diff for: api/fakes/httpclient.go

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: api/vm_extensions_service.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
)
9+
10+
type VMExtensionsService struct {
11+
client httpClient
12+
}
13+
14+
type CreateVMExtension struct {
15+
Name string `json:"name"`
16+
CloudProperties json.RawMessage `json:"cloud_properties"`
17+
}
18+
19+
func NewVMExtensionsService(client httpClient) VMExtensionsService {
20+
return VMExtensionsService{
21+
client: client,
22+
}
23+
}
24+
25+
type VMExtensionInput struct {
26+
Name string `json:"name"`
27+
CloudProperties string `json:"cloud_properties"`
28+
}
29+
30+
func (v VMExtensionsService) Create(input CreateVMExtension) error {
31+
jsonData, err := json.Marshal(&input)
32+
33+
if err != nil {
34+
return fmt.Errorf("could not marshal json: %s", err)
35+
}
36+
37+
verb := "POST"
38+
endpoint := "/api/v0/staged/vm_extensions"
39+
req, err := http.NewRequest(verb, endpoint, bytes.NewReader(jsonData))
40+
if err != nil {
41+
return fmt.Errorf("could not create api request %s %s: %s", verb, endpoint, err.Error())
42+
}
43+
req.Header.Add("Content-Type", "application/json")
44+
45+
resp, err := v.client.Do(req)
46+
if err != nil {
47+
return fmt.Errorf("could not send api request to %s %s: %s", verb, endpoint, err.Error())
48+
}
49+
50+
if err = ValidateStatusOK(resp); err != nil {
51+
return err
52+
}
53+
54+
return nil
55+
}

Diff for: api/vm_extensions_service_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package api_test
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"io/ioutil"
7+
"net/http"
8+
"strings"
9+
10+
. "github.com/onsi/ginkgo"
11+
. "github.com/onsi/gomega"
12+
"github.com/pivotal-cf/om/api"
13+
"github.com/pivotal-cf/om/api/fakes"
14+
)
15+
16+
var _ = Describe("VMExtensionsService", func() {
17+
var (
18+
client *fakes.HttpClient
19+
vmExtensionsService api.VMExtensionsService
20+
)
21+
22+
BeforeEach(func() {
23+
client = &fakes.HttpClient{}
24+
vmExtensionsService = api.NewVMExtensionsService(client)
25+
26+
client.DoReturns(&http.Response{
27+
StatusCode: http.StatusOK,
28+
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, nil)
29+
})
30+
31+
It("creates a VM Extension", func() {
32+
err := vmExtensionsService.Create(api.CreateVMExtension{
33+
Name: "some-vm-extension",
34+
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
35+
})
36+
37+
Expect(err).NotTo(HaveOccurred())
38+
39+
Expect(client.DoCallCount()).To(Equal(1))
40+
req := client.DoArgsForCall(0)
41+
42+
Expect(req.Method).To(Equal("POST"))
43+
Expect(req.URL.Path).To(Equal("/api/v0/staged/vm_extensions"))
44+
Expect(req.Header.Get("Content-Type")).To(Equal("application/json"))
45+
46+
jsonBody, err := ioutil.ReadAll(req.Body)
47+
Expect(err).NotTo(HaveOccurred())
48+
Expect(jsonBody).To(MatchJSON(`{
49+
"name": "some-vm-extension",
50+
"cloud_properties": {"iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"]}
51+
}`))
52+
})
53+
54+
Context("failure cases", func() {
55+
It("returns an error when the http status is non-200", func() {
56+
57+
client.DoReturns(&http.Response{
58+
StatusCode: http.StatusInternalServerError,
59+
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, nil)
60+
61+
err := vmExtensionsService.Create(api.CreateVMExtension{
62+
Name: "some-vm-extension",
63+
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
64+
})
65+
66+
Expect(err).To(MatchError(ContainSubstring("500 Internal Server Error")))
67+
})
68+
69+
It("returns an error when the api endpoint fails", func() {
70+
client.DoReturns(&http.Response{
71+
StatusCode: http.StatusOK,
72+
Body: ioutil.NopCloser(strings.NewReader(`{}`))}, errors.New("api endpoint failed"))
73+
74+
err := vmExtensionsService.Create(api.CreateVMExtension{
75+
Name: "some-vm-extension",
76+
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
77+
})
78+
79+
Expect(err).To(MatchError("could not send api request to POST /api/v0/staged/vm_extensions: api endpoint failed"))
80+
})
81+
})
82+
})

Diff for: commands/create_vm_extension.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package commands
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/pivotal-cf/jhanda"
8+
"github.com/pivotal-cf/om/api"
9+
)
10+
11+
//go:generate counterfeiter -o ./fakes/vm_extension_creator.go --fake-name VMExtensionCreator . vmExtensionCreator
12+
type vmExtensionCreator interface {
13+
Create(api.CreateVMExtension) error
14+
}
15+
16+
type CreateVMExtension struct {
17+
service vmExtensionCreator
18+
logger logger
19+
Options struct {
20+
Name string `long:"name" short:"n" required:"true" description:"VM extension name"`
21+
CloudProperties string `long:"cloud-properties" short:"cp" required:"true" description:"cloud properties in JSON format"`
22+
}
23+
}
24+
25+
func NewCreateVMExtension(service vmExtensionCreator, logger logger) CreateVMExtension {
26+
return CreateVMExtension{
27+
service: service,
28+
logger: logger,
29+
}
30+
}
31+
32+
func (c CreateVMExtension) Execute(args []string) error {
33+
if _, err := jhanda.Parse(&c.Options, args); err != nil {
34+
return fmt.Errorf("could not parse create-vm-extension flags: %s", err)
35+
}
36+
37+
err := c.service.Create(api.CreateVMExtension{
38+
Name: c.Options.Name,
39+
CloudProperties: json.RawMessage(c.Options.CloudProperties),
40+
})
41+
42+
if err != nil {
43+
return err
44+
}
45+
46+
c.logger.Printf("VM Extension '%s' created\n", c.Options.Name)
47+
48+
return nil
49+
}
50+
51+
func (c CreateVMExtension) Usage() jhanda.Usage {
52+
return jhanda.Usage{
53+
Description: "This creates a VM extension",
54+
ShortDescription: "creates a VM extension",
55+
Flags: c.Options,
56+
}
57+
}

Diff for: commands/create_vm_extension_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package commands_test
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
"github.com/pivotal-cf/jhanda"
11+
"github.com/pivotal-cf/om/api"
12+
"github.com/pivotal-cf/om/commands"
13+
"github.com/pivotal-cf/om/commands/fakes"
14+
)
15+
16+
var _ = Describe("CreateVMExtension", func() {
17+
var (
18+
fakeVMExtensionService *fakes.VMExtensionCreator
19+
fakeLogger *fakes.Logger
20+
command commands.CreateVMExtension
21+
)
22+
23+
BeforeEach(func() {
24+
fakeVMExtensionService = &fakes.VMExtensionCreator{}
25+
fakeLogger = &fakes.Logger{}
26+
command = commands.NewCreateVMExtension(fakeVMExtensionService, fakeLogger)
27+
})
28+
29+
Describe("Execute", func() {
30+
It("makes a request to the OpsMan to create a VM extension", func() {
31+
err := command.Execute([]string{
32+
"--name", "some-vm-extension",
33+
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
34+
})
35+
36+
Expect(err).NotTo(HaveOccurred())
37+
Expect(fakeVMExtensionService.CreateArgsForCall(0)).To(Equal(api.CreateVMExtension{
38+
Name: "some-vm-extension",
39+
CloudProperties: json.RawMessage(`{ "iam_instance_profile": "some-iam-profile", "elbs": ["some-elb"] }`),
40+
}))
41+
42+
Expect(fakeLogger.PrintfCallCount()).To(Equal(1))
43+
format, content := fakeLogger.PrintfArgsForCall(0)
44+
Expect(fmt.Sprintf(format, content...)).To(Equal("VM Extension 'some-vm-extension' created\n"))
45+
})
46+
47+
Context("failure cases", func() {
48+
Context("when the service fails to create a VM extension", func() {
49+
It("returns an error", func() {
50+
fakeVMExtensionService.CreateReturns(errors.New("failed to create VM extension"))
51+
52+
err := command.Execute([]string{
53+
"--name", "some-vm-extension",
54+
"--cloud-properties", "{ \"iam_instance_profile\": \"some-iam-profile\", \"elbs\": [\"some-elb\"] }",
55+
})
56+
57+
Expect(err).To(MatchError("failed to create VM extension"))
58+
})
59+
})
60+
61+
Context("when an unknown flag is provided", func() {
62+
It("returns an error", func() {
63+
err := command.Execute([]string{"--badflag"})
64+
Expect(err).To(MatchError("could not parse create-vm-extension flags: flag provided but not defined: -badflag"))
65+
})
66+
})
67+
})
68+
})
69+
70+
Describe("Usage", func() {
71+
It("returns usage information for the command", func() {
72+
command := commands.NewCreateVMExtension(nil, nil)
73+
Expect(command.Usage()).To(Equal(jhanda.Usage{
74+
Description: "This creates a VM extension",
75+
ShortDescription: "creates a VM extension",
76+
Flags: command.Options,
77+
}))
78+
})
79+
})
80+
})

0 commit comments

Comments
 (0)