github.com/waldiirawan/apm-agent-go/v2@v2.2.2/internal/apmcloudutil/azure.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 "net/http" 25 "os" 26 "strings" 27 28 "github.com/waldiirawan/apm-agent-go/v2/model" 29 ) 30 31 const ( 32 azureMetadataURL = "http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15" 33 ) 34 35 // See: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service 36 func getAzureCloudMetadata(ctx context.Context, client *http.Client, out *model.Cloud) error { 37 // First check for Azure App Service environment variables, which can 38 // be done without performing any network requests. 39 if getAzureAppServiceCloudMetadata(ctx, out) { 40 return nil 41 } 42 43 req, err := http.NewRequest("GET", azureMetadataURL, nil) 44 if err != nil { 45 return err 46 } 47 req.Header.Set("Metadata", "true") 48 49 resp, err := client.Do(req.WithContext(ctx)) 50 if err != nil { 51 return err 52 } 53 defer resp.Body.Close() 54 if resp.StatusCode != http.StatusOK { 55 return errors.New(resp.Status) 56 } 57 58 var azureMetadata struct { 59 Location string `json:"location"` 60 Name string `json:"name"` 61 ResourceGroupName string `json:"resourceGroupName"` 62 SubscriptionID string `json:"subscriptionId"` 63 VMID string `json:"vmId"` 64 VMSize string `json:"vmSize"` 65 Zone string `json:"zone"` 66 } 67 if err := json.NewDecoder(resp.Body).Decode(&azureMetadata); err != nil { 68 return err 69 } 70 71 out.Region = azureMetadata.Location 72 out.AvailabilityZone = azureMetadata.Zone 73 if azureMetadata.VMID != "" || azureMetadata.Name != "" { 74 out.Instance = &model.CloudInstance{ID: azureMetadata.VMID, Name: azureMetadata.Name} 75 } 76 if azureMetadata.VMSize != "" { 77 out.Machine = &model.CloudMachine{Type: azureMetadata.VMSize} 78 } 79 if azureMetadata.ResourceGroupName != "" { 80 out.Project = &model.CloudProject{Name: azureMetadata.ResourceGroupName} 81 } 82 if azureMetadata.SubscriptionID != "" { 83 out.Account = &model.CloudAccount{ID: azureMetadata.SubscriptionID} 84 } 85 return nil 86 } 87 88 func getAzureAppServiceCloudMetadata(ctx context.Context, out *model.Cloud) bool { 89 // WEBSITE_OWNER_NAME has the form: 90 // {subscription id}+{app service plan resource group}-{region}webspace{.*} 91 websiteOwnerName := os.Getenv("WEBSITE_OWNER_NAME") 92 if websiteOwnerName == "" { 93 return false 94 } 95 websiteInstanceID := os.Getenv("WEBSITE_INSTANCE_ID") 96 if websiteInstanceID == "" { 97 return false 98 } 99 websiteSiteName := os.Getenv("WEBSITE_SITE_NAME") 100 if websiteSiteName == "" { 101 return false 102 } 103 websiteResourceGroup := os.Getenv("WEBSITE_RESOURCE_GROUP") 104 if websiteResourceGroup == "" { 105 return false 106 } 107 108 plus := strings.IndexRune(websiteOwnerName, '+') 109 if plus == -1 { 110 return false 111 } 112 out.Account = &model.CloudAccount{ID: websiteOwnerName[:plus]} 113 websiteOwnerName = websiteOwnerName[plus+1:] 114 115 webspace := strings.LastIndex(websiteOwnerName, "webspace") 116 if webspace == -1 { 117 return false 118 } 119 websiteOwnerName = websiteOwnerName[:webspace] 120 121 hyphen := strings.LastIndex(websiteOwnerName, "-") 122 if hyphen == -1 { 123 return false 124 } 125 out.Region = websiteOwnerName[hyphen+1:] 126 out.Instance = &model.CloudInstance{ID: websiteInstanceID, Name: websiteSiteName} 127 out.Project = &model.CloudProject{Name: websiteResourceGroup} 128 return true 129 }