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 }