github.com/webonyx/up@v0.7.4-0.20180808230834-91b94e551323/platform/lambda/lambda.go (about)

     1  // Package lambda implements the API Gateway & AWS Lambda platform.
     2  package lambda
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/apex/log"
    16  	"github.com/apex/log/handlers/discard"
    17  	"github.com/aws/aws-sdk-go/aws"
    18  	"github.com/aws/aws-sdk-go/aws/session"
    19  	"github.com/aws/aws-sdk-go/service/acm"
    20  	"github.com/aws/aws-sdk-go/service/apigateway"
    21  	"github.com/aws/aws-sdk-go/service/iam"
    22  	"github.com/aws/aws-sdk-go/service/lambda"
    23  	"github.com/aws/aws-sdk-go/service/route53"
    24  	"github.com/aws/aws-sdk-go/service/s3"
    25  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    26  	"github.com/dchest/uniuri"
    27  	"github.com/dustin/go-humanize"
    28  	"github.com/golang/sync/errgroup"
    29  	"github.com/pkg/errors"
    30  
    31  	"github.com/apex/up"
    32  	"github.com/apex/up/config"
    33  	"github.com/apex/up/internal/proxy/bin"
    34  	"github.com/apex/up/internal/shim"
    35  	"github.com/apex/up/internal/util"
    36  	"github.com/apex/up/internal/zip"
    37  	"github.com/apex/up/platform/aws/domains"
    38  	"github.com/apex/up/platform/aws/logs"
    39  	"github.com/apex/up/platform/aws/runtime"
    40  	"github.com/apex/up/platform/event"
    41  	"github.com/apex/up/platform/lambda/stack"
    42  	"github.com/apex/up/platform/lambda/stack/resources"
    43  )
    44  
    45  // errFirstDeploy is returned from .deploy() when a function is created.
    46  var errFirstDeploy = errors.New("first deploy")
    47  
    48  const (
    49  	// maxCodeSize is the max code size supported by Lambda (250MiB).
    50  	maxCodeSize = 250 << 20
    51  )
    52  
    53  // assume policy for the lambda function.
    54  var apiGatewayAssumePolicy = `{
    55  	"Version": "2012-10-17",
    56  	"Statement": [
    57  		{
    58  			"Effect": "Allow",
    59  			"Principal": {
    60  				"Service": "apigateway.amazonaws.com"
    61  			},
    62  			"Action": "sts:AssumeRole"
    63  		},
    64  		{
    65        "Effect": "Allow",
    66        "Principal": {
    67          "Service": "lambda.amazonaws.com"
    68        },
    69        "Action": "sts:AssumeRole"
    70      }
    71  	]
    72  }`
    73  
    74  // TODO: aggregate progress report for N regions or distinct progress bars
    75  // TODO: refactor with another region-scoped struct to clean this up
    76  
    77  // Platform implementation.
    78  type Platform struct {
    79  	config  *up.Config
    80  	runtime string
    81  	handler string
    82  	zip     *bytes.Buffer
    83  	events  event.Events
    84  }
    85  
    86  // New platform.
    87  func New(c *up.Config, events event.Events) *Platform {
    88  	return &Platform{
    89  		config:  c,
    90  		runtime: c.Lambda.Runtime,
    91  		handler: "_proxy.handle",
    92  		events:  events,
    93  	}
    94  }
    95  
    96  // Build implementation.
    97  func (p *Platform) Build() error {
    98  	start := time.Now()
    99  	p.zip = new(bytes.Buffer)
   100  
   101  	if err := p.injectProxy(); err != nil {
   102  		return errors.Wrap(err, "injecting proxy")
   103  	}
   104  	defer p.removeProxy()
   105  
   106  	r, stats, err := zip.Build(".")
   107  	if err != nil {
   108  		return errors.Wrap(err, "zip")
   109  	}
   110  
   111  	if _, err := io.Copy(p.zip, r); err != nil {
   112  		return errors.Wrap(err, "copying")
   113  	}
   114  
   115  	if err := r.Close(); err != nil {
   116  		return errors.Wrap(err, "closing")
   117  	}
   118  
   119  	p.events.Emit("platform.build.zip", event.Fields{
   120  		"files":             stats.FilesAdded,
   121  		"size_uncompressed": stats.SizeUncompressed,
   122  		"size_compressed":   p.zip.Len(),
   123  		"duration":          time.Since(start),
   124  	})
   125  
   126  	if stats.SizeUncompressed > maxCodeSize {
   127  		size := humanize.Bytes(uint64(stats.SizeUncompressed))
   128  		max := humanize.Bytes(uint64(maxCodeSize))
   129  		return errors.Errorf("zip contents is %s, exceeding Lambda's limit of %s", size, max)
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // Zip returns the zip reader.
   136  func (p *Platform) Zip() io.Reader {
   137  	return p.zip
   138  }
   139  
   140  // Init initializes the runtime.
   141  func (p *Platform) Init(stage string) error {
   142  	return runtime.New(
   143  		p.config,
   144  		runtime.WithLogger(&log.Logger{
   145  			Handler: discard.Default,
   146  		}),
   147  	).Init(stage)
   148  }
   149  
   150  // Deploy implementation.
   151  func (p *Platform) Deploy(d up.Deploy) error {
   152  	regions := p.config.Regions
   153  	var g errgroup.Group
   154  
   155  	if err := p.createRole(); err != nil {
   156  		return errors.Wrap(err, "iam")
   157  	}
   158  
   159  	for _, r := range regions {
   160  		region := r
   161  		g.Go(func() error {
   162  			version, err := p.deploy(region, d)
   163  			if err == nil {
   164  				return nil
   165  			}
   166  
   167  			if err != errFirstDeploy {
   168  				return errors.Wrap(err, region)
   169  			}
   170  
   171  			if err := p.CreateStack(region, version); err != nil {
   172  				return errors.Wrap(err, region)
   173  			}
   174  
   175  			return nil
   176  		})
   177  	}
   178  
   179  	return g.Wait()
   180  }
   181  
   182  // Rollback implementation.
   183  func (p *Platform) Rollback(region, stage, version string) error {
   184  	c := lambda.New(session.New(aws.NewConfig().WithRegion(region)))
   185  	log.Debugf("rolling back %s %s to %q", region, stage, version)
   186  
   187  	// git commit or tag
   188  	if version != "" && !util.IsNumeric(version) {
   189  		log.Debugf("fetching version for %q", version)
   190  		v, err := getAliasVersion(c, p.config.Name, util.EncodeAlias(version))
   191  		if err != nil {
   192  			return errors.Wrapf(err, "fetching alias %q", version)
   193  		}
   194  		log.Debugf("version for %q is %s", version, v)
   195  		version = v
   196  	}
   197  
   198  	// previous version
   199  	if version == "" {
   200  		log.Debug("fetching previous version")
   201  		v, err := getAliasVersion(c, p.config.Name, previous(stage))
   202  		if err != nil {
   203  			return errors.Wrap(err, "fetching previous alias")
   204  		}
   205  		version = v
   206  	}
   207  
   208  	// current version
   209  	curr, err := getAliasVersion(c, p.config.Name, stage)
   210  	if err != nil {
   211  		return errors.Wrap(err, "fetching current alias")
   212  	}
   213  	log.Debugf("current version is %s", curr)
   214  
   215  	// update stage
   216  	if err := p.alias(c, stage, version); err != nil {
   217  		return errors.Wrap(err, "updating alias")
   218  	}
   219  
   220  	// update stage previous
   221  	if err := p.alias(c, previous(stage), curr); err != nil {
   222  		return errors.Wrap(err, "updating previous alias")
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Logs implementation.
   229  func (p *Platform) Logs(c up.LogsConfig) up.Logs {
   230  	g := "/aws/lambda/" + p.config.Name
   231  	return logs.New(g, c)
   232  }
   233  
   234  // Domains implementation.
   235  func (p *Platform) Domains() up.Domains {
   236  	return domains.New()
   237  }
   238  
   239  // Secrets implementation.
   240  func (p *Platform) Secrets(stage string) up.Secrets {
   241  	// TODO: all regions
   242  	return runtime.NewSecrets(p.config.Name, stage, p.config.Regions[0])
   243  }
   244  
   245  // URL returns the stage url.
   246  func (p *Platform) URL(region, stage string) (string, error) {
   247  	s := session.New(aws.NewConfig().WithRegion(region))
   248  	c := apigateway.New(s)
   249  
   250  	api, err := p.getAPI(c)
   251  	if err != nil {
   252  		return "", errors.Wrap(err, "fetching api")
   253  	}
   254  
   255  	if api == nil {
   256  		return "", errors.Errorf("cannot find the API, looks like you haven't deployed")
   257  	}
   258  
   259  	id := fmt.Sprintf("https://%s.execute-api.%s.amazonaws.com/%s/", *api.Id, region, stage)
   260  	return id, nil
   261  }
   262  
   263  // CreateStack implementation.
   264  func (p *Platform) CreateStack(region, version string) error {
   265  	versions := make(resources.Versions)
   266  
   267  	for _, s := range p.config.Stages {
   268  		versions[s.Name] = version
   269  	}
   270  
   271  	if err := p.createCerts(); err != nil {
   272  		return errors.Wrap(err, "creating certs")
   273  	}
   274  
   275  	zones, err := p.getHostedZone()
   276  	if err != nil {
   277  		return errors.Wrap(err, "fetching zones")
   278  	}
   279  
   280  	return stack.New(p.config, p.events, zones, region).Create(versions)
   281  }
   282  
   283  // DeleteStack implementation.
   284  func (p *Platform) DeleteStack(region string, wait bool) error {
   285  	versions := resources.Versions{}
   286  
   287  	for _, s := range p.config.Stages {
   288  		versions[s.Name] = "1"
   289  	}
   290  
   291  	if err := p.createRole(); err != nil {
   292  		return errors.Wrap(err, "creating iam role")
   293  	}
   294  
   295  	log.Debug("deleting bucket objects")
   296  	if err := p.deleteBucketObjects(region); err != nil && !util.IsNotFound(err) {
   297  		return errors.Wrap(err, "deleting s3 objects")
   298  	}
   299  
   300  	log.Debug("deleting stack")
   301  	if err := stack.New(p.config, p.events, nil, region).Delete(versions, wait); err != nil && !util.IsNotFound(err) {
   302  		return errors.Wrap(err, "deleting stack")
   303  	}
   304  
   305  	log.Debug("deleting function")
   306  	if err := p.deleteFunction(region); err != nil && !util.IsNotFound(err) {
   307  		return errors.Wrap(err, "deleting function")
   308  	}
   309  
   310  	log.Debug("deleting role")
   311  	if err := p.deleteRole(region); err != nil && !util.IsNotFound(err) {
   312  		return errors.Wrap(err, "deleting function iam role")
   313  	}
   314  
   315  	return nil
   316  }
   317  
   318  // ShowStack implementation.
   319  func (p *Platform) ShowStack(region string) error {
   320  	return stack.New(p.config, p.events, nil, region).Show()
   321  }
   322  
   323  // PlanStack implementation.
   324  func (p *Platform) PlanStack(region string) error {
   325  	versions, err := p.getAliasVersions(region)
   326  	if err != nil {
   327  		return errors.Wrap(err, "fetching alias versions")
   328  	}
   329  
   330  	if err := p.createCerts(); err != nil {
   331  		return errors.Wrap(err, "creating certs")
   332  	}
   333  
   334  	zones, err := p.getHostedZone()
   335  	if err != nil {
   336  		return errors.Wrap(err, "fetching zones")
   337  	}
   338  
   339  	return stack.New(p.config, p.events, zones, region).Plan(versions)
   340  }
   341  
   342  // ApplyStack implementation.
   343  func (p *Platform) ApplyStack(region string) error {
   344  	if err := p.createCerts(); err != nil {
   345  		return errors.Wrap(err, "creating certs")
   346  	}
   347  
   348  	return stack.New(p.config, p.events, nil, region).Apply()
   349  }
   350  
   351  // Exists implementation.
   352  func (p *Platform) Exists(region string) (bool, error) {
   353  	log.Debug("checking if application exists")
   354  	c := lambda.New(session.New(aws.NewConfig().WithRegion(region)))
   355  
   356  	_, err := c.GetFunctionConfiguration(&lambda.GetFunctionConfigurationInput{
   357  		FunctionName: &p.config.Name,
   358  	})
   359  
   360  	if util.IsNotFound(err) {
   361  		return false, nil
   362  	}
   363  
   364  	if err != nil {
   365  		return false, err
   366  	}
   367  
   368  	return true, nil
   369  }
   370  
   371  // getAliasVersions returns the function alias versions.
   372  func (p *Platform) getAliasVersions(region string) (resources.Versions, error) {
   373  	var g errgroup.Group
   374  	var mu sync.Mutex
   375  
   376  	c := lambda.New(session.New(aws.NewConfig().WithRegion(region)))
   377  	versions := make(resources.Versions)
   378  
   379  	log.Debug("fetching aliases")
   380  	for _, s := range p.config.Stages {
   381  		s := s
   382  
   383  		g.Go(func() error {
   384  			log.Debugf("fetching %s alias", s.Name)
   385  			version, err := p.getAliasVersion(c, s.Name)
   386  
   387  			if util.IsNotFound(err) {
   388  				log.Debugf("%s has no alias, defaulting to staging", s.Name)
   389  				version, err = p.getAliasVersion(c, "staging")
   390  				if err != nil {
   391  					return errors.Wrap(err, "fetching staging alias")
   392  				}
   393  			}
   394  
   395  			if err != nil {
   396  				return errors.Wrapf(err, "fetching %q alias", s.Name)
   397  			}
   398  
   399  			log.Debugf("fetched %s alias (%s)", s.Name, version)
   400  			mu.Lock()
   401  			versions[s.Name] = version
   402  			mu.Unlock()
   403  
   404  			return nil
   405  		})
   406  	}
   407  
   408  	return versions, g.Wait()
   409  }
   410  
   411  // getAliasVersion retruns the alias version for a stage.
   412  func (p *Platform) getAliasVersion(c *lambda.Lambda, stage string) (string, error) {
   413  	res, err := c.GetAlias(&lambda.GetAliasInput{
   414  		FunctionName: &p.config.Name,
   415  		Name:         &stage,
   416  	})
   417  
   418  	if err != nil {
   419  		return "", err
   420  	}
   421  
   422  	return *res.FunctionVersion, nil
   423  }
   424  
   425  // getHostedZone returns existing hosted zones.
   426  func (p *Platform) getHostedZone() (zones []*route53.HostedZone, err error) {
   427  	r := route53.New(session.New(aws.NewConfig()))
   428  
   429  	log.Debug("fetching hosted zones")
   430  	res, err := r.ListHostedZonesByName(&route53.ListHostedZonesByNameInput{
   431  		MaxItems: aws.String("100"),
   432  	})
   433  
   434  	if err != nil {
   435  		return
   436  	}
   437  
   438  	zones = res.HostedZones
   439  	return
   440  }
   441  
   442  // createCerts creates the certificates if necessary.
   443  //
   444  // We perform this task outside of CloudFormation because
   445  // the certificates currently must be created in the us-east-1
   446  // region. This also gives us a chance to let the user know
   447  // that they have to confirm an email.
   448  func (p *Platform) createCerts() error {
   449  	s := session.New(aws.NewConfig().WithRegion("us-east-1"))
   450  	a := acm.New(s)
   451  	var domains []string
   452  
   453  	// existing certs
   454  	log.Debug("fetching existing certs")
   455  	certs, err := getCerts(a)
   456  	if err != nil {
   457  		return errors.Wrap(err, "fetching certs")
   458  	}
   459  
   460  	// request certs
   461  	for _, s := range p.config.Stages.List() {
   462  		if s.Domain == "" {
   463  			continue
   464  		}
   465  
   466  		certDomains := util.CertDomainNames(s.Domain)
   467  
   468  		// see if the cert exists
   469  		log.Debugf("looking up cert for %s", s.Domain)
   470  		arn := getCert(certs, s.Domain)
   471  		if arn != "" {
   472  			log.Debugf("found cert for %s: %s", s.Domain, arn)
   473  			s.Cert = arn
   474  			continue
   475  		}
   476  
   477  		option := acm.DomainValidationOption{
   478  			DomainName:       aws.String(certDomains[0]),
   479  			ValidationDomain: aws.String(util.Domain(s.Domain)),
   480  		}
   481  
   482  		options := []*acm.DomainValidationOption{
   483  			&option,
   484  		}
   485  
   486  		// request the cert
   487  		res, err := a.RequestCertificate(&acm.RequestCertificateInput{
   488  			DomainName:              aws.String(certDomains[0]),
   489  			DomainValidationOptions: options,
   490  			SubjectAlternativeNames: aws.StringSlice(certDomains[1:]),
   491  		})
   492  
   493  		if err != nil {
   494  			return errors.Wrapf(err, "requesting cert for %v", certDomains)
   495  		}
   496  
   497  		domains = append(domains, certDomains[0])
   498  		s.Cert = *res.CertificateArn
   499  	}
   500  
   501  	// no certs needed
   502  	if len(domains) == 0 {
   503  		return nil
   504  	}
   505  
   506  	defer p.events.Time("platform.certs.create", event.Fields{
   507  		"domains": domains,
   508  	})()
   509  
   510  	// wait for approval
   511  	for range time.Tick(4 * time.Second) {
   512  		res, err := a.ListCertificates(&acm.ListCertificatesInput{
   513  			MaxItems:            aws.Int64(1000),
   514  			CertificateStatuses: aws.StringSlice([]string{acm.CertificateStatusPendingValidation}),
   515  		})
   516  
   517  		if err != nil {
   518  			return errors.Wrap(err, "listing")
   519  		}
   520  
   521  		if len(res.CertificateSummaryList) == 0 {
   522  			break
   523  		}
   524  	}
   525  
   526  	return nil
   527  }
   528  
   529  // deploy to the given region.
   530  func (p *Platform) deploy(region string, d up.Deploy) (version string, err error) {
   531  	start := time.Now()
   532  
   533  	fields := event.Fields{
   534  		"commit": d.Commit,
   535  		"stage":  d.Stage,
   536  		"region": region,
   537  	}
   538  
   539  	p.events.Emit("platform.deploy", fields)
   540  
   541  	defer func() {
   542  		fields["duration"] = time.Since(start)
   543  		fields["commit"] = d.Commit
   544  		fields["version"] = version
   545  		p.events.Emit("platform.deploy.complete", fields)
   546  	}()
   547  
   548  	ctx := log.WithField("region", region)
   549  	s := session.New(aws.NewConfig().WithRegion(region).WithS3UseAccelerate(p.config.Lambda.Accelerate))
   550  	u := s3manager.NewUploaderWithClient(s3.New(s))
   551  	a := apigateway.New(s)
   552  	c := lambda.New(s)
   553  
   554  	ctx.Debug("fetching function config")
   555  	_, err = c.GetFunctionConfiguration(&lambda.GetFunctionConfigurationInput{
   556  		FunctionName: &p.config.Name,
   557  	})
   558  
   559  	if util.IsNotFound(err) {
   560  		defer p.events.Time("platform.function.create", fields)
   561  		return p.createFunction(c, a, u, region, d)
   562  	}
   563  
   564  	if err != nil {
   565  		return "", errors.Wrap(err, "fetching function config")
   566  	}
   567  
   568  	defer p.events.Time("platform.function.update", fields)
   569  	return p.updateFunction(c, a, u, region, d)
   570  }
   571  
   572  // createFunction creates the function.
   573  func (p *Platform) createFunction(c *lambda.Lambda, a *apigateway.APIGateway, up *s3manager.Uploader, region string, d up.Deploy) (version string, err error) {
   574  	// ensure bucket exists
   575  	if err := p.createBucket(region); err != nil {
   576  		return "", errors.Wrap(err, "creating s3 bucket")
   577  	}
   578  
   579  	// upload to s3
   580  	log.Debug("uploading function")
   581  	b := aws.String(p.getS3BucketName(region))
   582  	k := aws.String(p.getS3Key(d.Stage))
   583  
   584  	_, err = up.Upload(&s3manager.UploadInput{
   585  		Bucket: b,
   586  		Key:    k,
   587  		Body:   bytes.NewReader(p.zip.Bytes()),
   588  	})
   589  
   590  	if err != nil {
   591  		return "", errors.Wrap(err, "uploading function")
   592  	}
   593  
   594  	// load environment
   595  	env, err := p.loadEnvironment(d)
   596  	if err != nil {
   597  		return "", errors.Wrap(err, "loading environment variables")
   598  	}
   599  
   600  	// create function
   601  retry:
   602  	log.Debug("creating function")
   603  	res, err := c.CreateFunction(&lambda.CreateFunctionInput{
   604  		FunctionName: &p.config.Name,
   605  		Handler:      &p.handler,
   606  		Runtime:      &p.runtime,
   607  		Role:         &p.config.Lambda.Role,
   608  		MemorySize:   aws.Int64(int64(p.config.Lambda.Memory)),
   609  		Timeout:      aws.Int64(int64(p.config.Proxy.Timeout + 3)),
   610  		Publish:      aws.Bool(true),
   611  		Environment:  env,
   612  		Code: &lambda.FunctionCode{
   613  			S3Bucket: b,
   614  			S3Key:    k,
   615  		},
   616  		VpcConfig: p.vpc(),
   617  	})
   618  
   619  	// IAM is eventually consistent apparently, so we have to keep retrying
   620  	if isCreatingRole(err) {
   621  		log.Debug("waiting for role to be created")
   622  		time.Sleep(500 * time.Millisecond)
   623  		goto retry
   624  	}
   625  
   626  	if err != nil {
   627  		return "", errors.Wrap(err, "creating function")
   628  	}
   629  
   630  	return *res.Version, errFirstDeploy
   631  }
   632  
   633  // updateFunction updates the function.
   634  func (p *Platform) updateFunction(c *lambda.Lambda, a *apigateway.APIGateway, up *s3manager.Uploader, region string, d up.Deploy) (version string, err error) {
   635  	b := aws.String(p.getS3BucketName(region))
   636  	k := aws.String(p.getS3Key(d.Stage))
   637  
   638  	// ensure bucket exists
   639  	if err := p.createBucket(region); err != nil {
   640  		return "", errors.Wrap(err, "creating s3 bucket")
   641  	}
   642  
   643  	// upload
   644  	log.Debug("uploading function")
   645  	_, err = up.Upload(&s3manager.UploadInput{
   646  		Bucket: b,
   647  		Key:    k,
   648  		Body:   bytes.NewReader(p.zip.Bytes()),
   649  	})
   650  
   651  	if err != nil {
   652  		return "", errors.Wrap(err, "uploading function")
   653  	}
   654  
   655  	// load environment
   656  	env, err := p.loadEnvironment(d)
   657  	if err != nil {
   658  		return "", errors.Wrap(err, "loading environment variables")
   659  	}
   660  
   661  	// update function config
   662  	log.Debug("updating function")
   663  	_, err = c.UpdateFunctionConfiguration(&lambda.UpdateFunctionConfigurationInput{
   664  		FunctionName: &p.config.Name,
   665  		Handler:      &p.handler,
   666  		Runtime:      &p.runtime,
   667  		Role:         &p.config.Lambda.Role,
   668  		MemorySize:   aws.Int64(int64(p.config.Lambda.Memory)),
   669  		Timeout:      aws.Int64(int64(p.config.Proxy.Timeout + 3)),
   670  		Environment:  env,
   671  		VpcConfig:    p.vpc(),
   672  	})
   673  
   674  	if err != nil {
   675  		return "", errors.Wrap(err, "updating function config")
   676  	}
   677  
   678  	// update function code
   679  	log.Debug("updating function code")
   680  	res, err := c.UpdateFunctionCode(&lambda.UpdateFunctionCodeInput{
   681  		FunctionName: &p.config.Name,
   682  		Publish:      aws.Bool(true),
   683  		S3Bucket:     b,
   684  		S3Key:        k,
   685  	})
   686  
   687  	if err != nil {
   688  		return "", errors.Wrap(err, "updating function code")
   689  	}
   690  
   691  	// get current alias
   692  	curr, err := getAliasVersion(c, p.config.Name, d.Stage)
   693  	if err != nil {
   694  		return "", errors.Wrap(err, "fetching current version")
   695  	}
   696  
   697  	// create stage alias
   698  	if err := p.alias(c, d.Stage, *res.Version); err != nil {
   699  		return "", errors.Wrapf(err, "creating function stage %q alias", d.Stage)
   700  	}
   701  
   702  	// create previous alias
   703  	if err := p.alias(c, previous(d.Stage), curr); err != nil {
   704  		return "", errors.Wrapf(err, "creating function %q alias", d.Stage)
   705  	}
   706  
   707  	// create git alias
   708  	if d.Commit != "" {
   709  		if err := p.alias(c, util.EncodeAlias(d.Commit), *res.Version); err != nil {
   710  			return "", errors.Wrapf(err, "creating function git %q alias", d.Commit)
   711  		}
   712  	}
   713  
   714  	if err := p.alias(c, previous(d.Stage), curr); err != nil {
   715  		return "", errors.Wrapf(err, "creating function %q alias", d.Stage)
   716  	}
   717  
   718  	return *res.Version, nil
   719  }
   720  
   721  // vpc returns the vpc configuration or nil.
   722  func (p *Platform) vpc() *lambda.VpcConfig {
   723  	v := p.config.Lambda.VPC
   724  	if v == nil {
   725  		return nil
   726  	}
   727  
   728  	return &lambda.VpcConfig{
   729  		SubnetIds:        aws.StringSlice(v.Subnets),
   730  		SecurityGroupIds: aws.StringSlice(v.SecurityGroups),
   731  	}
   732  }
   733  
   734  // alias creates or updates an alias.
   735  func (p *Platform) alias(c *lambda.Lambda, alias, version string) error {
   736  	log.Debugf("alias %s to %s", alias, version)
   737  	_, err := c.UpdateAlias(&lambda.UpdateAliasInput{
   738  		FunctionName:    &p.config.Name,
   739  		FunctionVersion: &version,
   740  		Name:            &alias,
   741  		Description:     aws.String(util.ManagedByUp("")),
   742  	})
   743  
   744  	if util.IsNotFound(err) {
   745  		_, err = c.CreateAlias(&lambda.CreateAliasInput{
   746  			FunctionName:    &p.config.Name,
   747  			FunctionVersion: &version,
   748  			Name:            &alias,
   749  			Description:     aws.String(util.ManagedByUp("")),
   750  		})
   751  	}
   752  
   753  	return err
   754  }
   755  
   756  // deleteFunction deletes the lambda function.
   757  func (p *Platform) deleteFunction(region string) error {
   758  	// TODO: sessions all over... refactor
   759  	c := lambda.New(session.New(aws.NewConfig().WithRegion(region)))
   760  
   761  	_, err := c.DeleteFunction(&lambda.DeleteFunctionInput{
   762  		FunctionName: &p.config.Name,
   763  	})
   764  
   765  	return err
   766  }
   767  
   768  // loadEnvironment loads environment variables.
   769  func (p *Platform) loadEnvironment(d up.Deploy) (*lambda.Environment, error) {
   770  	m := aws.StringMap(p.config.Environment)
   771  	m["UP_STAGE"] = &d.Stage
   772  	m["UP_COMMIT"] = &d.Commit
   773  	m["UP_AUTHOR"] = &d.Author
   774  	return &lambda.Environment{
   775  		Variables: m,
   776  	}, nil
   777  }
   778  
   779  // createRole creates the IAM role unless it is present.
   780  func (p *Platform) createRole() error {
   781  	s := session.New(aws.NewConfig())
   782  	c := iam.New(s)
   783  
   784  	name := p.roleName()
   785  	desc := util.ManagedByUp("")
   786  
   787  	// role is provided
   788  	if s := p.config.Lambda.Role; s != "" {
   789  		log.Debugf("using role from config %s", s)
   790  		return nil
   791  	}
   792  
   793  	log.Debug("checking for role")
   794  	existing, err := c.GetRole(&iam.GetRoleInput{
   795  		RoleName: &name,
   796  	})
   797  
   798  	// network or permission error
   799  	if err != nil && !util.IsNotFound(err) {
   800  		return errors.Wrap(err, "fetching role")
   801  	}
   802  
   803  	// use the existing role
   804  	if err == nil {
   805  		log.Debug("found existing role")
   806  
   807  		if err := p.updateRole(c); err != nil {
   808  			return errors.Wrap(err, "updating role policy")
   809  		}
   810  
   811  		p.setRoleARN(*existing.Role.Arn)
   812  		return nil
   813  	}
   814  
   815  	log.Debug("creating role")
   816  	role, err := c.CreateRole(&iam.CreateRoleInput{
   817  		RoleName:                 &name,
   818  		Description:              &desc,
   819  		AssumeRolePolicyDocument: &apiGatewayAssumePolicy,
   820  	})
   821  
   822  	if err != nil {
   823  		return errors.Wrap(err, "creating role")
   824  	}
   825  
   826  	if err := p.updateRole(c); err != nil {
   827  		return errors.Wrap(err, "updating role policy")
   828  	}
   829  
   830  	p.setRoleARN(*role.Role.Arn)
   831  
   832  	return nil
   833  }
   834  
   835  // updateRole updates the IAM role.
   836  func (p *Platform) updateRole(c *iam.IAM) error {
   837  	name := p.roleName()
   838  
   839  	policy, err := p.functionPolicy()
   840  	if err != nil {
   841  		return errors.Wrap(err, "creating function policy")
   842  	}
   843  
   844  	log.Debug("updating role policy")
   845  	_, err = c.PutRolePolicy(&iam.PutRolePolicyInput{
   846  		PolicyName:     &name,
   847  		RoleName:       &name,
   848  		PolicyDocument: &policy,
   849  	})
   850  
   851  	return err
   852  }
   853  
   854  // setRoleARN sets the role ARN.
   855  func (p *Platform) setRoleARN(arn string) {
   856  	log.Debugf("set role to %s", arn)
   857  	p.config.Lambda.Role = arn
   858  }
   859  
   860  // roleName returns the IAM role name.
   861  func (p *Platform) roleName() string {
   862  	return fmt.Sprintf("%s-function", p.config.Name)
   863  }
   864  
   865  // deleteRole deletes the role and policy.
   866  func (p *Platform) deleteRole(region string) error {
   867  	name := fmt.Sprintf("%s-function", p.config.Name)
   868  	c := iam.New(session.New(aws.NewConfig().WithRegion(region)))
   869  
   870  	_, err := c.DeleteRolePolicy(&iam.DeleteRolePolicyInput{
   871  		RoleName:   &name,
   872  		PolicyName: &name,
   873  	})
   874  
   875  	if err != nil {
   876  		return errors.Wrap(err, "deleting policy")
   877  	}
   878  
   879  	_, err = c.DeleteRole(&iam.DeleteRoleInput{
   880  		RoleName: &name,
   881  	})
   882  
   883  	if err != nil {
   884  		return errors.Wrap(err, "deleting iam role")
   885  	}
   886  
   887  	return nil
   888  }
   889  
   890  // createBucket creates the bucket.
   891  func (p *Platform) createBucket(region string) error {
   892  	s := s3.New(session.New(aws.NewConfig().WithRegion(region)))
   893  	n := p.getS3BucketName(region)
   894  
   895  	log.WithField("name", n).Debug("ensuring s3 bucket exists")
   896  	_, err := s.CreateBucket(&s3.CreateBucketInput{
   897  		Bucket: &n,
   898  	})
   899  
   900  	if err != nil && !util.IsBucketExists(err) {
   901  		return errors.Wrap(err, "creating bucket")
   902  	}
   903  
   904  	_, err = s.PutBucketAccelerateConfiguration(&s3.PutBucketAccelerateConfigurationInput{
   905  		Bucket: &n,
   906  		AccelerateConfiguration: &s3.AccelerateConfiguration{
   907  			Status: aws.String(s3.BucketAccelerateStatusEnabled),
   908  		},
   909  	})
   910  
   911  	if err != nil {
   912  		return errors.Wrap(err, "updating acceleration status")
   913  	}
   914  
   915  	return nil
   916  }
   917  
   918  // deleteBucketObjects deletes the objects for the app.
   919  func (p *Platform) deleteBucketObjects(region string) error {
   920  	s := s3.New(session.New(aws.NewConfig().WithRegion(region)))
   921  	b := aws.String(p.getS3BucketName(region))
   922  	prefix := p.config.Name + "/"
   923  
   924  	params := &s3.ListObjectsInput{
   925  		Bucket: b,
   926  		Prefix: &prefix,
   927  	}
   928  
   929  	return s.ListObjectsPages(params, func(page *s3.ListObjectsOutput, lastPage bool) bool {
   930  		for _, c := range page.Contents {
   931  			ctx := log.WithField("key", *c.Key)
   932  
   933  			ctx.Debug("deleting object")
   934  			_, err := s.DeleteObject(&s3.DeleteObjectInput{
   935  				Bucket: b,
   936  				Key:    c.Key,
   937  			})
   938  
   939  			if err != nil {
   940  				ctx.WithError(err).Warn("deleting object")
   941  			}
   942  		}
   943  
   944  		return *page.IsTruncated
   945  	})
   946  }
   947  
   948  // getAPI returns the API if present or nil.
   949  func (p *Platform) getAPI(c *apigateway.APIGateway) (api *apigateway.RestApi, err error) {
   950  	name := p.config.Name
   951  
   952  	res, err := c.GetRestApis(&apigateway.GetRestApisInput{
   953  		Limit: aws.Int64(500),
   954  	})
   955  
   956  	if err != nil {
   957  		return nil, errors.Wrap(err, "fetching apis")
   958  	}
   959  
   960  	for _, a := range res.Items {
   961  		if *a.Name == name {
   962  			api = a
   963  		}
   964  	}
   965  
   966  	return
   967  }
   968  
   969  // injectProxy injects the Go proxy.
   970  func (p *Platform) injectProxy() error {
   971  	log.Debugf("injecting proxy")
   972  
   973  	if err := ioutil.WriteFile("main", bin.MustAsset("up-proxy"), 0777); err != nil {
   974  		return errors.Wrap(err, "writing up-proxy")
   975  	}
   976  
   977  	if err := ioutil.WriteFile("byline.js", shim.MustAsset("byline.js"), 0755); err != nil {
   978  		return errors.Wrap(err, "writing byline.js")
   979  	}
   980  
   981  	if err := ioutil.WriteFile("_proxy.js", shim.MustAsset("index.js"), 0755); err != nil {
   982  		return errors.Wrap(err, "writing _proxy.js")
   983  	}
   984  
   985  	return nil
   986  }
   987  
   988  // removeProxy removes the Go proxy.
   989  func (p *Platform) removeProxy() error {
   990  	log.Debugf("removing proxy")
   991  	os.Remove("main")
   992  	os.Remove("_proxy.js")
   993  	os.Remove("byline.js")
   994  	return nil
   995  }
   996  
   997  // getS3Key returns a randomized s3 key.
   998  func (p *Platform) getS3Key(stage string) string {
   999  	ts := time.Now().Unix()
  1000  	uid := uniuri.New()
  1001  	return fmt.Sprintf("%s/%s/%d-%s.zip", p.config.Name, stage, ts, uid)
  1002  }
  1003  
  1004  // getS3BucketName returns the s3 bucket name.
  1005  func (p *Platform) getS3BucketName(region string) string {
  1006  	return fmt.Sprintf("up-%s-%s", p.getAccountID(), region)
  1007  }
  1008  
  1009  // getAccountID returns the AWS account id derived from Lambda role,
  1010  // which is currently always present, implicitly or explicitly.
  1011  func (p *Platform) getAccountID() string {
  1012  	return strings.Split(p.config.Lambda.Role, ":")[4]
  1013  }
  1014  
  1015  // functionPolicy returns the IAM function role policy.
  1016  func (p *Platform) functionPolicy() (string, error) {
  1017  	policy := struct {
  1018  		Version   string
  1019  		Statement []config.IAMPolicyStatement
  1020  	}{
  1021  		Version:   "2012-10-17",
  1022  		Statement: p.config.Lambda.Policy,
  1023  	}
  1024  
  1025  	b, err := json.MarshalIndent(policy, "", "  ")
  1026  	if err != nil {
  1027  		return "", err
  1028  	}
  1029  
  1030  	return string(b), nil
  1031  }
  1032  
  1033  // isCreatingRole returns true if the role has not been created.
  1034  func isCreatingRole(err error) bool {
  1035  	return err != nil && strings.Contains(err.Error(), "role defined for the function cannot be assumed by Lambda")
  1036  }
  1037  
  1038  // getCerts returns the certificates available.
  1039  func getCerts(a *acm.ACM) (certs []*acm.CertificateDetail, err error) {
  1040  	var g errgroup.Group
  1041  	var mu sync.Mutex
  1042  
  1043  	res, err := a.ListCertificates(&acm.ListCertificatesInput{
  1044  		MaxItems: aws.Int64(1000),
  1045  	})
  1046  
  1047  	if err != nil {
  1048  		return nil, errors.Wrap(err, "listing")
  1049  	}
  1050  
  1051  	for _, c := range res.CertificateSummaryList {
  1052  		c := c
  1053  		g.Go(func() error {
  1054  			res, err := a.DescribeCertificate(&acm.DescribeCertificateInput{
  1055  				CertificateArn: c.CertificateArn,
  1056  			})
  1057  
  1058  			if err != nil {
  1059  				return errors.Wrap(err, "describing")
  1060  			}
  1061  
  1062  			mu.Lock()
  1063  			certs = append(certs, res.Certificate)
  1064  			mu.Unlock()
  1065  			return nil
  1066  		})
  1067  	}
  1068  
  1069  	err = g.Wait()
  1070  	return
  1071  }
  1072  
  1073  // getCert returns the ARN of a certificate with can satisfy domain,
  1074  // favoring more specific certificates, then falling back on wildcards.
  1075  func getCert(certs []*acm.CertificateDetail, domain string) string {
  1076  	// exact domain
  1077  	for _, c := range certs {
  1078  		if *c.DomainName == domain {
  1079  			return *c.CertificateArn
  1080  		}
  1081  	}
  1082  
  1083  	// exact alt
  1084  	for _, c := range certs {
  1085  		for _, a := range c.SubjectAlternativeNames {
  1086  			if *a == domain {
  1087  				return *c.CertificateArn
  1088  			}
  1089  		}
  1090  	}
  1091  
  1092  	// wildcards
  1093  	for _, c := range certs {
  1094  		if util.WildcardMatches(*c.DomainName, domain) {
  1095  			return *c.CertificateArn
  1096  		}
  1097  
  1098  		for _, a := range c.SubjectAlternativeNames {
  1099  			if util.WildcardMatches(*a, domain) {
  1100  				return *c.CertificateArn
  1101  			}
  1102  		}
  1103  	}
  1104  
  1105  	return ""
  1106  }
  1107  
  1108  // getAliasVersion returns the alias version if it is present, or an error.
  1109  func getAliasVersion(c *lambda.Lambda, name, alias string) (string, error) {
  1110  	res, err := c.GetAlias(&lambda.GetAliasInput{
  1111  		FunctionName: &name,
  1112  		Name:         &alias,
  1113  	})
  1114  
  1115  	if err != nil {
  1116  		return "", errors.Wrap(err, "fetching alias")
  1117  	}
  1118  
  1119  	return *res.FunctionVersion, nil
  1120  }
  1121  
  1122  // previous returns the "previous" alias.
  1123  func previous(s string) string {
  1124  	return s + "-previous"
  1125  }