github.com/google/cadvisor@v0.49.1/client/client.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // This is an implementation of a cAdvisor REST API in Go.
    16  // To use it, create a client (replace the URL with your actual cAdvisor REST endpoint):
    17  //
    18  //	client, err := client.NewClient("http://192.168.59.103:8080/")
    19  //
    20  // Then, the client interface exposes go methods corresponding to the REST endpoints.
    21  package client
    22  
    23  import (
    24  	"bytes"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io"
    28  	"net/http"
    29  	"path"
    30  	"strings"
    31  
    32  	v1 "github.com/google/cadvisor/info/v1"
    33  
    34  	"k8s.io/klog/v2"
    35  )
    36  
    37  // Client represents the base URL for a cAdvisor client.
    38  type Client struct {
    39  	baseURL    string
    40  	httpClient *http.Client
    41  }
    42  
    43  // NewClient returns a new v1.3 client with the specified base URL.
    44  func NewClient(url string) (*Client, error) {
    45  	return newClient(url, http.DefaultClient)
    46  }
    47  
    48  func newClient(url string, client *http.Client) (*Client, error) {
    49  	if !strings.HasSuffix(url, "/") {
    50  		url += "/"
    51  	}
    52  
    53  	return &Client{
    54  		baseURL:    fmt.Sprintf("%sapi/v1.3/", url),
    55  		httpClient: client,
    56  	}, nil
    57  }
    58  
    59  // Returns all past events that satisfy the request
    60  func (c *Client) EventStaticInfo(name string) (einfo []*v1.Event, err error) {
    61  	u := c.eventsInfoURL(name)
    62  	ret := new([]*v1.Event)
    63  	if err = c.httpGetJSONData(ret, nil, u, "event info"); err != nil {
    64  		return
    65  	}
    66  	einfo = *ret
    67  	return
    68  }
    69  
    70  // Streams all events that occur that satisfy the request into the channel
    71  // that is passed
    72  func (c *Client) EventStreamingInfo(name string, einfo chan *v1.Event) (err error) {
    73  	u := c.eventsInfoURL(name)
    74  	if err = c.getEventStreamingData(u, einfo); err != nil {
    75  		return
    76  	}
    77  	return nil
    78  }
    79  
    80  // MachineInfo returns the JSON machine information for this client.
    81  // A non-nil error result indicates a problem with obtaining
    82  // the JSON machine information data.
    83  func (c *Client) MachineInfo() (minfo *v1.MachineInfo, err error) {
    84  	u := c.machineInfoURL()
    85  	ret := new(v1.MachineInfo)
    86  	if err = c.httpGetJSONData(ret, nil, u, "machine info"); err != nil {
    87  		return
    88  	}
    89  	minfo = ret
    90  	return
    91  }
    92  
    93  // ContainerInfo returns the JSON container information for the specified
    94  // container and request.
    95  func (c *Client) ContainerInfo(name string, query *v1.ContainerInfoRequest) (cinfo *v1.ContainerInfo, err error) {
    96  	u := c.containerInfoURL(name)
    97  	ret := new(v1.ContainerInfo)
    98  	if err = c.httpGetJSONData(ret, query, u, fmt.Sprintf("container info for %q", name)); err != nil {
    99  		return
   100  	}
   101  	cinfo = ret
   102  	return
   103  }
   104  
   105  // Returns the information about all subcontainers (recursive) of the specified container (including itself).
   106  func (c *Client) SubcontainersInfo(name string, query *v1.ContainerInfoRequest) ([]v1.ContainerInfo, error) {
   107  	var response []v1.ContainerInfo
   108  	url := c.subcontainersInfoURL(name)
   109  	err := c.httpGetJSONData(&response, query, url, fmt.Sprintf("subcontainers container info for %q", name))
   110  	if err != nil {
   111  		return []v1.ContainerInfo{}, err
   112  
   113  	}
   114  	return response, nil
   115  }
   116  
   117  // Returns the JSON container information for the specified
   118  // Docker container and request.
   119  func (c *Client) DockerContainer(name string, query *v1.ContainerInfoRequest) (cinfo v1.ContainerInfo, err error) {
   120  	u := c.dockerInfoURL(name)
   121  	ret := make(map[string]v1.ContainerInfo)
   122  	if err = c.httpGetJSONData(&ret, query, u, fmt.Sprintf("Docker container info for %q", name)); err != nil {
   123  		return
   124  	}
   125  	if len(ret) != 1 {
   126  		err = fmt.Errorf("expected to only receive 1 Docker container: %+v", ret)
   127  		return
   128  	}
   129  	for _, cont := range ret {
   130  		cinfo = cont
   131  	}
   132  	return
   133  }
   134  
   135  // Returns the JSON container information for all Docker containers.
   136  func (c *Client) AllDockerContainers(query *v1.ContainerInfoRequest) (cinfo []v1.ContainerInfo, err error) {
   137  	u := c.dockerInfoURL("/")
   138  	ret := make(map[string]v1.ContainerInfo)
   139  	if err = c.httpGetJSONData(&ret, query, u, "all Docker containers info"); err != nil {
   140  		return
   141  	}
   142  	cinfo = make([]v1.ContainerInfo, 0, len(ret))
   143  	for _, cont := range ret {
   144  		cinfo = append(cinfo, cont)
   145  	}
   146  	return
   147  }
   148  
   149  func (c *Client) machineInfoURL() string {
   150  	return c.baseURL + path.Join("machine")
   151  }
   152  
   153  func (c *Client) containerInfoURL(name string) string {
   154  	return c.baseURL + path.Join("containers", name)
   155  }
   156  
   157  func (c *Client) subcontainersInfoURL(name string) string {
   158  	return c.baseURL + path.Join("subcontainers", name)
   159  }
   160  
   161  func (c *Client) dockerInfoURL(name string) string {
   162  	return c.baseURL + path.Join("docker", name)
   163  }
   164  
   165  func (c *Client) eventsInfoURL(name string) string {
   166  	return c.baseURL + path.Join("events", name)
   167  }
   168  
   169  func (c *Client) httpGetJSONData(data, postData interface{}, url, infoName string) error {
   170  	var resp *http.Response
   171  	var err error
   172  
   173  	if postData != nil {
   174  		data, marshalErr := json.Marshal(postData)
   175  		if marshalErr != nil {
   176  			return fmt.Errorf("unable to marshal data: %v", marshalErr)
   177  		}
   178  		resp, err = c.httpClient.Post(url, "application/json", bytes.NewBuffer(data))
   179  	} else {
   180  		resp, err = c.httpClient.Get(url)
   181  	}
   182  	if err != nil {
   183  		return fmt.Errorf("unable to get %q from %q: %v", infoName, url, err)
   184  	}
   185  	if resp == nil {
   186  		return fmt.Errorf("received empty response for %q from %q", infoName, url)
   187  	}
   188  	defer resp.Body.Close()
   189  	body, err := io.ReadAll(resp.Body)
   190  	if err != nil {
   191  		err = fmt.Errorf("unable to read all %q from %q: %v", infoName, url, err)
   192  		return err
   193  	}
   194  	if resp.StatusCode != 200 {
   195  		return fmt.Errorf("request %q failed with error: %q", url, strings.TrimSpace(string(body)))
   196  	}
   197  	if err = json.Unmarshal(body, data); err != nil {
   198  		err = fmt.Errorf("unable to unmarshal %q (Body: %q) from %q with error: %v", infoName, string(body), url, err)
   199  		return err
   200  	}
   201  	return nil
   202  }
   203  
   204  func (c *Client) getEventStreamingData(url string, einfo chan *v1.Event) error {
   205  	req, err := http.NewRequest("GET", url, nil)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	resp, err := c.httpClient.Do(req)
   210  	if err != nil {
   211  		return err
   212  	}
   213  	if resp.StatusCode != http.StatusOK {
   214  		return fmt.Errorf("Status code is not OK: %v (%s)", resp.StatusCode, resp.Status)
   215  	}
   216  
   217  	dec := json.NewDecoder(resp.Body)
   218  	var m *v1.Event = &v1.Event{}
   219  	for {
   220  		err := dec.Decode(m)
   221  		if err != nil {
   222  			if err == io.EOF {
   223  				break
   224  			}
   225  			// if called without &stream=true will not be able to parse event and will trigger fatal
   226  			klog.Fatalf("Received error %v", err)
   227  		}
   228  		einfo <- m
   229  	}
   230  	return nil
   231  }