зеркало из https://github.com/golang/build.git
158 строки
5.2 KiB
Go
158 строки
5.2 KiB
Go
// 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 during 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 environment")
|
|
}
|
|
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.DeleteIn == 0 {
|
|
// Note: This implements a short default in the rare case the caller doesn't care.
|
|
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
|
|
}
|