зеркало из https://github.com/golang/build.git
buildlet: use cloud client with AWS buildlet
Use the cloud package for AWS functions. Remove the unused destroyVM function. Updates golang/go#36841 Change-Id: I00e1a20c904f7c4be6460ac302085b28f518d161 Reviewed-on: https://go-review.googlesource.com/c/build/+/236300 Run-TryBot: Carlos Amedee <carlos@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alexander Rakoczy <alex@golang.org>
This commit is contained in:
Родитель
db1d16dd18
Коммит
87d102082c
254
buildlet/aws.go
254
buildlet/aws.go
|
@ -1,254 +0,0 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildlet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"golang.org/x/build/buildenv"
|
||||
"golang.org/x/build/dashboard"
|
||||
)
|
||||
|
||||
// EC2UserData is stored in the user data for each EC2 instance. This is
|
||||
// used to store metadata about the running instance. The buildlet will retrieve
|
||||
// this on EC2 instances before allowing connections from the coordinator.
|
||||
type EC2UserData struct {
|
||||
BuildletBinaryURL string `json:"buildlet_binary_url,omitempty"`
|
||||
BuildletHostType string `json:"buildlet_host_type,omitempty"`
|
||||
BuildletImageURL string `json:"buildlet_image_url,omitempty"`
|
||||
BuildletName string `json:"buildlet_name,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
TLSCert string `json:"tls_cert,omitempty"`
|
||||
TLSKey string `json:"tls_key,omitempty"`
|
||||
TLSPassword string `json:"tls_password,omitempty"`
|
||||
}
|
||||
|
||||
// ec2Client represents the EC2 specific calls made durring the
|
||||
// lifecycle of a buildlet.
|
||||
type ec2Client interface {
|
||||
DescribeInstancesWithContext(context.Context, *ec2.DescribeInstancesInput, ...request.Option) (*ec2.DescribeInstancesOutput, error)
|
||||
RunInstancesWithContext(context.Context, *ec2.RunInstancesInput, ...request.Option) (*ec2.Reservation, error)
|
||||
TerminateInstancesWithContext(context.Context, *ec2.TerminateInstancesInput, ...request.Option) (*ec2.TerminateInstancesOutput, error)
|
||||
WaitUntilInstanceRunningWithContext(context.Context, *ec2.DescribeInstancesInput, ...request.WaiterOption) error
|
||||
}
|
||||
|
||||
// AWSClient is the client used to create and destroy buildlets on AWS.
|
||||
type AWSClient struct {
|
||||
client ec2Client
|
||||
}
|
||||
|
||||
// NewAWSClient creates a new AWSClient.
|
||||
func NewAWSClient(region, keyID, accessKey string) (*AWSClient, error) {
|
||||
s, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(region),
|
||||
Credentials: credentials.NewStaticCredentials(keyID, accessKey, ""), // Token is only required for STS
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create AWS session: %v", err)
|
||||
}
|
||||
return &AWSClient{
|
||||
client: ec2.New(s),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StartNewVM boots a new VM on EC2, waits until the client is accepting connections
|
||||
// on the configured port and returns a buildlet client configured communicate with it.
|
||||
func (c *AWSClient) StartNewVM(ctx context.Context, buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) (*Client, error) {
|
||||
// check required params
|
||||
if opts == nil || opts.TLS.IsZero() {
|
||||
return nil, errors.New("TLS keypair is not set")
|
||||
}
|
||||
if buildEnv == nil {
|
||||
return nil, errors.New("invalid build enviornment")
|
||||
}
|
||||
if hconf == nil {
|
||||
return nil, errors.New("invalid host configuration")
|
||||
}
|
||||
if vmName == "" || hostType == "" {
|
||||
return nil, fmt.Errorf("invalid vmName: %q and hostType: %q", vmName, hostType)
|
||||
}
|
||||
|
||||
// configure defaults
|
||||
if opts.Description == "" {
|
||||
opts.Description = fmt.Sprintf("Go Builder for %s", hostType)
|
||||
}
|
||||
if opts.Zone == "" {
|
||||
opts.Zone = buildEnv.RandomEC2VMZone()
|
||||
}
|
||||
if opts.DeleteIn == 0 {
|
||||
opts.DeleteIn = 30 * time.Minute
|
||||
}
|
||||
|
||||
vmConfig := c.configureVM(buildEnv, hconf, vmName, hostType, opts)
|
||||
|
||||
vmID, err := c.createVM(ctx, vmConfig, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = c.WaitUntilVMExists(ctx, vmID, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm, err := c.RetrieveVMInfo(ctx, vmID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buildletURL, ipPort, err := ec2BuildletParams(vm, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildletClient(ctx, buildletURL, ipPort, opts)
|
||||
}
|
||||
|
||||
// createVM submits a request for the creation of a VM.
|
||||
func (c *AWSClient) createVM(ctx context.Context, vmConfig *ec2.RunInstancesInput, opts *VMOpts) (string, error) {
|
||||
runResult, err := c.client.RunInstancesWithContext(ctx, vmConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create instance: %w", err)
|
||||
}
|
||||
condRun(opts.OnInstanceRequested)
|
||||
return *runResult.Instances[0].InstanceId, nil
|
||||
}
|
||||
|
||||
// WaitUntilVMExists submits a request which waits until an instance exists before returning.
|
||||
func (c *AWSClient) WaitUntilVMExists(ctx context.Context, instID string, opts *VMOpts) error {
|
||||
err := c.client.WaitUntilInstanceRunningWithContext(ctx, &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instID)},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for vm instance: %w", err)
|
||||
}
|
||||
condRun(opts.OnInstanceCreated)
|
||||
return err
|
||||
}
|
||||
|
||||
// RetrieveVMInfo retrives the information about a VM.
|
||||
func (c *AWSClient) RetrieveVMInfo(ctx context.Context, instID string) (*ec2.Instance, error) {
|
||||
instances, err := c.client.DescribeInstancesWithContext(ctx, &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{aws.String(instID)},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve instance %q information: %w", instID, err)
|
||||
}
|
||||
|
||||
instance, err := ec2Instance(instances)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read instance description: %w", err)
|
||||
}
|
||||
return instance, err
|
||||
}
|
||||
|
||||
// configureVM creates a configuration for an EC2 VM instance.
|
||||
func (c *AWSClient) configureVM(buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) *ec2.RunInstancesInput {
|
||||
vmConfig := &ec2.RunInstancesInput{
|
||||
ImageId: aws.String(hconf.VMImage),
|
||||
InstanceType: aws.String(hconf.MachineType()),
|
||||
MinCount: aws.Int64(1),
|
||||
MaxCount: aws.Int64(1),
|
||||
Placement: &ec2.Placement{
|
||||
AvailabilityZone: aws.String(opts.Zone),
|
||||
},
|
||||
KeyName: aws.String("ec2-go-builders"),
|
||||
InstanceInitiatedShutdownBehavior: aws.String("terminate"),
|
||||
TagSpecifications: []*ec2.TagSpecification{
|
||||
&ec2.TagSpecification{
|
||||
ResourceType: aws.String("instance"),
|
||||
Tags: []*ec2.Tag{
|
||||
&ec2.Tag{
|
||||
Key: aws.String("Name"),
|
||||
Value: aws.String(vmName),
|
||||
},
|
||||
&ec2.Tag{
|
||||
Key: aws.String("Description"),
|
||||
Value: aws.String(opts.Description),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c.vmUserDataSpec(vmConfig, buildEnv, hconf, vmName, hostType, opts)
|
||||
return vmConfig
|
||||
}
|
||||
|
||||
func (c *AWSClient) vmUserDataSpec(vmConfig *ec2.RunInstancesInput, buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) {
|
||||
// add custom metadata to the user data.
|
||||
ud := EC2UserData{
|
||||
BuildletName: vmName,
|
||||
BuildletBinaryURL: hconf.BuildletBinaryURL(buildEnv),
|
||||
BuildletHostType: hostType,
|
||||
BuildletImageURL: hconf.ContainerVMImage(),
|
||||
Metadata: make(map[string]string),
|
||||
TLSCert: opts.TLS.CertPEM,
|
||||
TLSKey: opts.TLS.KeyPEM,
|
||||
TLSPassword: opts.TLS.Password(),
|
||||
}
|
||||
for k, v := range opts.Meta {
|
||||
ud.Metadata[k] = v
|
||||
}
|
||||
jsonUserData, err := json.Marshal(ud)
|
||||
if err != nil {
|
||||
log.Printf("unable to marshal user data: %v", err)
|
||||
}
|
||||
// user data must be base64 encoded
|
||||
jud := base64.StdEncoding.EncodeToString([]byte(jsonUserData))
|
||||
vmConfig.SetUserData(jud)
|
||||
}
|
||||
|
||||
// DestroyVM submits a request to destroy a VM.
|
||||
func (c *AWSClient) DestroyVM(ctx context.Context, vmID string) error {
|
||||
_, err := c.client.TerminateInstancesWithContext(ctx, &ec2.TerminateInstancesInput{
|
||||
InstanceIds: []*string{aws.String(vmID)},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to destroy vm: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ec2Instance extracts the first instance found in the the describe instances output.
|
||||
func ec2Instance(dio *ec2.DescribeInstancesOutput) (*ec2.Instance, error) {
|
||||
if dio == nil || dio.Reservations == nil || dio.Reservations[0].Instances == nil {
|
||||
return nil, errors.New("describe instances output does not contain a valid instance")
|
||||
}
|
||||
return dio.Reservations[0].Instances[0], nil
|
||||
}
|
||||
|
||||
// ec2InstanceIPs returns the internal and external ip addresses for the VM.
|
||||
func ec2InstanceIPs(inst *ec2.Instance) (intIP, extIP string, err error) {
|
||||
if inst.PrivateIpAddress == nil || *inst.PrivateIpAddress == "" {
|
||||
return "", "", errors.New("internal IP address is not set")
|
||||
}
|
||||
if inst.PublicIpAddress == nil || *inst.PublicIpAddress == "" {
|
||||
return "", "", errors.New("external IP address is not set")
|
||||
}
|
||||
return *inst.PrivateIpAddress, *inst.PublicIpAddress, nil
|
||||
}
|
||||
|
||||
// ec2BuildletParams returns the necessary information to connect to an EC2 buildlet. A
|
||||
// buildlet URL and an IP address port are required to connect to a buildlet.
|
||||
func ec2BuildletParams(inst *ec2.Instance, opts *VMOpts) (string, string, error) {
|
||||
_, extIP, err := ec2InstanceIPs(inst)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to retrieve IP addresses: %w", err)
|
||||
}
|
||||
buildletURL := fmt.Sprintf("https://%s", extIP)
|
||||
ipPort := net.JoinHostPort(extIP, "443")
|
||||
|
||||
if opts.OnGotEC2InstanceInfo != nil {
|
||||
opts.OnGotEC2InstanceInfo(inst)
|
||||
}
|
||||
return buildletURL, ipPort, err
|
||||
}
|
|
@ -1,741 +0,0 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildlet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/build/buildenv"
|
||||
"golang.org/x/build/dashboard"
|
||||
)
|
||||
|
||||
type fakeEC2Client struct {
|
||||
// returned in describe instances
|
||||
PrivateIP *string
|
||||
PublicIP *string
|
||||
}
|
||||
|
||||
func (f *fakeEC2Client) DescribeInstancesWithContext(ctx context.Context, input *ec2.DescribeInstancesInput, opt ...request.Option) (*ec2.DescribeInstancesOutput, error) {
|
||||
if ctx == nil || input == nil || len(input.InstanceIds) == 0 {
|
||||
return nil, request.ErrInvalidParams{}
|
||||
}
|
||||
return &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
&ec2.Reservation{
|
||||
Instances: []*ec2.Instance{
|
||||
&ec2.Instance{
|
||||
InstanceId: input.InstanceIds[0],
|
||||
PrivateIpAddress: f.PrivateIP,
|
||||
PublicIpAddress: f.PublicIP,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeEC2Client) RunInstancesWithContext(ctx context.Context, input *ec2.RunInstancesInput, opts ...request.Option) (*ec2.Reservation, error) {
|
||||
if ctx == nil || input == nil {
|
||||
return nil, request.ErrInvalidParams{}
|
||||
}
|
||||
if input.ImageId == nil || input.InstanceType == nil || input.MinCount == nil || input.Placement == nil {
|
||||
return nil, errors.New("invalid instance configuration")
|
||||
}
|
||||
return &ec2.Reservation{
|
||||
Instances: []*ec2.Instance{
|
||||
&ec2.Instance{
|
||||
ImageId: input.ImageId,
|
||||
InstanceType: input.InstanceType,
|
||||
InstanceId: aws.String("44"),
|
||||
Placement: input.Placement,
|
||||
},
|
||||
},
|
||||
ReservationId: aws.String("res_id"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeEC2Client) TerminateInstancesWithContext(ctx context.Context, input *ec2.TerminateInstancesInput, opts ...request.Option) (*ec2.TerminateInstancesOutput, error) {
|
||||
if ctx == nil || input == nil || len(input.InstanceIds) == 0 {
|
||||
return nil, request.ErrInvalidParams{}
|
||||
}
|
||||
for _, id := range input.InstanceIds {
|
||||
if *id == "" {
|
||||
return nil, errors.New("invalid instance id")
|
||||
}
|
||||
}
|
||||
return &ec2.TerminateInstancesOutput{
|
||||
TerminatingInstances: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeEC2Client) WaitUntilInstanceRunningWithContext(ctx context.Context, input *ec2.DescribeInstancesInput, opt ...request.WaiterOption) error {
|
||||
if ctx == nil || input == nil || len(input.InstanceIds) == 0 {
|
||||
return request.ErrInvalidParams{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRetrieveVMInfo(t *testing.T) {
|
||||
wantVMID := "22"
|
||||
ctx := context.Background()
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotInst, gotErr := c.RetrieveVMInfo(ctx, wantVMID)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("RetrieveVMInfo(%v, %q) failed with error %s", ctx, wantVMID, gotErr)
|
||||
}
|
||||
if gotInst == nil || *gotInst.InstanceId != wantVMID {
|
||||
t.Errorf("RetrieveVMInfo(%v, %q) failed with error %s", ctx, wantVMID, gotErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartNewVM(t *testing.T) {
|
||||
kp, err := NewKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate key pair: %s", err)
|
||||
}
|
||||
buildEnv := &buildenv.Environment{}
|
||||
hconf := &dashboard.HostConfig{}
|
||||
vmName := "sample-vm"
|
||||
hostType := "host-sample-os"
|
||||
opts := &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
SkipEndpointVerification: true,
|
||||
}
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{
|
||||
PrivateIP: aws.String("8.8.8.8"),
|
||||
PublicIP: aws.String("9.9.9.9"),
|
||||
},
|
||||
}
|
||||
gotClient, gotErr := c.StartNewVM(context.Background(), buildEnv, hconf, vmName, hostType, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("error is not nil: %v", gotErr)
|
||||
}
|
||||
if gotClient == nil {
|
||||
t.Fatalf("response is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartNewVMError(t *testing.T) {
|
||||
kp, err := NewKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate key pair: %s", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
buildEnv *buildenv.Environment
|
||||
hconf *dashboard.HostConfig
|
||||
vmName string
|
||||
hostType string
|
||||
opts *VMOpts
|
||||
}{
|
||||
{
|
||||
desc: "nil-buildenv",
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil-hconf",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty-vnName",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty-hostType",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-certs",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil-opts",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotClient, gotErr := c.StartNewVM(context.Background(), tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
|
||||
if gotErr == nil {
|
||||
t.Errorf("expected error did not occur")
|
||||
}
|
||||
if gotClient != nil {
|
||||
t.Errorf("got %+v; expected nil", gotClient)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitUntilInstanceExists(t *testing.T) {
|
||||
vmID := "22"
|
||||
invoked := false
|
||||
opts := &VMOpts{
|
||||
OnInstanceCreated: func() {
|
||||
invoked = true
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotErr := c.WaitUntilVMExists(ctx, vmID, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("WaitUntilVMExists(%v, %v, %v) failed with error %s", ctx, vmID, opts, gotErr)
|
||||
}
|
||||
if !invoked {
|
||||
t.Errorf("OnInstanceCreated() was not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateVM(t *testing.T) {
|
||||
vmConfig := &ec2.RunInstancesInput{
|
||||
ImageId: aws.String("foo"),
|
||||
InstanceType: aws.String("type-a"),
|
||||
MinCount: aws.Int64(15),
|
||||
Placement: &ec2.Placement{
|
||||
AvailabilityZone: aws.String("eu-15"),
|
||||
},
|
||||
}
|
||||
invoked := false
|
||||
opts := &VMOpts{
|
||||
OnInstanceRequested: func() {
|
||||
invoked = true
|
||||
},
|
||||
}
|
||||
wantVMID := aws.String("44")
|
||||
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotVMID, gotErr := c.createVM(context.Background(), vmConfig, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
|
||||
}
|
||||
if gotVMID != *wantVMID {
|
||||
t.Errorf("createVM(ctx, %v, %v) = %s, nil; want %s, nil", vmConfig, opts, gotVMID, *wantVMID)
|
||||
}
|
||||
if !invoked {
|
||||
t.Errorf("OnInstanceRequested() was not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateVMError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
vmConfig *ec2.RunInstancesInput
|
||||
opts *VMOpts
|
||||
}{
|
||||
{
|
||||
desc: "missing-vmConfig",
|
||||
},
|
||||
{
|
||||
desc: "missing-image-id",
|
||||
vmConfig: &ec2.RunInstancesInput{
|
||||
InstanceType: aws.String("type-a"),
|
||||
MinCount: aws.Int64(15),
|
||||
Placement: &ec2.Placement{
|
||||
AvailabilityZone: aws.String("eu-15"),
|
||||
},
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-instance-id",
|
||||
vmConfig: &ec2.RunInstancesInput{
|
||||
ImageId: aws.String("foo"),
|
||||
MinCount: aws.Int64(15),
|
||||
Placement: &ec2.Placement{
|
||||
AvailabilityZone: aws.String("eu-15"),
|
||||
},
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-placement",
|
||||
vmConfig: &ec2.RunInstancesInput{
|
||||
ImageId: aws.String("foo"),
|
||||
InstanceType: aws.String("type-a"),
|
||||
MinCount: aws.Int64(15),
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotVMID, gotErr := c.createVM(context.Background(), tc.vmConfig, tc.opts)
|
||||
if gotErr == nil {
|
||||
t.Errorf("createVM(ctx, %v, %v) = %s, %v; want error", tc.vmConfig, tc.opts, gotVMID, gotErr)
|
||||
}
|
||||
if gotVMID != "" {
|
||||
t.Errorf("createVM(ctx, %v, %v) = %s, %v; %q, error", tc.vmConfig, tc.opts, gotVMID, gotErr, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDestroyVM(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ctx context.Context
|
||||
vmID string
|
||||
wantErr bool
|
||||
}{
|
||||
{"baseline request", context.Background(), "vm-20", false},
|
||||
{"nil context", nil, "vm-20", true},
|
||||
{"nil context", context.Background(), "", true},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &AWSClient{
|
||||
client: &fakeEC2Client{},
|
||||
}
|
||||
gotErr := c.DestroyVM(tc.ctx, tc.vmID)
|
||||
if (gotErr != nil) != tc.wantErr {
|
||||
t.Errorf("DestroyVM(%v, %q) = %v; want error %t", tc.ctx, tc.vmID, gotErr, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2BuildletParams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inst *ec2.Instance
|
||||
opts *VMOpts
|
||||
wantURL string
|
||||
wantPort string
|
||||
wantCalled bool
|
||||
}{
|
||||
{
|
||||
desc: "base case",
|
||||
inst: &ec2.Instance{
|
||||
PrivateIpAddress: aws.String("9.9.9.9"),
|
||||
PublicIpAddress: aws.String("8.8.8.8"),
|
||||
},
|
||||
opts: &VMOpts{},
|
||||
wantCalled: true,
|
||||
wantURL: "https://8.8.8.8",
|
||||
wantPort: "8.8.8.8:443",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotURL, gotPort, gotErr := ec2BuildletParams(tc.inst, tc.opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("ec2BuildletParams(%v, %v) failed; %v", tc.inst, tc.opts, gotErr)
|
||||
}
|
||||
if gotURL != tc.wantURL || gotPort != tc.wantPort {
|
||||
t.Errorf("ec2BuildletParams(%v, %v) = %q, %q, nil; want %q, %q, nil", tc.inst, tc.opts, gotURL, gotPort, tc.wantURL, tc.wantPort)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureVM(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
buildEnv *buildenv.Environment
|
||||
hconf *dashboard.HostConfig
|
||||
hostType string
|
||||
opts *VMOpts
|
||||
vmName string
|
||||
wantDesc string
|
||||
wantImageID string
|
||||
wantInstanceType string
|
||||
wantName string
|
||||
wantZone string
|
||||
wantBuildletName string
|
||||
wantBuildletImage string
|
||||
}{
|
||||
{
|
||||
desc: "default-values",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{
|
||||
KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
vmName: "base_vm",
|
||||
hostType: "host-foo-bar",
|
||||
opts: &VMOpts{},
|
||||
wantInstanceType: "n1-highcpu-2",
|
||||
wantName: "base_vm",
|
||||
wantBuildletName: "base_vm",
|
||||
wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
{
|
||||
desc: "full-configuration",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{
|
||||
VMImage: "awesome_image",
|
||||
KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
vmName: "base-vm",
|
||||
hostType: "host-foo-bar",
|
||||
opts: &VMOpts{
|
||||
Zone: "sa-west",
|
||||
TLS: KeyPair{
|
||||
CertPEM: "abc",
|
||||
KeyPEM: "xyz",
|
||||
},
|
||||
Description: "test description",
|
||||
Meta: map[string]string{
|
||||
"sample": "value",
|
||||
},
|
||||
},
|
||||
wantDesc: "test description",
|
||||
wantImageID: "awesome_image",
|
||||
wantInstanceType: "n1-highcpu-2",
|
||||
wantName: "base-vm",
|
||||
wantZone: "sa-west",
|
||||
wantBuildletName: "base-vm",
|
||||
wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &AWSClient{}
|
||||
got := c.configureVM(tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
|
||||
if *got.ImageId != tc.wantImageID {
|
||||
t.Errorf("ImageId got %s; want %s", *got.ImageId, tc.wantImageID)
|
||||
}
|
||||
if *got.InstanceType != tc.wantInstanceType {
|
||||
t.Errorf("InstanceType got %s; want %s", *got.InstanceType, tc.wantInstanceType)
|
||||
}
|
||||
|
||||
if *got.MinCount != 1 {
|
||||
t.Errorf("MinCount got %d; want %d", *got.MinCount, 1)
|
||||
}
|
||||
if *got.MaxCount != 1 {
|
||||
t.Errorf("MaxCount got %d; want %d", *got.MaxCount, 1)
|
||||
}
|
||||
if *got.Placement.AvailabilityZone != tc.wantZone {
|
||||
t.Errorf("AvailabilityZone got %s; want %s", *got.Placement.AvailabilityZone, tc.wantZone)
|
||||
}
|
||||
if *got.InstanceInitiatedShutdownBehavior != "terminate" {
|
||||
t.Errorf("InstanceType got %s; want %s", *got.InstanceInitiatedShutdownBehavior, "terminate")
|
||||
}
|
||||
if *got.TagSpecifications[0].ResourceType != "instance" {
|
||||
t.Errorf("Tag Resource Type got %s; want %s", *got.TagSpecifications[0].ResourceType, "instance")
|
||||
}
|
||||
if *got.TagSpecifications[0].Tags[0].Key != "Name" {
|
||||
t.Errorf("First Tag Key got %s; want %s", *got.TagSpecifications[0].Tags[0].Key, "Name")
|
||||
}
|
||||
if *got.TagSpecifications[0].Tags[0].Value != tc.wantName {
|
||||
t.Errorf("First Tag Value got %s; want %s", *got.TagSpecifications[0].Tags[0].Value, tc.wantName)
|
||||
}
|
||||
if *got.TagSpecifications[0].Tags[1].Key != "Description" {
|
||||
t.Errorf("Second Tag Key got %s; want %s", *got.TagSpecifications[0].Tags[1].Key, "Description")
|
||||
}
|
||||
if *got.TagSpecifications[0].Tags[1].Value != tc.wantDesc {
|
||||
t.Errorf("Second Tag Value got %s; want %s", *got.TagSpecifications[0].Tags[1].Value, tc.wantDesc)
|
||||
}
|
||||
gotUD := &EC2UserData{}
|
||||
gotUDJson, err := base64.StdEncoding.DecodeString(*got.UserData)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to base64 decode string %q: %s", *got.UserData, err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(gotUDJson), gotUD)
|
||||
if err != nil {
|
||||
t.Errorf("unable to unmarshal user data: %v", err)
|
||||
}
|
||||
if gotUD.BuildletBinaryURL != tc.hconf.BuildletBinaryURL(tc.buildEnv) {
|
||||
t.Errorf("buildletBinaryURL got %s; want %s", gotUD.BuildletBinaryURL, tc.hconf.BuildletBinaryURL(tc.buildEnv))
|
||||
}
|
||||
if gotUD.BuildletHostType != tc.hostType {
|
||||
t.Errorf("buildletHostType got %s; want %s", gotUD.BuildletHostType, tc.hostType)
|
||||
}
|
||||
if gotUD.BuildletName != tc.wantBuildletName {
|
||||
t.Errorf("buildletName got %s; want %s", gotUD.BuildletName, tc.wantBuildletName)
|
||||
}
|
||||
if gotUD.BuildletImageURL != tc.wantBuildletImage {
|
||||
t.Errorf("buildletImageURL got %s; want %s", gotUD.BuildletImageURL, tc.wantBuildletImage)
|
||||
}
|
||||
|
||||
if gotUD.TLSCert != tc.opts.TLS.CertPEM {
|
||||
t.Errorf("TLSCert got %s; want %s", gotUD.TLSCert, tc.opts.TLS.CertPEM)
|
||||
}
|
||||
if gotUD.TLSKey != tc.opts.TLS.KeyPEM {
|
||||
t.Errorf("TLSKey got %s; want %s", gotUD.TLSKey, tc.opts.TLS.KeyPEM)
|
||||
}
|
||||
if gotUD.TLSPassword != tc.opts.TLS.Password() {
|
||||
t.Errorf("TLSPassword got %s; want %s", gotUD.TLSPassword, tc.opts.TLS.Password())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2Instance(t *testing.T) {
|
||||
instSample1 := &ec2.Instance{
|
||||
InstanceId: aws.String("id1"),
|
||||
}
|
||||
instSample2 := &ec2.Instance{
|
||||
InstanceId: aws.String("id2"),
|
||||
}
|
||||
resSample1 := &ec2.Reservation{
|
||||
Instances: []*ec2.Instance{
|
||||
instSample1,
|
||||
},
|
||||
RequesterId: aws.String("user1"),
|
||||
ReservationId: aws.String("reservation12"),
|
||||
}
|
||||
resSample2 := &ec2.Reservation{
|
||||
Instances: []*ec2.Instance{
|
||||
instSample2,
|
||||
},
|
||||
RequesterId: aws.String("user2"),
|
||||
ReservationId: aws.String("reservation22"),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dio *ec2.DescribeInstancesOutput
|
||||
wantInst *ec2.Instance
|
||||
}{
|
||||
{
|
||||
desc: "single reservation",
|
||||
dio: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
resSample1,
|
||||
},
|
||||
},
|
||||
wantInst: instSample1,
|
||||
},
|
||||
{
|
||||
desc: "multiple reservations",
|
||||
dio: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
resSample2,
|
||||
resSample1,
|
||||
},
|
||||
},
|
||||
wantInst: instSample2,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotInst, gotErr := ec2Instance(tc.dio)
|
||||
if gotErr != nil {
|
||||
t.Errorf("ec2Instance(%v) failed: %v",
|
||||
tc.dio, gotErr)
|
||||
}
|
||||
if !cmp.Equal(gotInst, tc.wantInst) {
|
||||
t.Errorf("ec2Instance(%v) = %s; want %s",
|
||||
tc.dio, gotInst, tc.wantInst)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2InstanceError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
dio *ec2.DescribeInstancesOutput
|
||||
}{
|
||||
{
|
||||
desc: "nil input",
|
||||
dio: nil,
|
||||
},
|
||||
{
|
||||
desc: "nil reservation",
|
||||
dio: &ec2.DescribeInstancesOutput{
|
||||
Reservations: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil instances",
|
||||
dio: &ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
&ec2.Reservation{
|
||||
Instances: nil,
|
||||
RequesterId: aws.String("user1"),
|
||||
ReservationId: aws.String("reservation12"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
_, gotErr := ec2Instance(tc.dio)
|
||||
if gotErr == nil {
|
||||
t.Errorf("ec2Instance(%v) did not fail", tc.dio)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2InstanceIPs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inst *ec2.Instance
|
||||
wantIntIP string
|
||||
wantExtIP string
|
||||
}{
|
||||
{
|
||||
desc: "base case",
|
||||
inst: &ec2.Instance{
|
||||
PrivateIpAddress: aws.String("1.1.1.1"),
|
||||
PublicIpAddress: aws.String("8.8.8.8"),
|
||||
},
|
||||
wantIntIP: "1.1.1.1",
|
||||
wantExtIP: "8.8.8.8",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotIntIP, gotExtIP, gotErr := ec2InstanceIPs(tc.inst)
|
||||
if gotErr != nil {
|
||||
t.Errorf("ec2InstanceIPs(%v) failed: %v",
|
||||
tc.inst, gotErr)
|
||||
}
|
||||
if gotIntIP != tc.wantIntIP || gotExtIP != tc.wantExtIP {
|
||||
t.Errorf("ec2InstanceIPs(%v) = %s, %s, %v; want %s, %s, nil",
|
||||
tc.inst, gotIntIP, gotExtIP, gotErr, tc.wantIntIP, tc.wantExtIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2InstanceIPsErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inst *ec2.Instance
|
||||
}{
|
||||
{
|
||||
desc: "default vallues",
|
||||
inst: &ec2.Instance{},
|
||||
},
|
||||
{
|
||||
desc: "missing public ip",
|
||||
inst: &ec2.Instance{
|
||||
PrivateIpAddress: aws.String("1.1.1.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing private ip",
|
||||
inst: &ec2.Instance{
|
||||
PublicIpAddress: aws.String("8.8.8.8"),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty public ip",
|
||||
inst: &ec2.Instance{
|
||||
PrivateIpAddress: aws.String("1.1.1.1"),
|
||||
PublicIpAddress: aws.String(""),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty private ip",
|
||||
inst: &ec2.Instance{
|
||||
PrivateIpAddress: aws.String(""),
|
||||
PublicIpAddress: aws.String("8.8.8.8"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
_, _, gotErr := ec2InstanceIPs(tc.inst)
|
||||
if gotErr == nil {
|
||||
t.Errorf("ec2InstanceIPs(%v) = nil: want error", tc.inst)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"golang.org/x/build/internal/cloud"
|
||||
"google.golang.org/api/compute/v1"
|
||||
)
|
||||
|
||||
|
@ -61,7 +61,7 @@ type VMOpts struct {
|
|||
// OnInstanceCreated optionally specifies a hook to run synchronously
|
||||
// after the EC2 instance information is retrieved.
|
||||
// Only valid for EC2 resources.
|
||||
OnGotEC2InstanceInfo func(*ec2.Instance)
|
||||
OnGotEC2InstanceInfo func(*cloud.Instance)
|
||||
|
||||
// OnBeginBuildletProbe optionally specifies a hook to run synchronously
|
||||
// before StartNewVM tries to hit buildletURL to see if it's up yet.
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildlet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"golang.org/x/build/buildenv"
|
||||
"golang.org/x/build/dashboard"
|
||||
"golang.org/x/build/internal/cloud"
|
||||
)
|
||||
|
||||
// awsClient represents the AWS specific calls made durring the
|
||||
// lifecycle of a buildlet. This is a partial implementation of the AWSClient found at
|
||||
// `golang.org/x/internal/cloud`.
|
||||
type awsClient interface {
|
||||
Instance(ctx context.Context, instID string) (*cloud.Instance, error)
|
||||
CreateInstance(ctx context.Context, config *cloud.EC2VMConfiguration) (*cloud.Instance, error)
|
||||
WaitUntilInstanceRunning(ctx context.Context, instID string) error
|
||||
}
|
||||
|
||||
// EC2Client is the client used to create buildlets on EC2.
|
||||
type EC2Client struct {
|
||||
client awsClient
|
||||
}
|
||||
|
||||
// NewEC2Client creates a new EC2Client.
|
||||
func NewEC2Client(client *cloud.AWSClient) *EC2Client {
|
||||
return &EC2Client{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// StartNewVM boots a new VM on EC2, waits until the client is accepting connections
|
||||
// on the configured port and returns a buildlet client configured communicate with it.
|
||||
func (c *EC2Client) StartNewVM(ctx context.Context, buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) (*Client, error) {
|
||||
// check required params
|
||||
if opts == nil || opts.TLS.IsZero() {
|
||||
return nil, errors.New("TLS keypair is not set")
|
||||
}
|
||||
if buildEnv == nil {
|
||||
return nil, errors.New("invalid build enviornment")
|
||||
}
|
||||
if hconf == nil {
|
||||
return nil, errors.New("invalid host configuration")
|
||||
}
|
||||
if vmName == "" || hostType == "" {
|
||||
return nil, fmt.Errorf("invalid vmName: %q and hostType: %q", vmName, hostType)
|
||||
}
|
||||
|
||||
// configure defaults
|
||||
if opts.Description == "" {
|
||||
opts.Description = fmt.Sprintf("Go Builder for %s", hostType)
|
||||
}
|
||||
if opts.Zone == "" {
|
||||
opts.Zone = buildEnv.RandomEC2VMZone()
|
||||
}
|
||||
if opts.DeleteIn == 0 {
|
||||
opts.DeleteIn = 30 * time.Minute
|
||||
}
|
||||
|
||||
vmConfig := configureVM(buildEnv, hconf, vmName, hostType, opts)
|
||||
|
||||
vm, err := c.createVM(ctx, vmConfig, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = c.WaitUntilVMExists(ctx, vm.ID, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// once the VM is up and running then all of the configuration data is available
|
||||
// when the API is querried for the VM.
|
||||
vm, err = c.client.Instance(ctx, vm.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve instance %q information: %w", vm.ID, err)
|
||||
}
|
||||
buildletURL, ipPort, err := ec2BuildletParams(vm, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildletClient(ctx, buildletURL, ipPort, opts)
|
||||
}
|
||||
|
||||
// createVM submits a request for the creation of a VM.
|
||||
func (c *EC2Client) createVM(ctx context.Context, config *cloud.EC2VMConfiguration, opts *VMOpts) (*cloud.Instance, error) {
|
||||
if config == nil || opts == nil {
|
||||
return nil, errors.New("invalid parameter")
|
||||
}
|
||||
inst, err := c.client.CreateInstance(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create instance: %w", err)
|
||||
}
|
||||
condRun(opts.OnInstanceRequested)
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
// WaitUntilVMExists submits a request which waits until an instance exists before returning.
|
||||
func (c *EC2Client) WaitUntilVMExists(ctx context.Context, instID string, opts *VMOpts) error {
|
||||
if err := c.client.WaitUntilInstanceRunning(ctx, instID); err != nil {
|
||||
return fmt.Errorf("failed waiting for vm instance: %w", err)
|
||||
}
|
||||
condRun(opts.OnInstanceCreated)
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureVM creates a configuration for an EC2 VM instance.
|
||||
func configureVM(buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) *cloud.EC2VMConfiguration {
|
||||
return &cloud.EC2VMConfiguration{
|
||||
Description: opts.Description,
|
||||
ImageID: hconf.VMImage,
|
||||
Name: vmName,
|
||||
SSHKeyID: "ec2-go-builders",
|
||||
SecurityGroups: []string{buildEnv.AWSSecurityGroup},
|
||||
Tags: make(map[string]string),
|
||||
Type: hconf.MachineType(),
|
||||
UserData: vmUserDataSpec(buildEnv, hconf, vmName, hostType, opts),
|
||||
Zone: opts.Zone,
|
||||
}
|
||||
}
|
||||
|
||||
func vmUserDataSpec(buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *VMOpts) string {
|
||||
// add custom metadata to the user data.
|
||||
ud := cloud.EC2UserData{
|
||||
BuildletName: vmName,
|
||||
BuildletBinaryURL: hconf.BuildletBinaryURL(buildEnv),
|
||||
BuildletHostType: hostType,
|
||||
BuildletImageURL: hconf.ContainerVMImage(),
|
||||
Metadata: make(map[string]string),
|
||||
TLSCert: opts.TLS.CertPEM,
|
||||
TLSKey: opts.TLS.KeyPEM,
|
||||
TLSPassword: opts.TLS.Password(),
|
||||
}
|
||||
for k, v := range opts.Meta {
|
||||
ud.Metadata[k] = v
|
||||
}
|
||||
return ud.EncodedString()
|
||||
}
|
||||
|
||||
// ec2BuildletParams returns the necessary information to connect to an EC2 buildlet. A
|
||||
// buildlet URL and an IP address port are required to connect to a buildlet.
|
||||
func ec2BuildletParams(inst *cloud.Instance, opts *VMOpts) (string, string, error) {
|
||||
if inst.IPAddressExternal == "" {
|
||||
return "", "", errors.New("external IP address is not set")
|
||||
}
|
||||
extIP := inst.IPAddressExternal
|
||||
buildletURL := fmt.Sprintf("https://%s", extIP)
|
||||
ipPort := net.JoinHostPort(extIP, "443")
|
||||
|
||||
if opts.OnGotEC2InstanceInfo != nil {
|
||||
opts.OnGotEC2InstanceInfo(inst)
|
||||
}
|
||||
return buildletURL, ipPort, nil
|
||||
}
|
|
@ -0,0 +1,451 @@
|
|||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package buildlet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/build/buildenv"
|
||||
"golang.org/x/build/dashboard"
|
||||
"golang.org/x/build/internal/cloud"
|
||||
)
|
||||
|
||||
func TestStartNewVM(t *testing.T) {
|
||||
kp, err := NewKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate key pair: %s", err)
|
||||
}
|
||||
buildEnv := &buildenv.Environment{}
|
||||
hconf := &dashboard.HostConfig{
|
||||
VMImage: "image-x",
|
||||
}
|
||||
vmName := "sample-vm"
|
||||
hostType := "host-sample-os"
|
||||
opts := &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
SkipEndpointVerification: true,
|
||||
}
|
||||
c := &EC2Client{
|
||||
client: cloud.NewFakeAWSClient(),
|
||||
}
|
||||
gotClient, gotErr := c.StartNewVM(context.Background(), buildEnv, hconf, vmName, hostType, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("error is not nil: %v", gotErr)
|
||||
}
|
||||
if gotClient == nil {
|
||||
t.Fatalf("response is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartNewVMError(t *testing.T) {
|
||||
kp, err := NewKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate key pair: %s", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
buildEnv *buildenv.Environment
|
||||
hconf *dashboard.HostConfig
|
||||
vmName string
|
||||
hostType string
|
||||
opts *VMOpts
|
||||
}{
|
||||
{
|
||||
desc: "nil-buildenv",
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil-hconf",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty-vnName",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "empty-hostType",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
TLS: kp,
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-certs",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
opts: &VMOpts{
|
||||
Zone: "us-west",
|
||||
ProjectID: "project1",
|
||||
Description: "Golang builder for sample",
|
||||
Meta: map[string]string{
|
||||
"Owner": "george",
|
||||
},
|
||||
DeleteIn: 45 * time.Second,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "nil-opts",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{},
|
||||
vmName: "sample-vm",
|
||||
hostType: "host-sample-os",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &EC2Client{
|
||||
client: cloud.NewFakeAWSClient(),
|
||||
}
|
||||
gotClient, gotErr := c.StartNewVM(context.Background(), tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
|
||||
if gotErr == nil {
|
||||
t.Errorf("StartNewVM(ctx, %+v, %+v, %s, %s, %+v) = %+v, nil; want error", tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts, gotClient)
|
||||
}
|
||||
if gotClient != nil {
|
||||
t.Errorf("got %+v; expected nil", gotClient)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitUntilInstanceExists(t *testing.T) {
|
||||
vmConfig := &cloud.EC2VMConfiguration{
|
||||
ImageID: "foo",
|
||||
Type: "type-a",
|
||||
Zone: "eu-15",
|
||||
}
|
||||
invoked := false
|
||||
opts := &VMOpts{
|
||||
OnInstanceCreated: func() {
|
||||
invoked = true
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
c := &EC2Client{
|
||||
client: cloud.NewFakeAWSClient(),
|
||||
}
|
||||
gotVM, gotErr := c.createVM(ctx, vmConfig, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
|
||||
}
|
||||
gotErr = c.WaitUntilVMExists(ctx, gotVM.ID, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("WaitUntilVMExists(%v, %v, %v) failed with error %s", ctx, gotVM.ID, opts, gotErr)
|
||||
}
|
||||
if !invoked {
|
||||
t.Errorf("OnInstanceCreated() was not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateVM(t *testing.T) {
|
||||
vmConfig := &cloud.EC2VMConfiguration{
|
||||
ImageID: "foo",
|
||||
Type: "type-a",
|
||||
Zone: "eu-15",
|
||||
}
|
||||
invoked := false
|
||||
opts := &VMOpts{
|
||||
OnInstanceRequested: func() {
|
||||
invoked = true
|
||||
},
|
||||
}
|
||||
c := &EC2Client{
|
||||
client: cloud.NewFakeAWSClient(),
|
||||
}
|
||||
gotVM, gotErr := c.createVM(context.Background(), vmConfig, opts)
|
||||
if gotErr != nil {
|
||||
t.Fatalf("createVM(ctx, %v, %v) failed with %s", vmConfig, opts, gotErr)
|
||||
}
|
||||
if gotVM.ImageID != vmConfig.ImageID || gotVM.Type != vmConfig.Type || gotVM.Zone != vmConfig.Zone {
|
||||
t.Errorf("createVM(ctx, %+v, %+v) = %+v, nil; want vm to match config", vmConfig, opts, gotVM)
|
||||
}
|
||||
if !invoked {
|
||||
t.Errorf("OnInstanceRequested() was not invoked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateVMError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
vmConfig *cloud.EC2VMConfiguration
|
||||
opts *VMOpts
|
||||
}{
|
||||
{
|
||||
desc: "missing-vmConfig",
|
||||
},
|
||||
{
|
||||
desc: "missing-image-id",
|
||||
vmConfig: &cloud.EC2VMConfiguration{
|
||||
Type: "type-a",
|
||||
Zone: "eu-15",
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-instance-id",
|
||||
vmConfig: &cloud.EC2VMConfiguration{
|
||||
ImageID: "foo",
|
||||
Zone: "eu-15",
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing-placement",
|
||||
vmConfig: &cloud.EC2VMConfiguration{
|
||||
Name: "foo",
|
||||
Type: "type-a",
|
||||
},
|
||||
opts: &VMOpts{
|
||||
OnInstanceRequested: func() {},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
c := &EC2Client{
|
||||
client: cloud.NewFakeAWSClient(),
|
||||
//client: &fakeAWSClient{},
|
||||
}
|
||||
gotVM, gotErr := c.createVM(context.Background(), tc.vmConfig, tc.opts)
|
||||
if gotErr == nil {
|
||||
t.Errorf("createVM(ctx, %v, %v) = %s, %v; want error", tc.vmConfig, tc.opts, gotVM.ID, gotErr)
|
||||
}
|
||||
if gotVM != nil {
|
||||
t.Errorf("createVM(ctx, %v, %v) = %s, %v; %q, error", tc.vmConfig, tc.opts, gotVM.ID, gotErr, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEC2BuildletParams(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inst *cloud.Instance
|
||||
opts *VMOpts
|
||||
wantURL string
|
||||
wantPort string
|
||||
wantCalled bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
desc: "base-case",
|
||||
inst: &cloud.Instance{
|
||||
IPAddressExternal: "8.8.8.8",
|
||||
IPAddressInternal: "3.3.3.3",
|
||||
},
|
||||
opts: &VMOpts{},
|
||||
wantCalled: true,
|
||||
wantURL: "https://8.8.8.8",
|
||||
wantPort: "8.8.8.8:443",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
desc: "missing-int-ip",
|
||||
inst: &cloud.Instance{
|
||||
IPAddressExternal: "8.8.8.8",
|
||||
},
|
||||
opts: &VMOpts{},
|
||||
wantCalled: true,
|
||||
wantURL: "https://8.8.8.8",
|
||||
wantPort: "8.8.8.8:443",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
desc: "missing-ext-ip",
|
||||
inst: &cloud.Instance{
|
||||
IPAddressInternal: "3.3.3.3",
|
||||
},
|
||||
opts: &VMOpts{},
|
||||
wantCalled: true,
|
||||
wantURL: "",
|
||||
wantPort: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
gotURL, gotPort, gotErr := ec2BuildletParams(tc.inst, tc.opts)
|
||||
if gotURL != tc.wantURL || gotPort != tc.wantPort || tc.wantErr != (gotErr != nil) {
|
||||
t.Errorf("ec2BuildletParams(%v, %v) = %q, %q, nil; want %q, %q, nil", tc.inst, tc.opts, gotURL, gotPort, tc.wantURL, tc.wantPort)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureVM(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
buildEnv *buildenv.Environment
|
||||
hconf *dashboard.HostConfig
|
||||
hostType string
|
||||
opts *VMOpts
|
||||
vmName string
|
||||
wantDesc string
|
||||
wantImageID string
|
||||
wantInstanceType string
|
||||
wantName string
|
||||
wantZone string
|
||||
wantBuildletName string
|
||||
wantBuildletImage string
|
||||
}{
|
||||
{
|
||||
desc: "default-values",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{
|
||||
KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
vmName: "base_vm",
|
||||
hostType: "host-foo-bar",
|
||||
opts: &VMOpts{},
|
||||
wantInstanceType: "n1-highcpu-2",
|
||||
wantName: "base_vm",
|
||||
wantBuildletName: "base_vm",
|
||||
wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
{
|
||||
desc: "full-configuration",
|
||||
buildEnv: &buildenv.Environment{},
|
||||
hconf: &dashboard.HostConfig{
|
||||
VMImage: "awesome_image",
|
||||
KonletVMImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
vmName: "base-vm",
|
||||
hostType: "host-foo-bar",
|
||||
opts: &VMOpts{
|
||||
Zone: "sa-west",
|
||||
TLS: KeyPair{
|
||||
CertPEM: "abc",
|
||||
KeyPEM: "xyz",
|
||||
},
|
||||
Description: "test description",
|
||||
Meta: map[string]string{
|
||||
"sample": "value",
|
||||
},
|
||||
},
|
||||
wantDesc: "test description",
|
||||
wantImageID: "awesome_image",
|
||||
wantInstanceType: "n1-highcpu-2",
|
||||
wantName: "base-vm",
|
||||
wantZone: "sa-west",
|
||||
wantBuildletName: "base-vm",
|
||||
wantBuildletImage: "gcr.io/symbolic-datum-552/gobuilder-arm64-aws",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := configureVM(tc.buildEnv, tc.hconf, tc.vmName, tc.hostType, tc.opts)
|
||||
if got.ImageID != tc.wantImageID {
|
||||
t.Errorf("ImageId got %s; want %s", got.ImageID, tc.wantImageID)
|
||||
}
|
||||
if got.Type != tc.wantInstanceType {
|
||||
t.Errorf("Type got %s; want %s", got.Type, tc.wantInstanceType)
|
||||
}
|
||||
if got.Zone != tc.wantZone {
|
||||
t.Errorf("Zone got %s; want %s", got.Zone, tc.wantZone)
|
||||
}
|
||||
if got.Name != tc.wantName {
|
||||
t.Errorf("Name got %s; want %s", got.Name, tc.wantName)
|
||||
}
|
||||
if got.Description != tc.wantDesc {
|
||||
t.Errorf("Description got %s; want %s", got.Description, tc.wantDesc)
|
||||
}
|
||||
gotUDJson, err := base64.StdEncoding.DecodeString(got.UserData)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to base64 decode string %q: %s", got.UserData, err)
|
||||
}
|
||||
gotUD := &cloud.EC2UserData{}
|
||||
err = json.Unmarshal([]byte(gotUDJson), gotUD)
|
||||
if err != nil {
|
||||
t.Errorf("unable to unmarshal user data: %v", err)
|
||||
}
|
||||
if gotUD.BuildletBinaryURL != tc.hconf.BuildletBinaryURL(tc.buildEnv) {
|
||||
t.Errorf("buildletBinaryURL got %s; want %s", gotUD.BuildletBinaryURL, tc.hconf.BuildletBinaryURL(tc.buildEnv))
|
||||
}
|
||||
if gotUD.BuildletHostType != tc.hostType {
|
||||
t.Errorf("buildletHostType got %s; want %s", gotUD.BuildletHostType, tc.hostType)
|
||||
}
|
||||
if gotUD.BuildletName != tc.wantBuildletName {
|
||||
t.Errorf("buildletName got %s; want %s", gotUD.BuildletName, tc.wantBuildletName)
|
||||
}
|
||||
if gotUD.BuildletImageURL != tc.wantBuildletImage {
|
||||
t.Errorf("buildletImageURL got %s; want %s", gotUD.BuildletImageURL, tc.wantBuildletImage)
|
||||
}
|
||||
|
||||
if gotUD.TLSCert != tc.opts.TLS.CertPEM {
|
||||
t.Errorf("TLSCert got %s; want %s", gotUD.TLSCert, tc.opts.TLS.CertPEM)
|
||||
}
|
||||
if gotUD.TLSKey != tc.opts.TLS.KeyPEM {
|
||||
t.Errorf("TLSKey got %s; want %s", gotUD.TLSKey, tc.opts.TLS.KeyPEM)
|
||||
}
|
||||
if gotUD.TLSPassword != tc.opts.TLS.Password() {
|
||||
t.Errorf("TLSPassword got %s; want %s", gotUD.TLSPassword, tc.opts.TLS.Password())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"golang.org/x/build/buildlet"
|
||||
"golang.org/x/build/internal/cloud"
|
||||
"golang.org/x/build/pargzip"
|
||||
)
|
||||
|
||||
|
@ -321,7 +322,7 @@ var inKube = os.Getenv("KUBERNETES_SERVICE_HOST") != ""
|
|||
|
||||
var (
|
||||
// ec2UD contains a copy of the EC2 vm user data retrieved from the metadata.
|
||||
ec2UD *buildlet.EC2UserData
|
||||
ec2UD *cloud.EC2UserData
|
||||
// ec2MdC is an EC2 metadata client.
|
||||
ec2MdC *ec2metadata.EC2Metadata
|
||||
)
|
||||
|
@ -342,7 +343,7 @@ func onEC2() bool {
|
|||
|
||||
// mdValueFromUserData maps a metadata key value into the corresponding
|
||||
// EC2UserData value. If a mapping is not found, an empty string is returned.
|
||||
func mdValueFromUserData(ud *buildlet.EC2UserData, key string) string {
|
||||
func mdValueFromUserData(ud *cloud.EC2UserData, key string) string {
|
||||
switch key {
|
||||
case metaKeyTLSCert:
|
||||
return ud.TLSCert
|
||||
|
@ -385,7 +386,7 @@ func metadataValue(key string) string {
|
|||
if err != nil {
|
||||
log.Fatalf("unable to retrieve EC2 user data: %v", err)
|
||||
}
|
||||
ec2UD = &buildlet.EC2UserData{}
|
||||
ec2UD = &cloud.EC2UserData{}
|
||||
err = json.Unmarshal([]byte(ec2MetaJson), ec2UD)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to unmarshal user data json: %v", err)
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"golang.org/x/build/buildenv"
|
||||
"golang.org/x/build/buildlet"
|
||||
"golang.org/x/build/internal/cloud"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -47,7 +47,7 @@ var (
|
|||
isReverse = true
|
||||
isSingleRun = false
|
||||
// ec2UD contains a copy of the EC2 vm user data retrieved from the metadata.
|
||||
ec2UD *buildlet.EC2UserData
|
||||
ec2UD *cloud.EC2UserData
|
||||
// ec2MetaClient is an EC2 metadata client.
|
||||
ec2MetaClient *ec2metadata.EC2Metadata
|
||||
)
|
||||
|
@ -301,7 +301,7 @@ func initEC2Meta() {
|
|||
if err != nil {
|
||||
log.Fatalf("unable to retrieve EC2 user data: %v", err)
|
||||
}
|
||||
ec2UD = &buildlet.EC2UserData{}
|
||||
ec2UD = &cloud.EC2UserData{}
|
||||
err = json.Unmarshal([]byte(ec2MetaJson), ec2UD)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to unmarshal user data json: %v", err)
|
||||
|
|
Загрузка…
Ссылка в новой задаче