// SPDX-License-Identifier: GPL-3.0-or-later package client import ( "encoding/json" "fmt" "io" "net/http" "sync" "github.com/netdata/netdata/go/go.d.plugin/pkg/web" ) // Session: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.cis.session // Health: https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.appliance.health const ( pathCISSession = "/rest/com/vmware/cis/session" pathHealthSystem = "/rest/appliance/health/system" pathHealthSwap = "/rest/appliance/health/swap" pathHealthStorage = "/rest/appliance/health/storage" pathHealthSoftwarePackager = "/rest/appliance/health/software-packages" pathHealthMem = "/rest/appliance/health/mem" pathHealthLoad = "/rest/appliance/health/load" pathHealthDatabaseStorage = "/rest/appliance/health/database-storage" pathHealthApplMgmt = "/rest/appliance/health/applmgmt" apiSessIDKey = "vmware-api-session-id" ) type sessionToken struct { m *sync.RWMutex id string } func (s *sessionToken) set(id string) { s.m.Lock() defer s.m.Unlock() s.id = id } func (s *sessionToken) get() string { s.m.RLock() defer s.m.RUnlock() return s.id } func New(httpClient *http.Client, url, username, password string) *Client { if httpClient == nil { httpClient = &http.Client{} } return &Client{ httpClient: httpClient, url: url, username: username, password: password, token: &sessionToken{m: new(sync.RWMutex)}, } } type Client struct { httpClient *http.Client url string username string password string token *sessionToken } // Login creates a session with the API. This operation exchanges user credentials supplied in the security context // for a session identifier that is to be used for authenticating subsequent calls. func (c *Client) Login() error { req := web.Request{ URL: fmt.Sprintf("%s%s", c.url, pathCISSession), Username: c.username, Password: c.password, Method: http.MethodPost, } s := struct{ Value string }{} err := c.doOKWithDecode(req, &s) if err == nil { c.token.set(s.Value) } return err } // Logout terminates the validity of a session token. func (c *Client) Logout() error { req := web.Request{ URL: fmt.Sprintf("%s%s", c.url, pathCISSession), Method: http.MethodDelete, Headers: map[string]string{apiSessIDKey: c.token.get()}, } resp, err := c.doOK(req) closeBody(resp) c.token.set("") return err } // Ping sent a request to VCSA server to ensure the link is operating. // In case of 401 error Ping tries to re authenticate. func (c *Client) Ping() error { req := web.Request{ URL: fmt.Sprintf("%s%s?~action=get", c.url, pathCISSession), Method: http.MethodPost, Headers: map[string]string{apiSessIDKey: c.token.get()}, } resp, err := c.doOK(req) defer closeBody(resp) if resp != nil && resp.StatusCode == http.StatusUnauthorized { return c.Login() } return err } func (c *Client) health(urlPath string) (string, error) { req := web.Request{ URL: fmt.Sprintf("%s%s", c.url, urlPath), Headers: map[string]string{apiSessIDKey: c.token.get()}, } s := struct{ Value string }{} err := c.doOKWithDecode(req, &s) return s.Value, err } // ApplMgmt provides health status of applmgmt services. func (c *Client) ApplMgmt() (string, error) { return c.health(pathHealthApplMgmt) } // DatabaseStorage provides health status of database storage health. func (c *Client) DatabaseStorage() (string, error) { return c.health(pathHealthDatabaseStorage) } // Load provides health status of load health. func (c *Client) Load() (string, error) { return c.health(pathHealthLoad) } // Mem provides health status of memory health. func (c *Client) Mem() (string, error) { return c.health(pathHealthMem) } // SoftwarePackages provides information on available software updates available in remote VUM repository. // Red indicates that security updates are available. // Orange indicates that non-security updates are available. // Green indicates that there are no updates available. // Gray indicates that there was an error retrieving information on software updates. func (c *Client) SoftwarePackages() (string, error) { return c.health(pathHealthSoftwarePackager) } // Storage provides health status of storage health. func (c *Client) Storage() (string, error) { return c.health(pathHealthStorage) } // Swap provides health status of swap health. func (c *Client) Swap() (string, error) { return c.health(pathHealthSwap) } // System provides overall health of system. func (c *Client) System() (string, error) { return c.health(pathHealthSystem) } func (c *Client) do(req web.Request) (*http.Response, error) { httpReq, err := web.NewHTTPRequest(req) if err != nil { return nil, fmt.Errorf("error on creating http request to %s : %v", req.URL, err) } return c.httpClient.Do(httpReq) } func (c *Client) doOK(req web.Request) (*http.Response, error) { resp, err := c.do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return resp, fmt.Errorf("%s returned %d", req.URL, resp.StatusCode) } return resp, nil } func (c *Client) doOKWithDecode(req web.Request, dst interface{}) error { resp, err := c.doOK(req) defer closeBody(resp) if err != nil { return err } err = json.NewDecoder(resp.Body).Decode(dst) if err != nil { return fmt.Errorf("error on decoding response from %s : %v", req.URL, err) } return nil } func closeBody(resp *http.Response) { if resp != nil && resp.Body != nil { _, _ = io.Copy(io.Discard, resp.Body) _ = resp.Body.Close() } }