github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/apmcloudutil/gcp.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apmcloudutil
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"path"
    27  	"strconv"
    28  	"strings"
    29  
    30  	"github.com/waldiirawan/apm-agent-go/v2/model"
    31  )
    32  
    33  const (
    34  	gcpMetadataURL = "http://metadata.google.internal/computeMetadata/v1/?recursive=true"
    35  )
    36  
    37  // See: https://cloud.google.com/compute/docs/storing-retrieving-metadata
    38  func getGCPCloudMetadata(ctx context.Context, client *http.Client, out *model.Cloud) error {
    39  	req, err := http.NewRequest("GET", gcpMetadataURL, nil)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	req.Header.Set("Metadata-Flavor", "Google")
    44  
    45  	resp, err := client.Do(req.WithContext(ctx))
    46  	if err != nil {
    47  		return err
    48  	}
    49  	defer resp.Body.Close()
    50  	if resp.StatusCode != http.StatusOK {
    51  		return errors.New(resp.Status)
    52  	}
    53  
    54  	var gcpMetadata struct {
    55  		Instance struct {
    56  			// ID may be an integer or a hex string.
    57  			ID          interface{} `json:"id"`
    58  			MachineType string      `json:"machineType"`
    59  			Name        string      `json:"name"`
    60  			Zone        string      `json:"zone"`
    61  		} `json:"instance"`
    62  		Project struct {
    63  			NumericProjectID *int   `json:"numericProjectId"`
    64  			ProjectID        string `json:"projectId"`
    65  		} `json:"project"`
    66  	}
    67  	decoder := json.NewDecoder(resp.Body)
    68  	decoder.UseNumber()
    69  	if err := decoder.Decode(&gcpMetadata); err != nil {
    70  		return err
    71  	}
    72  
    73  	out.Region, out.AvailabilityZone = splitGCPZone(gcpMetadata.Instance.Zone)
    74  	if gcpMetadata.Instance.ID != nil || gcpMetadata.Instance.Name != "" {
    75  		out.Instance = &model.CloudInstance{
    76  			Name: gcpMetadata.Instance.Name,
    77  		}
    78  		if gcpMetadata.Instance.ID != nil {
    79  			out.Instance.ID = fmt.Sprint(gcpMetadata.Instance.ID)
    80  		}
    81  	}
    82  	if gcpMetadata.Instance.MachineType != "" {
    83  		out.Machine = &model.CloudMachine{Type: splitGCPMachineType(gcpMetadata.Instance.MachineType)}
    84  	}
    85  	if gcpMetadata.Project.NumericProjectID != nil || gcpMetadata.Project.ProjectID != "" {
    86  		out.Project = &model.CloudProject{Name: gcpMetadata.Project.ProjectID}
    87  		if gcpMetadata.Project.NumericProjectID != nil {
    88  			out.Project.ID = strconv.Itoa(*gcpMetadata.Project.NumericProjectID)
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  func splitGCPZone(s string) (region, zone string) {
    95  	// Format: "projects/projectnum/zones/zone"
    96  	zone = path.Base(s)
    97  	if sep := strings.LastIndex(zone, "-"); sep != -1 {
    98  		region = zone[:sep]
    99  	}
   100  	return region, zone
   101  }
   102  
   103  func splitGCPMachineType(s string) string {
   104  	// Format: projects/513326162531/machineTypes/n1-standard-1
   105  	return path.Base(s)
   106  }