Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client SDK changes for foundation based bare metal provisioning #352

98 changes: 87 additions & 11 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (

const (
// libraryVersion = "v3"
defaultBaseURL = "https://%s/"
defaultBaseURL = "%s://%s/"
// absolutePath = "api/nutanix/" + libraryVersion
// userAgent = "nutanix/" + libraryVersion
mediaType = "application/json"
Expand Down Expand Up @@ -52,14 +52,16 @@ type RequestCompletionCallback func(*http.Request, *http.Response, interface{})

// Credentials needed username and password
type Credentials struct {
URL string
Username string
Password string
Endpoint string
Port string
Insecure bool
SessionAuth bool
ProxyURL string
URL string
Username string
Password string
Endpoint string
Port string
Insecure bool
SessionAuth bool
ProxyURL string
FoundationEndpoint string
FoundationPort string
}

// AdditionalFilter specification for client side filters
Expand All @@ -69,7 +71,7 @@ type AdditionalFilter struct {
}

// NewClient returns a new Nutanix API client.
func NewClient(credentials *Credentials, userAgent string, absolutePath string) (*Client, error) {
func NewClient(credentials *Credentials, userAgent string, absolutePath string, isHttp bool) (*Client, error) {
if userAgent == "" {
return nil, fmt.Errorf("userAgent argument must be passed")
}
Expand All @@ -96,7 +98,12 @@ func NewClient(credentials *Credentials, userAgent string, absolutePath string)

httpClient.Transport = logging.NewTransport("Nutanix", transCfg)

baseURL, err := url.Parse(fmt.Sprintf(defaultBaseURL, credentials.URL))
protocol := "https"
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets move http / https in constants.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can also create a method to figure out and construct a baseURL

if isHttp {
protocol = "http"
}

baseURL, err := url.Parse(fmt.Sprintf(defaultBaseURL, protocol, credentials.URL))

if err != nil {
return nil, err
Expand Down Expand Up @@ -132,6 +139,37 @@ func NewClient(credentials *Credentials, userAgent string, absolutePath string)
return c, nil
}

// NewBaseClient returns a basic http/https client based on isHttp flag
func NewBaseClient(credentials *Credentials, absolutePath string, isHttp bool) (*Client, error) {
if absolutePath == "" {
return nil, fmt.Errorf("absolutePath argument must be passed")
}

httpClient := http.DefaultClient

transCfg := &http.Transport{
//to skip/unskip SSL certificate validation
TLSClientConfig: &tls.Config{
InsecureSkipVerify: credentials.Insecure,
},
}
httpClient.Transport = logging.NewTransport("Nutanix", transCfg)

protocol := "https"
if isHttp {
protocol = "http"
}

baseURL, err := url.Parse(fmt.Sprintf(defaultBaseURL, protocol, credentials.URL))
if err != nil {
return nil, err
}

c := &Client{credentials, httpClient, baseURL, "", nil, nil, absolutePath}

return c, nil
}

// NewRequest creates a request
func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {
rel, errp := url.Parse(c.AbsolutePath + urlStr)
Expand Down Expand Up @@ -171,6 +209,37 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int
return req, nil
}

// NewRequest creates a request without authorisation headers
func (c *Client) NewUnAuthRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {

//create main api url
rel, err := url.Parse(c.AbsolutePath + urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)

buf := new(bytes.Buffer)
if body != nil {
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}

req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}

//add api headers
req.Header.Add("Content-Type", mediaType)
req.Header.Add("Accept", mediaType)
req.Header.Add("User-Agent", c.UserAgent)

return req, nil
}

// NewUploadRequest Handles image uploads for image service
func (c *Client) NewUploadRequest(ctx context.Context, method, urlStr string, body []byte) (*http.Request, error) {
rel, errp := url.Parse(c.AbsolutePath + urlStr)
Expand Down Expand Up @@ -424,10 +493,17 @@ func CheckResponse(r *http.Response) error {
}
log.Print("[DEBUG] first nil check")

// foundation /image_nodes api error check
if errStruct, ok := res["error"]; ok {
return fmt.Errorf("error: %s", errStruct)
}

// karbon error check
if messageInfo, ok := res["message_info"]; ok {
return fmt.Errorf("error: %s", messageInfo)
}

//This check is also used for foundation /progress api errors
if message, ok := res["message"]; ok {
log.Print(message)
return fmt.Errorf("error: %s", message)
Expand Down
10 changes: 5 additions & 5 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ func setup() (*http.ServeMux, *Client, *httptest.Server) {
mux := http.NewServeMux()
server := httptest.NewServer(mux)

client, _ := NewClient(&Credentials{"", "username", "password", "", "", true, false, ""}, testUserAgent, testAbsolutePath)
client, _ := NewClient(&Credentials{"", "username", "password", "", "", true, false, "", "", ""}, testUserAgent, testAbsolutePath, false)
client.BaseURL, _ = url.Parse(server.URL)

return mux, client, server
}

func TestNewClient(t *testing.T) {
c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, ""}, testUserAgent, testAbsolutePath)
c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, "", "", ""}, testUserAgent, testAbsolutePath, false)

if err != nil {
t.Errorf("Unexpected Error: %v", err)
}

expectedURL := fmt.Sprintf(defaultBaseURL, "foo.com")
expectedURL := fmt.Sprintf(defaultBaseURL, "https", "foo.com")

if c.BaseURL == nil || c.BaseURL.String() != expectedURL {
t.Errorf("NewClient BaseURL = %v, expected %v", c.BaseURL, expectedURL)
Expand All @@ -49,13 +49,13 @@ func TestNewClient(t *testing.T) {
}

func TestNewRequest(t *testing.T) {
c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, ""}, testUserAgent, testAbsolutePath)
c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, "", "", ""}, testUserAgent, testAbsolutePath, false)

if err != nil {
t.Errorf("Unexpected Error: %v", err)
}

inURL, outURL := "/foo", fmt.Sprintf(defaultBaseURL+testAbsolutePath+"/foo", "foo.com")
inURL, outURL := "/foo", fmt.Sprintf(defaultBaseURL+testAbsolutePath+"/foo", "https", "foo.com")
inBody, outBody := map[string]interface{}{"name": "bar"}, `{"name":"bar"}`+"\n"

req, _ := c.NewRequest(context.TODO(), http.MethodPost, inURL, inBody)
Expand Down
51 changes: 51 additions & 0 deletions client/foundation/foundation_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package foundation

import (
"fmt"

"github.com/terraform-providers/terraform-provider-nutanix/client"
)

const (
absolutePath = "foundation"
userAgent = "foundation"
)

//Foundation client with its services
type Client struct {

//base client
client *client.Client

//Service for Imaging Nodes and Cluster Creation
NodeImaging NodeImagingService

//Service for File Management in foundation VM
FileManagement FileManagementService
}

//This routine returns new Foundation API Client
func NewFoundationAPIClient(credentials client.Credentials) (*Client, error) {

//for foundation client, url should be based on foundation's endpoint and port
credentials.URL = fmt.Sprintf("%s:%s", credentials.FoundationEndpoint, credentials.FoundationPort)
client, err := client.NewBaseClient(&credentials, absolutePath, true)

if err != nil {
return nil, err
}

//Fill user agent details
client.UserAgent = userAgent

foundationClient := &Client{
client: client,
NodeImaging: NodeImagingOperations{
client: client,
},
FileManagement: FileManagementOperations{
client: client,
},
}
return foundationClient, nil
}
39 changes: 39 additions & 0 deletions client/foundation/foundation_file_management_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package foundation

import (
"context"
"net/http"

"github.com/terraform-providers/terraform-provider-nutanix/client"
)

type FileManagementService interface {
ListNOSPackages(context.Context) (*ListNOSPackagesResponse, error)
ListHypervisorISOs(context.Context) (*ListHypervisorISOsResponse, error)
}

type FileManagementOperations struct {
client *client.Client
}

//Lists the available AOS packages in Foundation
func (fmo FileManagementOperations) ListNOSPackages(ctx context.Context) (*ListNOSPackagesResponse, error) {
path := "/enumerate_nos_packages"
req, err := fmo.client.NewUnAuthRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, err
}
listNOSPackagesResponse := new(ListNOSPackagesResponse)
return listNOSPackagesResponse, fmo.client.Do(ctx, req, listNOSPackagesResponse)
}

//Lists the hypervisor ISOs available in Foundation
func (fmo FileManagementOperations) ListHypervisorISOs(ctx context.Context) (*ListHypervisorISOsResponse, error) {
path := "/enumerate_hypervisor_isos"
req, err := fmo.client.NewUnAuthRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, err
}
listHypervisorISOsResponse := new(ListHypervisorISOsResponse)
return listHypervisorISOsResponse, fmo.client.Do(ctx, req, listHypervisorISOsResponse)
}
39 changes: 39 additions & 0 deletions client/foundation/foundation_node_imaging_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package foundation

import (
"context"
"net/http"

"github.com/terraform-providers/terraform-provider-nutanix/client"
)

type NodeImagingService interface {
ImageNodes(context.Context, *ImageNodesInput) (*ImageNodesAPIResponse, error)
ImageNodesProgress(context.Context, string) (*ImageNodesProgressResponse, error)
}

type NodeImagingOperations struct {
client *client.Client
}

func (op NodeImagingOperations) ImageNodes(ctx context.Context, imageNodeInput *ImageNodesInput) (*ImageNodesAPIResponse, error) {
path := "/image_nodes"
req, err := op.client.NewUnAuthRequest(ctx, http.MethodPost, path, imageNodeInput)
if err != nil {
return nil, err
}

imageNodesAPIResponse := new(ImageNodesAPIResponse)
return imageNodesAPIResponse, op.client.Do(ctx, req, imageNodesAPIResponse)
}

//Gets progress of imaging session.
func (op NodeImagingOperations) ImageNodesProgress(ctx context.Context, session_id string) (*ImageNodesProgressResponse, error) {
path := "/progress?session_id=" + session_id
req, err := op.client.NewUnAuthRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, err
}
imageNodesProgressResponse := new(ImageNodesProgressResponse)
return imageNodesProgressResponse, op.client.Do(ctx, req, imageNodesProgressResponse)
}
Loading