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

     1  package resources
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/apex/up/config"
     7  	"github.com/apex/up/internal/util"
     8  )
     9  
    10  // warmingFunctionSource is the source code.
    11  var warmingFunctionSource = `
    12  const http = require('https')
    13  
    14  exports.handle = function(e, ctx, fn) {
    15    const start = Date.now()
    16    let pending = e.count
    17    console.log('requesting %d', e.count)
    18  
    19    for (let i = 0; i < e.count; i++) {
    20      console.log('GET %s', e.url)
    21      http.get(e.url, function (res) {
    22        const d = Date.now() - start
    23        console.log('GET %s -> %s (%dms)', e.url, res.statusCode, d)
    24        --pending || fn()
    25      })
    26    }
    27  }`
    28  
    29  // warming resources.
    30  func warming(c *Config, m Map) {
    31  	for _, s := range c.Stages.List() {
    32  		if s.IsRemote() {
    33  			warmingStage(c, s, m)
    34  		}
    35  	}
    36  }
    37  
    38  // warmingStage sets up the warming resources for a given stage.
    39  func warmingStage(c *Config, s *config.Stage, m Map) {
    40  	// TODO: refactor overrides so this is not necessary
    41  	g := c.Lambda
    42  	l := s.Lambda
    43  
    44  	if l.Warm == nil {
    45  		l.Warm = g.Warm
    46  	}
    47  
    48  	if l.WarmCount == 0 {
    49  		l.WarmCount = g.WarmCount
    50  	}
    51  
    52  	if l.WarmRate == 0 {
    53  		l.WarmRate = g.WarmRate
    54  	}
    55  
    56  	if l.Warm == nil || !*l.Warm {
    57  		return
    58  	}
    59  
    60  	warmingFunctionRole(c, m)
    61  	warmingFunction(c, m)
    62  	eventID := warmingStageEvent(c, s, &l, m)
    63  	warmingStageFunctionPermission(c, s, m, eventID)
    64  }
    65  
    66  // warmingStageFunctionPermission sets up function permissions.
    67  func warmingStageFunctionPermission(c *Config, s *config.Stage, m Map, eventID string) {
    68  	id := util.Camelcase("warming_function_permission_%s", s.Name)
    69  	m[id] = Map{
    70  		"Type": "AWS::Lambda::Permission",
    71  		"Properties": Map{
    72  			"FunctionName": ref("WarmingFunction"),
    73  			"Action":       "lambda:InvokeFunction",
    74  			"Principal":    "events.amazonaws.com",
    75  			"SourceArn":    get(eventID, "Arn"),
    76  		},
    77  	}
    78  }
    79  
    80  // warmingStageEvent sets up a warming scheduled event.
    81  func warmingStageEvent(c *Config, s *config.Stage, l *config.Lambda, m Map) string {
    82  	url := endpoint(s.Name)
    83  	input := join("", `{ "url": "`, url, fmt.Sprintf(`", "count": %d }`, l.WarmCount))
    84  	id := util.Camelcase("warming_event_%s", s.Name)
    85  
    86  	m[id] = Map{
    87  		"Type": "AWS::Events::Rule",
    88  		"Properties": Map{
    89  			"State":              "ENABLED",
    90  			"Description":        util.ManagedByUp("Warming function scheduled event"),
    91  			"ScheduleExpression": rate(l.WarmRate),
    92  			"Targets": []Map{
    93  				{
    94  					"Arn":   get("WarmingFunction", "Arn"),
    95  					"Id":    "WarmingFunction",
    96  					"Input": input,
    97  				},
    98  			},
    99  		},
   100  	}
   101  
   102  	return id
   103  }
   104  
   105  // warmingFunction sets up a scheduled function for warming.
   106  func warmingFunction(c *Config, m Map) {
   107  	m["WarmingFunction"] = Map{
   108  		"Type": "AWS::Lambda::Function",
   109  		"Properties": Map{
   110  			"FunctionName": fmt.Sprintf("%s-warming", c.Name),
   111  			"Description":  util.ManagedByUp("Warming function"),
   112  			"Runtime":      "nodejs6.10",
   113  			"Handler":      "index.handle",
   114  			"Role":         get("WarmingFunctionRole", "Arn"),
   115  			"MemorySize":   512,
   116  			"Timeout":      300,
   117  			"Code": Map{
   118  				"ZipFile": warmingFunctionSource,
   119  			},
   120  		},
   121  	}
   122  }
   123  
   124  // warmingFunctionRole sets up the warming function role.
   125  func warmingFunctionRole(c *Config, m Map) {
   126  	m["WarmingFunctionRole"] = Map{
   127  		"Type": "AWS::IAM::Role",
   128  		"Properties": Map{
   129  			"RoleName": fmt.Sprintf("%s-warming-function", c.Name),
   130  			"AssumeRolePolicyDocument": Map{
   131  				"Version": "2012-10-17",
   132  				"Statement": []Map{
   133  					{
   134  						"Effect": "Allow",
   135  						"Principal": Map{
   136  							"Service": []string{"lambda.amazonaws.com"},
   137  						},
   138  						"Action": []string{"sts:AssumeRole"},
   139  					},
   140  				},
   141  			},
   142  			"Path": "/",
   143  			"Policies": []Map{
   144  				{
   145  					"PolicyName": "root",
   146  					"PolicyDocument": Map{
   147  						"Version": "2012-10-17",
   148  						"Statement": []Map{
   149  							{
   150  								"Effect":   "Allow",
   151  								"Action":   []string{"logs:*"},
   152  								"Resource": "arn:aws:logs:*:*:*",
   153  							},
   154  						},
   155  					},
   156  				},
   157  			},
   158  		},
   159  	}
   160  }
   161  
   162  // rate returns a rate string.
   163  func rate(d config.Duration) string {
   164  	switch m := d.Seconds() / 60; {
   165  	case m == 1:
   166  		return "rate(1 minute)"
   167  	default:
   168  		return fmt.Sprintf("rate(%0.0f minutes)", m)
   169  	}
   170  }
   171  
   172  // endpoint returns the api endpoint for stage.
   173  func endpoint(stage string) Map {
   174  	path := fmt.Sprintf("/%s/_ping", stage)
   175  	return join("", "https://", ref("Api"), ".execute-api.", ref("AWS::Region"), ".amazonaws.com", path)
   176  }