Skip to content
This repository has been archived by the owner on Feb 20, 2020. It is now read-only.

Commit

Permalink
Added --configure-for-azure option for running in Azure cloud
Browse files Browse the repository at this point in the history
  • Loading branch information
petemoore committed Dec 13, 2019
1 parent 595a689 commit 9667695
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 18 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ and reports back results to the queue.
Usage:
generic-worker run [--config CONFIG-FILE]
[--configure-for-aws | --configure-for-gcp]
[--configure-for-aws | --configure-for-gcp | --configure-for-azure]
generic-worker show-payload-schema
generic-worker new-ed25519-keypair --file ED25519-PRIVATE-KEY-FILE
generic-worker --help
Expand Down Expand Up @@ -487,6 +487,9 @@ and reports back results to the queue.
to self-configure, based on AWS metadata, information
from the provisioner, and the worker type definition
that the provisioner holds for the worker type.
--configure-for-azure This will create the CONFIG-FILE for an Azure
installation by querying the Azure environment
and setting appropriate values.
--configure-for-gcp This will create the CONFIG-FILE for a GCP
installation by querying the GCP environment
and setting appropriate values.
Expand Down
2 changes: 1 addition & 1 deletion aws_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func (m *MockAWSProvisionedEnvironment) Setup(t *testing.T) (teardown func(), er
configFile := &gwconfig.File{
Path: filepath.Join(testdataDir, t.Name(), "generic-worker.config"),
}
configProvider, err = loadConfig(configFile, true, false)
configProvider, err = loadConfig(configFile, AWS_PROVIDER)
return func() {
td()
err := s.Shutdown(context.Background())
Expand Down
169 changes: 169 additions & 0 deletions azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package main

import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"

"github.com/taskcluster/generic-worker/gwconfig"
"github.com/taskcluster/httpbackoff"
)

var (
// not a const, because in testing we swap this out
AzureMetadataBaseURL = "http://169.254.169.254"
)

type (
AzureConfigProvider struct {
}

AzureWorkerLocation struct {
Cloud string `json:"cloud"`
Region string `json:"region"`
}

AzureMetaData struct {
Compute struct {
CustomData string `json:"customData"`
Location string `json:"location"`
VMID string `json:"vmId"`
VMSize string `json:"vmSize"`
} `json:"compute"`
Network struct {
Interface []struct {
IPV4 struct {
IPAddress []struct {
PrivateIPAddress string `json:"privateIpAddress"`
PublicIPAddress string `json:"publicIpAddress"`
} `json:"ipAddress"`
} `json:"ipv4"`
} `json:"interface"`
} `json:"network"`
}

AttestedDocument struct {
Encoding string `json:"encoding"`
Signature string `json:"signature"`
}
)

func queryAzureMetaData(client *http.Client, path string, apiVersion string) ([]byte, error) {
req, err := http.NewRequest("GET", AzureMetadataBaseURL+path+"?api-version="+apiVersion, nil)

if err != nil {
return nil, err
}

req.Header.Add("Metadata", "true")

resp, _, err := httpbackoff.ClientDo(client, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}

func (g *AzureConfigProvider) UpdateConfig(c *gwconfig.Config) error {
log.Print("Querying Azure Metadata to get default worker type config settings...")

client := &http.Client{}

instanceMetaData, err := queryAzureMetaData(client, "/metadata/instance", "2019-04-30")
if err != nil {
return fmt.Errorf("Could not query taskcluster configuration: %v", err)
}
var azureMetaData AzureMetaData
err = json.Unmarshal(instanceMetaData, &azureMetaData)
if err != nil {
return fmt.Errorf("Could not unmarshal instance metadata %q into AzureMetaData struct - is it valid JSON? %v", string(instanceMetaData), err)
}

taskclusterConfig, err := base64.StdEncoding.DecodeString(azureMetaData.Compute.CustomData)
if err != nil {
return fmt.Errorf("Custom data %q is not valid base64: %v", azureMetaData.Compute.CustomData, err)
}

userData := new(WorkerManagerUserData)
err = json.Unmarshal(taskclusterConfig, userData)
if err != nil {
return fmt.Errorf("Could not parse base64 decoded custom data %q as JSON: %v", taskclusterConfig, err)
}

c.WorkerTypeMetadata["azure"] = map[string]interface{}{
"location": azureMetaData.Compute.Location,
"vmId": azureMetaData.Compute.VMID,
"vmSize": azureMetaData.Compute.VMSize,
}
c.WorkerID = azureMetaData.Compute.VMID
if len(azureMetaData.Network.Interface) == 1 {
iface := azureMetaData.Network.Interface[0]
if len(iface.IPV4.IPAddress) == 1 {
addr := iface.IPV4.IPAddress[0]
c.PublicIP = net.ParseIP(addr.PublicIPAddress)
c.PrivateIP = net.ParseIP(addr.PrivateIPAddress)
}
}
c.InstanceID = azureMetaData.Compute.VMID
c.InstanceType = azureMetaData.Compute.VMSize
c.AvailabilityZone = azureMetaData.Compute.Location
c.Region = azureMetaData.Compute.Location

attestedDoc, err := queryAzureMetaData(client, "/metadata/attested/document", "2019-04-30")
if err != nil {
return fmt.Errorf("Could not query taskcluster configuration: %v", err)
}
var doc AttestedDocument
err = json.Unmarshal(attestedDoc, &doc)
if err != nil {
return fmt.Errorf("Could not unmarshal attested document %q into AttestedDocument struct - is it valid JSON? %v", string(attestedDoc), err)
}
if doc.Encoding != "pkcs7" {
return fmt.Errorf(`Attested document has unsupported encoding (%q) - generic-worker only supports "pkcs7"`, doc.Encoding)
}

// TODO: when Azure Provider is completed in Worker Manager, we'll be able
// to do the following, but that isn't available yet:
//
// providerType := tcworkermanager.AzureProviderType{
// Document: doc.Signature,
// }

providerType := map[string]string{
"document": doc.Signature,
}

err = userData.UpdateConfig(c, providerType)
if err != nil {
return err
}

// Don't override WorkerLocation if configuration specifies an explicit
// value.
//
// See:
// * https://github.com/taskcluster/taskcluster-rfcs/blob/master/rfcs/0148-taskcluster-worker-location.md
// * https://github.com/taskcluster/taskcluster-worker-runner#google
if c.WorkerLocation == "" {
workerLocation := &AzureWorkerLocation{
Cloud: "azure",
Region: c.Region,
}

workerLocationJSON, err := json.Marshal(workerLocation)
if err != nil {
return fmt.Errorf("Error encoding worker location %#v as JSON: %v", workerLocation, err)
}
c.WorkerLocation = string(workerLocationJSON)
}
return nil
}

func (g *AzureConfigProvider) NewestDeploymentID() (string, error) {
return WMDeploymentID()
}
14 changes: 7 additions & 7 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestMissingIPConfig(t *testing.T) {
Path: filepath.Join("testdata", "config", "noip.json"),
}
const setting = "publicIP"
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err != nil {
t.Fatalf("%v", err)
}
Expand All @@ -39,7 +39,7 @@ func TestValidConfig(t *testing.T) {
}
const ipaddr = "2.1.2.1"
const workerType = "some-worker-type"
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err != nil {
t.Fatalf("%v", err)
}
Expand All @@ -59,7 +59,7 @@ func TestInvalidIPConfig(t *testing.T) {
file := &gwconfig.File{
Path: filepath.Join("testdata", "config", "invalid-ip.json"),
}
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err == nil {
t.Fatal("Was expecting to get an error back due to an invalid IP address, but didn't get one!")
}
Expand All @@ -73,7 +73,7 @@ func TestInvalidJsonConfig(t *testing.T) {
file := &gwconfig.File{
Path: filepath.Join("testdata", "config", "invalid-json.json"),
}
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err == nil {
t.Fatal("Was expecting to get an error back due to an invalid JSON config, but didn't get one!")
}
Expand All @@ -87,7 +87,7 @@ func TestMissingConfigFile(t *testing.T) {
file := &gwconfig.File{
Path: filepath.Join("testdata", "config", "non-existent-json.json"),
}
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err == nil {
t.Fatal("Was expecting an error when loading non existent config file without --configure-for-{aws,gcp} set")
}
Expand All @@ -100,7 +100,7 @@ func TestWorkerTypeMetadata(t *testing.T) {
file := &gwconfig.File{
Path: filepath.Join("testdata", "config", "worker-type-metadata.json"),
}
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err != nil {
t.Fatalf("%v", err)
}
Expand All @@ -126,7 +126,7 @@ func TestBoolAsString(t *testing.T) {
file := &gwconfig.File{
Path: filepath.Join("testdata", "config", "bool-as-string.json"),
}
_, err := loadConfig(file, false, false)
_, err := loadConfig(file, NO_PROVIDER)
if err == nil {
t.Fatal("Was expecting to get an error back due to a bool being specified as a string, but didn't get one!")
}
Expand Down
2 changes: 1 addition & 1 deletion gcp_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (m *MockGCPProvisionedEnvironment) Setup(t *testing.T) func() {
configFile := &gwconfig.File{
Path: filepath.Join(testdataDir, t.Name(), "generic-worker.config"),
}
configProvider, err = loadConfig(configFile, false, true)
configProvider, err = loadConfig(configFile, GCP_PROVIDER)
if err != nil {
t.Fatalf("Error: %v", err)
}
Expand Down
29 changes: 22 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var (
configureForAWS bool
// Whether we are running in GCP
configureForGCP bool
// Whether we are running in Azure
configureForAzure bool
// General platform independent user settings, such as home directory, username...
// Platform specific data should be managed in plat_<platform>.go files
taskContext = &TaskContext{}
Expand Down Expand Up @@ -120,6 +122,7 @@ func main() {
case arguments["run"]:
configureForAWS = arguments["--configure-for-aws"].(bool)
configureForGCP = arguments["--configure-for-gcp"].(bool)
configureForAzure = arguments["--configure-for-azure"].(bool)

configFileAbs, err := filepath.Abs(arguments["--config"].(string))
exitOnError(CANT_LOAD_CONFIG, err, "Cannot determine absolute path location for generic-worker config file '%v'", arguments["--config"])
Expand All @@ -128,7 +131,17 @@ func main() {
Path: configFileAbs,
}

configProvider, err = loadConfig(configFile, configureForAWS, configureForGCP)
provider := NO_PROVIDER
switch {
case configureForAWS:
provider = AWS_PROVIDER
case configureForGCP:
provider = GCP_PROVIDER
case configureForAzure:
provider = AZURE_PROVIDER
}

configProvider, err = loadConfig(configFile, provider)

// We need to persist the generic-worker config file if we fetched it
// over the network, for example if the config is fetched from the AWS
Expand Down Expand Up @@ -200,9 +213,9 @@ func main() {
}
}

func loadConfig(configFile *gwconfig.File, queryAWSUserData bool, queryGCPMetaData bool) (gwconfig.Provider, error) {
func loadConfig(configFile *gwconfig.File, provider Provider) (gwconfig.Provider, error) {

configProvider, err := ConfigProvider(configFile, queryAWSUserData, queryGCPMetaData)
configProvider, err := ConfigProvider(configFile, provider)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -278,17 +291,19 @@ func loadConfig(configFile *gwconfig.File, queryAWSUserData bool, queryGCPMetaDa
return configProvider, nil
}

func ConfigProvider(configFile *gwconfig.File, queryAWSUserData bool, queryGCPMetaData bool) (gwconfig.Provider, error) {
func ConfigProvider(configFile *gwconfig.File, provider Provider) (gwconfig.Provider, error) {
var configProvider gwconfig.Provider
switch {
case queryAWSUserData:
switch provider {
case AWS_PROVIDER:
var err error
configProvider, err = InferAWSConfigProvider()
if err != nil {
return nil, err
}
case queryGCPMetaData:
case GCP_PROVIDER:
configProvider = &GCPConfigProvider{}
case AZURE_PROVIDER:
configProvider = &AzureConfigProvider{}
default:
configProvider = configFile
}
Expand Down
5 changes: 4 additions & 1 deletion usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ and reports back results to the queue.
Usage:
generic-worker run [--config CONFIG-FILE]
[--configure-for-aws | --configure-for-gcp]` + installServiceSummary() + `
[--configure-for-aws | --configure-for-gcp | --configure-for-azure]` + installServiceSummary() + `
generic-worker show-payload-schema
generic-worker new-ed25519-keypair --file ED25519-PRIVATE-KEY-FILE` + customTargetsSummary() + `
generic-worker --help
Expand Down Expand Up @@ -65,6 +65,9 @@ and reports back results to the queue.
to self-configure, based on AWS metadata, information
from the provisioner, and the worker type definition
that the provisioner holds for the worker type.
--configure-for-azure This will create the CONFIG-FILE for an Azure
installation by querying the Azure environment
and setting appropriate values.
--configure-for-gcp This will create the CONFIG-FILE for a GCP
installation by querying the GCP environment
and setting appropriate values.` + platformCommandLineParameters() + `
Expand Down
9 changes: 9 additions & 0 deletions workermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ type WorkerManagerUserData struct {
WorkerConfig BootstrapConfig `json:"workerConfig"`
}

type Provider uint

const (
NO_PROVIDER = iota
AWS_PROVIDER
AZURE_PROVIDER
GCP_PROVIDER
)

func (userData *WorkerManagerUserData) UpdateConfig(c *gwconfig.Config, providerType interface{}) error {
wp := strings.SplitN(userData.WorkerPoolID, "/", -1)
if len(wp) != 2 {
Expand Down

0 comments on commit 9667695

Please sign in to comment.