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 }