123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- package utilization
- import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
- )
- const (
- gcpHostname = "metadata.google.internal"
- gcpEndpointPath = "/computeMetadata/v1/instance/?recursive=true"
- gcpEndpoint = "http://" + gcpHostname + gcpEndpointPath
- )
- func gatherGCP(util *Data, client *http.Client) error {
- gcp, err := getGCP(client)
- if err != nil {
- // Only return the error here if it is unexpected to prevent
- // warning customers who aren't running GCP about a timeout.
- if _, ok := err.(unexpectedGCPErr); ok {
- return err
- }
- return nil
- }
- util.Vendors.GCP = gcp
- return nil
- }
- // numericString is used rather than json.Number because we want the output when
- // marshalled to be a string, rather than a number.
- type numericString string
- func (ns *numericString) MarshalJSON() ([]byte, error) {
- return json.Marshal(ns.String())
- }
- func (ns *numericString) String() string {
- return string(*ns)
- }
- func (ns *numericString) UnmarshalJSON(data []byte) error {
- var n int64
- // Try to unmarshal as an integer first.
- if err := json.Unmarshal(data, &n); err == nil {
- *ns = numericString(fmt.Sprintf("%d", n))
- return nil
- }
- // Otherwise, unmarshal as a string, and verify that it's numeric (for our
- // definition of numeric, which is actually integral).
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- for _, r := range s {
- if r < '0' || r > '9' {
- return fmt.Errorf("invalid numeric character: %c", r)
- }
- }
- *ns = numericString(s)
- return nil
- }
- type gcp struct {
- ID numericString `json:"id"`
- MachineType string `json:"machineType,omitempty"`
- Name string `json:"name,omitempty"`
- Zone string `json:"zone,omitempty"`
- }
- type unexpectedGCPErr struct{ e error }
- func (e unexpectedGCPErr) Error() string {
- return fmt.Sprintf("unexpected GCP error: %v", e.e)
- }
- func getGCP(client *http.Client) (*gcp, error) {
- // GCP's metadata service requires a Metadata-Flavor header because... hell, I
- // don't know, maybe they really like Guy Fieri?
- req, err := http.NewRequest("GET", gcpEndpoint, nil)
- if err != nil {
- return nil, err
- }
- req.Header.Add("Metadata-Flavor", "Google")
- response, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer response.Body.Close()
- if response.StatusCode != 200 {
- return nil, unexpectedGCPErr{e: fmt.Errorf("response code %d", response.StatusCode)}
- }
- data, err := ioutil.ReadAll(response.Body)
- if err != nil {
- return nil, unexpectedGCPErr{e: err}
- }
- g := &gcp{}
- if err := json.Unmarshal(data, g); err != nil {
- return nil, unexpectedGCPErr{e: err}
- }
- if err := g.validate(); err != nil {
- return nil, unexpectedGCPErr{e: err}
- }
- return g, nil
- }
- func (g *gcp) validate() (err error) {
- id, err := normalizeValue(g.ID.String())
- if err != nil {
- return fmt.Errorf("Invalid ID: %v", err)
- }
- g.ID = numericString(id)
- mt, err := normalizeValue(g.MachineType)
- if err != nil {
- return fmt.Errorf("Invalid machine type: %v", err)
- }
- g.MachineType = stripGCPPrefix(mt)
- g.Name, err = normalizeValue(g.Name)
- if err != nil {
- return fmt.Errorf("Invalid name: %v", err)
- }
- zone, err := normalizeValue(g.Zone)
- if err != nil {
- return fmt.Errorf("Invalid zone: %v", err)
- }
- g.Zone = stripGCPPrefix(zone)
- return
- }
- // We're only interested in the last element of slash separated paths for the
- // machine type and zone values, so this function handles stripping the parts
- // we don't need.
- func stripGCPPrefix(s string) string {
- parts := strings.Split(s, "/")
- return parts[len(parts)-1]
- }
|