Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apmtest/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package apmtest // import "go.elastic.co/apm/apmtest"
import "os"

func init() {
// Disable cloud metadata sniffing by default in tests.
os.Setenv("ELASTIC_APM_CLOUD_PROVIDER", "none")
if os.Getenv("ELASTIC_APM_CLOUD_PROVIDER") == "" {
// Disable cloud metadata sniffing by default in tests.
os.Setenv("ELASTIC_APM_CLOUD_PROVIDER", "none")
}
}
71 changes: 71 additions & 0 deletions features/azure_app_service_metadata.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Feature: Extracting Metadata for Azure App Service

Background:
Given an instrumented application is configured to collect cloud provider metadata for azure

Scenario Outline: Azure App Service with all environment variables present in expected format
Given the following environment variables are present
| name | value |
| WEBSITE_OWNER_NAME | <WEBSITE_OWNER_NAME> |
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_SITE_NAME | site_name |
| WEBSITE_INSTANCE_ID | instance_id |
When cloud metadata is collected
Then cloud metadata is not null
And cloud metadata 'account.id' is 'f5940f10-2e30-3e4d-a259-63451ba6dae4'
And cloud metadata 'provider' is 'azure'
And cloud metadata 'instance.id' is 'instance_id'
And cloud metadata 'instance.name' is 'site_name'
And cloud metadata 'project.name' is 'resource_group'
And cloud metadata 'region' is 'AustraliaEast'
Examples:
| WEBSITE_OWNER_NAME |
| f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace |
| f5940f10-2e30-3e4d-a259-63451ba6dae4+appsvc_linux_australiaeast-AustraliaEastwebspace-Linux |

# WEBSITE_OWNER_NAME is expected to include a + character
Scenario: WEBSITE_OWNER_NAME environment variable not expected format
Given the following environment variables are present
| name | value |
| WEBSITE_OWNER_NAME | f5940f10-2e30-3e4d-a259-63451ba6dae4-elastic-apm-AustraliaEastwebspace |
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_SITE_NAME | site_name |
| WEBSITE_INSTANCE_ID | instance_id |
When cloud metadata is collected
Then cloud metadata is null

Scenario: Missing WEBSITE_OWNER_NAME environment variable
Given the following environment variables are present
| name | value |
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_SITE_NAME | site_name |
| WEBSITE_INSTANCE_ID | instance_id |
When cloud metadata is collected
Then cloud metadata is null

Scenario: Missing WEBSITE_RESOURCE_GROUP environment variable
Given the following environment variables are present
| name | value |
| WEBSITE_OWNER_NAME | f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace |
| WEBSITE_SITE_NAME | site_name |
| WEBSITE_INSTANCE_ID | instance_id |
When cloud metadata is collected
Then cloud metadata is null

Scenario: Missing WEBSITE_SITE_NAME environment variable
Given the following environment variables are present
| name | value |
| WEBSITE_OWNER_NAME | f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace |
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_INSTANCE_ID | instance_id |
When cloud metadata is collected
Then cloud metadata is null

Scenario: Missing WEBSITE_INSTANCE_ID environment variable
Given the following environment variables are present
| name | value |
| WEBSITE_OWNER_NAME | f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace |
| WEBSITE_RESOURCE_GROUP | resource_group |
| WEBSITE_SITE_NAME | site_name |
When cloud metadata is collected
Then cloud metadata is null
51 changes: 51 additions & 0 deletions internal/apmcloudutil/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"encoding/json"
"errors"
"net/http"
"os"
"strings"

"go.elastic.co/apm/model"
)
Expand All @@ -32,6 +34,12 @@ const (

// See: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
func getAzureCloudMetadata(ctx context.Context, client *http.Client, out *model.Cloud) error {
// First check for Azure App Service environment variables, which can
// be done without performing any network requests.
if getAzureAppServiceCloudMetadata(ctx, out) {
return nil
}

req, err := http.NewRequest("GET", azureMetadataURL, nil)
if err != nil {
return err
Expand Down Expand Up @@ -76,3 +84,46 @@ func getAzureCloudMetadata(ctx context.Context, client *http.Client, out *model.
}
return nil
}

func getAzureAppServiceCloudMetadata(ctx context.Context, out *model.Cloud) bool {
// WEBSITE_OWNER_NAME has the form:
// {subscription id}+{app service plan resource group}-{region}webspace{.*}
websiteOwnerName := os.Getenv("WEBSITE_OWNER_NAME")
if websiteOwnerName == "" {
return false
}
websiteInstanceID := os.Getenv("WEBSITE_INSTANCE_ID")
if websiteInstanceID == "" {
return false
}
websiteSiteName := os.Getenv("WEBSITE_SITE_NAME")
if websiteSiteName == "" {
return false
}
websiteResourceGroup := os.Getenv("WEBSITE_RESOURCE_GROUP")
if websiteResourceGroup == "" {
return false
}

plus := strings.IndexRune(websiteOwnerName, '+')
if plus == -1 {
return false
}
out.Account = &model.CloudAccount{ID: websiteOwnerName[:plus]}
websiteOwnerName = websiteOwnerName[plus+1:]

webspace := strings.LastIndex(websiteOwnerName, "webspace")
if webspace == -1 {
return false
}
websiteOwnerName = websiteOwnerName[:webspace]

hyphen := strings.LastIndex(websiteOwnerName, "-")
if hyphen == -1 {
return false
}
out.Region = websiteOwnerName[hyphen+1:]
out.Instance = &model.CloudInstance{ID: websiteInstanceID, Name: websiteSiteName}
out.Project = &model.CloudProject{Name: websiteResourceGroup}
return true
}
37 changes: 37 additions & 0 deletions internal/apmcloudutil/azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -57,6 +58,42 @@ func TestAzureCloudMetadata(t *testing.T) {
}
}

func TestAzureAppServiceCloudMetadata(t *testing.T) {
client := &http.Client{Transport: newTargetedRoundTripper("", "testing.invalid")}

os.Setenv("WEBSITE_OWNER_NAME", "f5940f10-2e30-3e4d-a259-63451ba6dae4+elastic-apm-AustraliaEastwebspace")
os.Setenv("WEBSITE_RESOURCE_GROUP", "resource_group")
os.Setenv("WEBSITE_SITE_NAME", "site_name")
os.Setenv("WEBSITE_INSTANCE_ID", "instance_id")
defer func() {
os.Unsetenv("WEBSITE_OWNER_NAME")
os.Unsetenv("WEBSITE_RESOURCE_GROUP")
os.Unsetenv("WEBSITE_SITE_NAME")
os.Unsetenv("WEBSITE_INSTANCE_ID")
}()

for _, provider := range []Provider{Auto, Azure} {
var out model.Cloud
var logger testLogger
assert.True(t, provider.getCloudMetadata(context.Background(), client, &logger, &out))
assert.Zero(t, logger)
assert.Equal(t, model.Cloud{
Provider: "azure",
Region: "AustraliaEast",
Instance: &model.CloudInstance{
ID: "instance_id",
Name: "site_name",
},
Project: &model.CloudProject{
Name: "resource_group",
},
Account: &model.CloudAccount{
ID: "f5940f10-2e30-3e4d-a259-63451ba6dae4",
},
}, out)
}
}

func newAzureMetadataServer() (*httptest.Server, *http.Client) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/metadata/instance/compute" {
Expand Down
19 changes: 19 additions & 0 deletions internal/apmgodog/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

// Package apmgodog implements the Gherkin feature spec tests.
package apmgodog
2 changes: 1 addition & 1 deletion internal/apmgodog/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

// +build go1.9

package apmgodog
package apmgodog_test

import "testing"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

// +build go1.9

package apmgodog
package apmgodog_test

import (
"fmt"
Expand Down
1 change: 1 addition & 0 deletions internal/apmgodog/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
go.elastic.co/apm v1.11.0
go.elastic.co/apm/module/apmgrpc v1.11.0
go.elastic.co/apm/module/apmhttp v1.11.0
go.elastic.co/fastjson v1.1.0
google.golang.org/grpc v1.17.0
)

Expand Down
Loading