Skip to content

Commit

Permalink
[CMP-1414] - Added Client To Get CloudBolt Resource By ID (#14)
Browse files Browse the repository at this point in the history
* [CMP-1414] - Added Client To Get CloudBolt Resource By ID

Added method and unit test to get CloudBolt resource by ID.

* [CMP-1414] - Added Client SDK Methods to Get Server and Resource By Name
  • Loading branch information
laltomar authored Jan 31, 2024
1 parent caeb89f commit e6af043
Show file tree
Hide file tree
Showing 6 changed files with 520 additions and 2 deletions.
56 changes: 56 additions & 0 deletions cbclient/resource_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cbclient

import (
"encoding/json"
"fmt"
"log"
"net/url"
)

// CloudBoltResource contains metadata about Resources (e.g., "Services") in CloudBolt
Expand All @@ -25,6 +27,60 @@ type CloudBoltResource struct {
Attributes []map[string]interface{} `json:"attributes"`
}

type CloudBoltResourceResult struct {
CloudBoltResult
Embedded struct {
Resources []CloudBoltResource `json:"resources"`
} `json:"_embedded"`
}

func (c *CloudBoltClient) GetResourceById(id string) (*CloudBoltResource, error) {
apiurl := c.baseURL
apiurl.Path = c.apiEndpoint("cmp", "resources", id)

resp, err := c.makeRequest("GET", apiurl.String(), nil)
if err != nil {
return nil, err
}

var res CloudBoltResource
json.NewDecoder(resp.Body).Decode(&res)

return &res, nil
}

func (c *CloudBoltClient) GetResourceByName(name string) (*CloudBoltResource, error) {
apiurl := c.baseURL
apiurl.Path = c.apiEndpoint("cmp", "resources")
apiurl.RawQuery = fmt.Sprintf(filterByName+";status:ACTIVE", url.QueryEscape(name))

resp, err := c.makeRequest("GET", apiurl.String(), nil)
if err != nil {
return nil, err
}

// We Decode the data because we already have an io.Reader on hand
var res CloudBoltResourceResult
json.NewDecoder(resp.Body).Decode(&res)

// TODO: Sanity check the decoded object
if len(res.Embedded.Resources) == 0 {
return nil, fmt.Errorf(
"Could not find resorce with name %s. Does the user have permission to view this?",
name,
)
}

if len(res.Embedded.Resources) > 1 {
return nil, fmt.Errorf(
"More than one resource with name %s found.",
name,
)
}

return &res.Embedded.Resources[0], nil
}

// GetResource fetches a Resource object from CloudBolt at the given path
// - Resource Path (resourcePath) e.g., "/api/v2/resources/service/123/"
func (c *CloudBoltClient) GetResource(resourcePath string) (*CloudBoltResource, error) {
Expand Down
136 changes: 136 additions & 0 deletions cbclient/resource_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,139 @@ func TestGetResource(t *testing.T) {
Expect(resource.Status).To(Equal("ACTIVE"))
Expect(resource.Attributes).To(Not(BeNil()))
}

func TestGetResourceByID(t *testing.T) {
// Register test with gomega
RegisterTestingT(t)

// Setup mock server with scripted responses
// Setup requests buffer
server, requests := mockServer(responsesForGetResourceById)
Expect(server).NotTo(BeNil())
Expect(requests).NotTo(BeNil())

// Setup CloudBolt Client
client := getClient(server)
Expect(client).NotTo(BeNil())

// Define a resourcePath parameter value
resourceId := "RSC-hjt2wha2"

// Get the CloudBolt Resource object
// Expect no errors to occur
resource, err := client.GetResourceById(resourceId)
Expect(resource).NotTo(BeNil())
Expect(err).NotTo(HaveOccurred())

// This should have made three requests:
// 1+2. Fail to get resource, get a token
// 3. Successfully getting the resource
Expect(len(*requests)).To(Equal(3))

// The last request is the one we care about
Expect((*requests)[2].URL.Path).To(Equal("/api/v3/cmp/resources/RSC-hjt2wha2/"))

// The CloudBolt Resource object should be parsed correctly
Expect(resource.Links.Self.Href).To(Equal("/api/v3/cmp/resources/RSC-hjt2wha2/"))
Expect(resource.Links.Self.Title).To(Equal("My Simple Blueprint"))
Expect(resource.Links.ResourceType.Href).To(Equal("/api/v3/cmp/resourceTypes/RT-bde9nds8/"))
Expect(resource.Links.ResourceType.Title).To(Equal("service"))
Expect(resource.Links.Blueprint.Href).To(Equal("/api/v3/cmp/blueprints/BP-esnjtp7u/"))
Expect(resource.Links.Blueprint.Title).To(Equal("My Simple Blueprint"))
Expect(resource.Links.Owner.Href).To(Equal("/api/v3/cloudbolt/users/USR-mxpqe1x7/"))
Expect(resource.Links.Owner.Title).To(Equal("user001"))
Expect(resource.Links.Group.Href).To(Equal("/api/v3/cloudbolt/groups/GRP-yfbbsfht/"))
Expect(resource.Links.Group.Title).To(Equal("My Org"))
Expect(len(resource.Links.Jobs)).To(Equal(3))
Expect(resource.Links.Jobs[0].Href).To(Equal("/api/v3/cmp/jobs/JOB-9nrax3gb/"))
Expect(resource.Links.Jobs[0].Title).To(Equal("Deploy Blueprint Job 1011"))
Expect(resource.Links.Jobs[1].Href).To(Equal("/api/v3/cmp/jobs/JOB-t2js3lwf/"))
Expect(resource.Links.Jobs[1].Title).To(Equal("My Action Job 1013"))
Expect(resource.Links.Jobs[2].Href).To(Equal("/api/v3/cmp/jobs/JOB-8i53zztl/"))
Expect(resource.Links.Jobs[2].Title).To(Equal("My Simple Resource Action Job 1016"))
Expect(resource.Links.ParentResource.Href).To(Equal(""))
Expect(resource.Links.ParentResource.Title).To(Equal(""))
Expect(len(resource.Links.Actions)).To(Equal(3))
Expect(resource.Links.Actions[0].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-hxfync2x/"))
Expect(resource.Links.Actions[0].Title).To(Equal("Scale"))
Expect(resource.Links.Actions[1].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-aq3b3gxm/"))
Expect(resource.Links.Actions[1].Title).To(Equal("My Resource Action"))
Expect(resource.Links.Actions[2].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-beim3g0e/"))
Expect(resource.Links.Actions[2].Title).To(Equal("Delete"))
Expect(len(resource.Links.Servers)).To(Equal(1))
Expect(resource.Links.Servers[0].Href).To(Equal("/api/v3/cmp/servers/SVR-srb5y8r3/"))
Expect(resource.Links.Servers[0].Title).To(Equal("myawsinstance"))
Expect(resource.Name).To(Equal("My Simple Blueprint"))
Expect(resource.ID).To(Equal("RSC-hjt2wha2"))
Expect(resource.Created).To(Equal("2022-04-10 10:04:15"))
Expect(resource.Status).To(Equal("ACTIVE"))
Expect(resource.Attributes).To(Not(BeNil()))
}

func TestGetResourceByName(t *testing.T) {
// Register test with gomega
RegisterTestingT(t)

// Setup mock server with scripted responses
// Setup requests buffer
server, requests := mockServer(responsesForGetResourceByName)
Expect(server).NotTo(BeNil())
Expect(requests).NotTo(BeNil())

// Setup CloudBolt Client
client := getClient(server)
Expect(client).NotTo(BeNil())

// Define a resourcePath parameter value
resourceName := "My Simple Blueprint"

// Get the CloudBolt Resource object
// Expect no errors to occur
resource, err := client.GetResourceByName(resourceName)
Expect(resource).NotTo(BeNil())
Expect(err).NotTo(HaveOccurred())

// This should have made three requests:
// 1+2. Fail to get resource, get a token
// 3. Successfully getting the resource
Expect(len(*requests)).To(Equal(3))

// The last request is the one we care about
Expect((*requests)[2].URL.Path).To(Equal("/api/v3/cmp/resources/"))

// The CloudBolt Resource object should be parsed correctly
Expect(resource.Links.Self.Href).To(Equal("/api/v3/cmp/resources/RSC-no9aztne/"))
Expect(resource.Links.Self.Title).To(Equal("My Simple Blueprint"))
Expect(resource.Links.ResourceType.Href).To(Equal("/api/v3/cmp/resourceTypes/RT-bde9nds8/"))
Expect(resource.Links.ResourceType.Title).To(Equal("service"))
Expect(resource.Links.Blueprint.Href).To(Equal("/api/v3/cmp/blueprints/BP-esnjtp7u/"))
Expect(resource.Links.Blueprint.Title).To(Equal("My Simple Blueprint"))
Expect(resource.Links.Owner.Href).To(Equal("/api/v3/cloudbolt/users/USR-mxpqe1x7/"))
Expect(resource.Links.Owner.Title).To(Equal("user001"))
Expect(resource.Links.Group.Href).To(Equal("/api/v3/cloudbolt/groups/GRP-yfbbsfht/"))
Expect(resource.Links.Group.Title).To(Equal("My Org"))
Expect(len(resource.Links.Jobs)).To(Equal(3))
Expect(resource.Links.Jobs[0].Href).To(Equal("/api/v3/cmp/jobs/JOB-9nrax3gb/"))
Expect(resource.Links.Jobs[0].Title).To(Equal("Deploy Blueprint Job 1011"))
Expect(resource.Links.Jobs[1].Href).To(Equal("/api/v3/cmp/jobs/JOB-t2js3lwf/"))
Expect(resource.Links.Jobs[1].Title).To(Equal("My Action Job 1013"))
Expect(resource.Links.Jobs[2].Href).To(Equal("/api/v3/cmp/jobs/JOB-8i53zztl/"))
Expect(resource.Links.Jobs[2].Title).To(Equal("My Simple Resource Action Job 1016"))
Expect(resource.Links.ParentResource.Href).To(Equal(""))
Expect(resource.Links.ParentResource.Title).To(Equal(""))
Expect(len(resource.Links.Actions)).To(Equal(3))
Expect(resource.Links.Actions[0].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-hxfync2x/"))
Expect(resource.Links.Actions[0].Title).To(Equal("Scale"))
Expect(resource.Links.Actions[1].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-aq3b3gxm/"))
Expect(resource.Links.Actions[1].Title).To(Equal("My Resource Action"))
Expect(resource.Links.Actions[2].Href).To(Equal("/api/v3/cmp/resourceActions/RSA-beim3g0e/"))
Expect(resource.Links.Actions[2].Title).To(Equal("Delete"))
Expect(len(resource.Links.Servers)).To(Equal(1))
Expect(resource.Links.Servers[0].Href).To(Equal("/api/v3/cmp/servers/SVR-srb5y8r3/"))
Expect(resource.Links.Servers[0].Title).To(Equal("myawsinstance"))
Expect(resource.Name).To(Equal("My Simple Blueprint"))
Expect(resource.ID).To(Equal("RSC-no9aztne"))
Expect(resource.Created).To(Equal("2022-04-10 10:04:15"))
Expect(resource.Status).To(Equal("ACTIVE"))
Expect(resource.Attributes).To(Not(BeNil()))
}
41 changes: 41 additions & 0 deletions cbclient/server_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cbclient

import (
"encoding/json"
"fmt"
"log"
"net/url"
)

// CloudBoltServer stores metadata about servers in CloudBolt.
Expand Down Expand Up @@ -45,6 +47,13 @@ type CloudBoltServer struct {
TechSpecificAttributes map[string]interface{} `json:"techSpecificAttributes"`
}

type CloudBoltServerResult struct {
CloudBoltResult
Embedded struct {
Servers []CloudBoltServer `json:"servers"`
} `json:"_embedded"`
}

type CloudBoltDecomServerResult struct {
Links struct {
Self CloudBoltHALItem `json:"self"`
Expand Down Expand Up @@ -86,6 +95,38 @@ func (c *CloudBoltClient) GetServerById(id string) (*CloudBoltServer, error) {
return &svr, nil
}

func (c *CloudBoltClient) GetServerByHostname(hostname string) (*CloudBoltServer, error) {
apiurl := c.baseURL
apiurl.Path = c.apiEndpoint("cmp", "servers")
apiurl.RawQuery = fmt.Sprintf("filter=hostname:%s;status:ACTIVE", url.QueryEscape(hostname))

resp, err := c.makeRequest("GET", apiurl.String(), nil)
if err != nil {
return nil, err
}

// We Decode the data because we already have an io.Reader on hand
var res CloudBoltServerResult
json.NewDecoder(resp.Body).Decode(&res)

// TODO: Sanity check the decoded object
if len(res.Embedded.Servers) == 0 {
return nil, fmt.Errorf(
"Could not find active server with hostname %s. Does the user have permission to view this?",
hostname,
)
}

if len(res.Embedded.Servers) > 1 {
return nil, fmt.Errorf(
"More than one active server with hostname %s found.",
hostname,
)
}

return &res.Embedded.Servers[0], nil
}

func (c *CloudBoltClient) DecomServer(serverId string) (*CloudBoltDecomServerResult, error) {
apiurl := c.baseURL
apiurl.Path = c.apiEndpoint(
Expand Down
55 changes: 55 additions & 0 deletions cbclient/server_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,61 @@ func TestGetServerById(t *testing.T) {
Expect(cbServer.TechSpecificAttributes).To(Not(BeNil()))
}

func TestGetServerByHostname(t *testing.T) {
// Register the test with gomega
RegisterTestingT(t)

// Setup mock server with scripted responses
// Setup requests buffer
server, requests := mockServer(responsesForGetServerByHostname)
Expect(server).NotTo(BeNil())
Expect(requests).NotTo(BeNil())

// Setup CloudBolt Client
client := getClient(server)
Expect(client).NotTo(BeNil())

// Define a serverPath parameter value
hostname := "myawsinstance1"

// Get the CloudBolt Server object
// Expect no errors to occur
cbServer, err := client.GetServerByHostname(hostname)
Expect(cbServer).NotTo(BeNil())
Expect(err).NotTo(HaveOccurred())

// This should have made three requests:
// 1+2. Fail to get server, get a token
// 3. Successfully getting the server
Expect(len(*requests)).To(Equal(3))

// The last request is the one we care about
Expect((*requests)[2].URL.Path).To(Equal("/api/v3/cmp/servers/"))

// The CloudBolt Server object should be parsed correctly
Expect(cbServer.Links.Self.Href).To(Equal("/api/v3/cmp/servers/SVR-yrk09wht/"))
Expect(cbServer.Links.Self.Title).To(Equal("myawsinstance1"))
Expect(cbServer.ID).To(Equal("SVR-yrk09wht"))
Expect(cbServer.Hostname).To(Equal("myawsinstance1"))
Expect(cbServer.IP).To(Equal("3.17.176.101"))
Expect(cbServer.Status).To(Equal("ACTIVE"))
Expect(cbServer.Mac).To(Equal("02:99:e2:0f:18:b2"))
Expect(cbServer.PowerStatus).To(Equal("POWERON"))
Expect(cbServer.DateAddedToCloudbolt).To(Equal("2022-04-08 11:55:07.056038"))
Expect(cbServer.CPUCount).To(Equal(1))
Expect(cbServer.MemorySizeGB).To(Equal("0.5000"))
Expect(cbServer.DiskSizeGB).To(Equal(8))
Expect(cbServer.Notes).To(Equal(""))
Expect(len(cbServer.Labels)).To(Equal(0))
Expect(cbServer.OsFamily).To(Equal("Amazon Linux"))
Expect(cbServer.RateBreakdown).To(Not(BeNil()))
Expect(cbServer.Attributes).To(Not(BeNil()))
Expect(cbServer.Credentials).To(Not(BeNil()))
Expect(len(cbServer.Disks)).To(Equal(1))
Expect(len(cbServer.Networks)).To(Equal(1))
Expect(cbServer.TechSpecificAttributes).To(Not(BeNil()))
}

func TestDecomServerOrderResponse(t *testing.T) {
// Register the test with gomega
RegisterTestingT(t)
Expand Down
Loading

0 comments on commit e6af043

Please sign in to comment.