2017-01-31 12:11:06 +01:00
|
|
|
package govcloudair
|
2016-01-29 20:53:56 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type VCDClient struct {
|
|
|
|
OrgHREF url.URL // vCloud Director OrgRef
|
|
|
|
Org Org // Org
|
|
|
|
OrgVdc Vdc // Org vDC
|
|
|
|
Client Client // Client for the underlying VCD instance
|
|
|
|
sessionHREF url.URL // HREF for the session API
|
2017-01-20 02:10:17 +01:00
|
|
|
QueryHREF url.URL // HREF for the query API
|
2016-01-29 20:53:56 +01:00
|
|
|
Mutex sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
type supportedVersions struct {
|
|
|
|
VersionInfo struct {
|
|
|
|
Version string `xml:"Version"`
|
|
|
|
LoginUrl string `xml:"LoginUrl"`
|
|
|
|
} `xml:"VersionInfo"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *VCDClient) vcdloginurl() error {
|
|
|
|
|
|
|
|
s := c.Client.VCDVDCHREF
|
|
|
|
s.Path += "/versions"
|
|
|
|
|
|
|
|
// No point in checking for errors here
|
|
|
|
req := c.Client.NewRequest(map[string]string{}, "GET", s, nil)
|
|
|
|
|
|
|
|
resp, err := checkResp(c.Client.Http.Do(req))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
supportedVersions := new(supportedVersions)
|
|
|
|
|
|
|
|
err = decodeBody(resp, supportedVersions)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error decoding versions response: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(supportedVersions.VersionInfo.LoginUrl)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't find a LoginUrl in versions")
|
|
|
|
}
|
|
|
|
c.sessionHREF = *u
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *VCDClient) vcdauthorize(user, pass, org string) error {
|
|
|
|
|
|
|
|
if user == "" {
|
|
|
|
user = os.Getenv("VCLOUD_USERNAME")
|
|
|
|
}
|
|
|
|
|
|
|
|
if pass == "" {
|
|
|
|
pass = os.Getenv("VCLOUD_PASSWORD")
|
|
|
|
}
|
|
|
|
|
|
|
|
if org == "" {
|
|
|
|
org = os.Getenv("VCLOUD_ORG")
|
|
|
|
}
|
|
|
|
|
|
|
|
// No point in checking for errors here
|
|
|
|
req := c.Client.NewRequest(map[string]string{}, "POST", c.sessionHREF, nil)
|
|
|
|
|
|
|
|
// Set Basic Authentication Header
|
|
|
|
req.SetBasicAuth(user+"@"+org, pass)
|
|
|
|
|
|
|
|
// Add the Accept header for vCA
|
|
|
|
req.Header.Add("Accept", "application/*+xml;version=5.5")
|
|
|
|
|
|
|
|
resp, err := checkResp(c.Client.Http.Do(req))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
// Store the authentication header
|
|
|
|
c.Client.VCDToken = resp.Header.Get("x-vcloud-authorization")
|
|
|
|
c.Client.VCDAuthHeader = "x-vcloud-authorization"
|
|
|
|
|
|
|
|
session := new(session)
|
|
|
|
err = decodeBody(resp, session)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Errorf("error decoding session response: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
org_found := false
|
2017-01-20 02:10:17 +01:00
|
|
|
// Loop in the session struct to find the organization and query api.
|
2016-01-29 20:53:56 +01:00
|
|
|
for _, s := range session.Link {
|
|
|
|
if s.Type == "application/vnd.vmware.vcloud.org+xml" && s.Rel == "down" {
|
|
|
|
u, err := url.Parse(s.HREF)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't find a Organization in current session, %v", err)
|
|
|
|
}
|
|
|
|
c.OrgHREF = *u
|
|
|
|
org_found = true
|
|
|
|
}
|
2017-01-20 02:10:17 +01:00
|
|
|
if s.Type == "application/vnd.vmware.vcloud.query.queryList+xml" && s.Rel == "down" {
|
|
|
|
u, err := url.Parse(s.HREF)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't find a Query API in current session, %v", err)
|
|
|
|
}
|
|
|
|
c.QueryHREF = *u
|
|
|
|
}
|
2016-01-29 20:53:56 +01:00
|
|
|
}
|
|
|
|
if !org_found {
|
|
|
|
return fmt.Errorf("couldn't find a Organization in current session")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Loop in the session struct to find the session url.
|
|
|
|
session_found := false
|
|
|
|
for _, s := range session.Link {
|
|
|
|
if s.Rel == "remove" {
|
|
|
|
u, err := url.Parse(s.HREF)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't find a logout HREF in current session, %v", err)
|
|
|
|
}
|
|
|
|
c.sessionHREF = *u
|
|
|
|
session_found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !session_found {
|
|
|
|
return fmt.Errorf("couldn't find a logout HREF in current session")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *VCDClient) RetrieveOrg(vcdname string) (Org, error) {
|
|
|
|
|
|
|
|
req := c.Client.NewRequest(map[string]string{}, "GET", c.OrgHREF, nil)
|
|
|
|
req.Header.Add("Accept", "vnd.vmware.vcloud.org+xml;version=5.5")
|
|
|
|
|
|
|
|
// TODO: wrap into checkresp to parse error
|
|
|
|
resp, err := checkResp(c.Client.Http.Do(req))
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, fmt.Errorf("error retreiving org: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
org := NewOrg(&c.Client)
|
|
|
|
|
|
|
|
if err = decodeBody(resp, org.Org); err != nil {
|
|
|
|
return Org{}, fmt.Errorf("error decoding org response: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the VDC ref from the Org
|
|
|
|
for _, s := range org.Org.Link {
|
|
|
|
if s.Type == "application/vnd.vmware.vcloud.vdc+xml" && s.Rel == "down" {
|
|
|
|
if vcdname != "" && s.Name != vcdname {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
u, err := url.Parse(s.HREF)
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, err
|
|
|
|
}
|
|
|
|
c.Client.VCDVDCHREF = *u
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if &c.Client.VCDVDCHREF == nil {
|
|
|
|
return Org{}, fmt.Errorf("error finding the organization VDC HREF")
|
|
|
|
}
|
|
|
|
|
|
|
|
return *org, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewVCDClient(vcdEndpoint url.URL, insecure bool) *VCDClient {
|
|
|
|
|
|
|
|
return &VCDClient{
|
|
|
|
Client: Client{
|
|
|
|
APIVersion: "5.5",
|
|
|
|
VCDVDCHREF: vcdEndpoint,
|
|
|
|
Http: http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
InsecureSkipVerify: insecure,
|
|
|
|
},
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
TLSHandshakeTimeout: 120 * time.Second,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Authenticate is an helper function that performs a login in vCloud Director.
|
|
|
|
func (c *VCDClient) Authenticate(username, password, org, vdcname string) (Org, Vdc, error) {
|
|
|
|
|
|
|
|
// LoginUrl
|
|
|
|
err := c.vcdloginurl()
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, Vdc{}, fmt.Errorf("error finding LoginUrl: %s", err)
|
|
|
|
}
|
|
|
|
// Authorize
|
|
|
|
err = c.vcdauthorize(username, password, org)
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, Vdc{}, fmt.Errorf("error authorizing: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Org
|
|
|
|
o, err := c.RetrieveOrg(vdcname)
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, Vdc{}, fmt.Errorf("error acquiring Org: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
vdc, err := c.Client.retrieveVDC()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return Org{}, Vdc{}, fmt.Errorf("error retrieving the organization VDC")
|
|
|
|
}
|
|
|
|
|
|
|
|
return o, vdc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disconnect performs a disconnection from the vCloud Director API endpoint.
|
|
|
|
func (c *VCDClient) Disconnect() error {
|
|
|
|
if c.Client.VCDToken == "" && c.Client.VCDAuthHeader == "" {
|
|
|
|
return fmt.Errorf("cannot disconnect, client is not authenticated")
|
|
|
|
}
|
|
|
|
|
|
|
|
req := c.Client.NewRequest(map[string]string{}, "DELETE", c.sessionHREF, nil)
|
|
|
|
|
|
|
|
// Add the Accept header for vCA
|
|
|
|
req.Header.Add("Accept", "application/xml;version=5.5")
|
|
|
|
|
|
|
|
// Set Authorization Header
|
|
|
|
req.Header.Add(c.Client.VCDAuthHeader, c.Client.VCDToken)
|
|
|
|
|
|
|
|
if _, err := checkResp(c.Client.Http.Do(req)); err != nil {
|
|
|
|
return fmt.Errorf("error processing session delete for vCloud Director: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|