lamma 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ require 'pathname'
2
+ require 'lamma/runtime'
3
+
4
+ module Lamma
5
+ class CLI::Init
6
+ include Thor::Actions
7
+ attr_reader :target, :thor, :options, :runtime
8
+
9
+ def initialize(options, function_name, thor)
10
+ @options = options
11
+ @thor = thor
12
+ runtime_str = options.fetch('runtime') { raise ArgumentError.new('runtime must be set.') }
13
+ @runtime = Lamma::Runtime.new(runtime_str)
14
+ @target = Pathname.pwd.join(function_name)
15
+ end
16
+
17
+ def run
18
+ if Dir.exist?(target)
19
+ abort("Directory '#{target}' already exists.")
20
+ end
21
+
22
+ tpath = File.join(File.dirname(__FILE__), '..', 'templates', runtime.to_dirname)
23
+ templates = Dir.glob(File.join(tpath, '**/*.erb')).map do |path|
24
+ tar_file = path[tpath.length..path.length - 5] # /foo/bar/templates/RUNTIME/baz/lambda_function.py.erb => /baz/lambda_function.py
25
+
26
+ [File.expand_path(path), File.join(target, tar_file)]
27
+ end.to_h
28
+
29
+ unless Dir.exists?(target)
30
+ FileUtils.makedirs(target)
31
+ end
32
+
33
+ templates.each do |src, dst|
34
+ File.open(dst, "w") do |f|
35
+ template = File.read(src)
36
+ formatted = ERB.new(template).result(binding)
37
+ f.write(formatted)
38
+ end
39
+ end
40
+
41
+ Dir.chdir(target) do
42
+ `git init`
43
+ `git add .`
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def default_region
51
+ 'us-east-1'
52
+ end
53
+
54
+ def default_account_id
55
+ nil
56
+ end
57
+
58
+ def default_role_name
59
+ nil
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ require 'yaml'
2
+
3
+ require 'lamma'
4
+ require 'lamma/function'
5
+ require 'lamma/alias'
6
+
7
+ module Lamma
8
+ class CLI::Rollback
9
+ attr_reader :options, :thor, :path
10
+
11
+ def initialize(options, thor)
12
+ @options = options
13
+ @thor = thor
14
+ @conf_path = options['path'] || Lamma::DEFAULT_CONF_PATH
15
+ end
16
+
17
+ def run
18
+ unless File.exists?(@conf_path)
19
+ abort("Config file #{@conf_path} is missing.")
20
+ end
21
+
22
+ f = Lamma::Function.new(@conf_path)
23
+
24
+ unless f.remote_exists?
25
+ abort("Remote function #{f.name} doesn't seem to be exists. You have to create or deploy it first")
26
+ end
27
+
28
+ unless options['alias']
29
+ abort("You can't rollback with alias (-a) option.")
30
+ end
31
+
32
+ a = Lamma::Alias.new(f, options['alias'])
33
+
34
+ unless a.remote_exists?
35
+ abort("Alias #{a.name} doesn't exist. You have to deploy the function first.")
36
+ end
37
+
38
+ la = Lamma::Alias.new(f, "#{options['alias']}_#{Lamma::LAST_DEPLOY_SUFFIX}")
39
+
40
+ unless la.remote_exists?
41
+ abort("Alias #{la.name} doesn't exist. You have to deploy the function first.")
42
+ end
43
+
44
+ v = a.remote_version
45
+ lv = la.remote_version
46
+
47
+ if v == lv
48
+ abort("Aliases #{a.name} and #{la.name} indicates same version #{v}. Deploy it first?")
49
+ end
50
+
51
+ a.version = lv
52
+ a.update
53
+ end
54
+
55
+ private
56
+ def templates_path
57
+ File.expand_path(File.join(File.dirname(__FILE__), '../templates'))
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ module Lamma
2
+ class CLI::Show
3
+ def initialize(options, function_name, thor)
4
+ @options = options
5
+ @thor = thor
6
+ @function_name = function_name
7
+ @conf_path = options[:path] || Lamma::DEFAULT_CONF_PATH
8
+ end
9
+
10
+ def run
11
+ f = Lamma::Function.new(@function_name)
12
+
13
+ unless f.remote_exists?
14
+ thor.say("Function #{f.name} doesn't exist in remote.")
15
+ end
16
+
17
+ thor.say(f) # XXX:
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,133 @@
1
+ require 'digest/md5'
2
+ require 'zip'
3
+
4
+ require 'lamma/logger'
5
+
6
+ module Lamma
7
+ class Code
8
+ BUILD_FILE_NAME = 'build.zip'
9
+
10
+ def initialize(function, yaml)
11
+ @function = function
12
+ @source_path = yaml.fetch('source_path', '.')
13
+ @prebuild = yaml.fetch('prebuild', nil)
14
+ @build_path = yaml.fetch('build_path', nil)
15
+ end
16
+
17
+ def zip_io
18
+ return @zip_io if @zip_io
19
+
20
+ if h = cached_build_hash
21
+ bp = File.join(build_path, h, BUILD_FILE_NAME)
22
+ Lamma.logger.info "Using cached build: #{bp}"
23
+ @zip_io = File.open(bp, 'rb')
24
+ else
25
+ unless File.directory?(@source_path)
26
+ raise "Source path #{@source_path} doesn't exists"
27
+ end
28
+
29
+ # prebuild script
30
+ if @prebuild
31
+ Lamma.logger.info 'Running prebuild script...'
32
+ raise unless system(@prebuild)
33
+ elsif @function.runtime == Lamma::Runtime::PYTHON_27 \
34
+ && File.exist?(File.join(@source_path, 'requirements.txt'))
35
+ raise unless system("pip", "install", "-r", "requirements.txt", "-t", ".")
36
+ # XXX: verbose?
37
+ elsif [Lamma::Runtime::EDGE_NODE_43, Lamma::Runtime::NODE_43].include? @function.runtime \
38
+ && File.exist?(File.join(@source_path, 'package.json'))
39
+ raise unless system("npm", "install", "--production")
40
+ end
41
+
42
+ io = Zip::OutputStream.write_buffer do |zio|
43
+ active_paths.each do |path|
44
+ next unless File.file?(path)
45
+ File.open(path) do |source_io|
46
+ zio.put_next_entry(path)
47
+ data = source_io.read
48
+ zio.write(data)
49
+ end
50
+ end
51
+ end
52
+
53
+ io.rewind
54
+ if true # XXX: option save_builds?
55
+ File.open(File.join(zipfile_path, BUILD_FILE_NAME), 'w').write(io.read)
56
+ Lamma.logger.info("Saved the build: #{zipfile_path}")
57
+ io.rewind
58
+ end
59
+ @zip_io = io
60
+ end
61
+
62
+ @zip_io
63
+ end
64
+
65
+ def to_h
66
+ {
67
+ zip_file: zip_io
68
+ }
69
+ end
70
+
71
+ private
72
+
73
+ def cached_build_hash
74
+ Dir.glob(File.join(build_path, "*/"))
75
+ .map{|path| File.basename(path)}.each do |h|
76
+ if hash == h
77
+ return h
78
+ end
79
+ end
80
+
81
+ nil
82
+ end
83
+
84
+ def hash
85
+ return @hash if @hash
86
+
87
+ h = Digest::MD5.new
88
+
89
+ active_paths.each do |path|
90
+ next unless File.file?(path)
91
+ h.update(path)
92
+ t = Digest::MD5.file(path)
93
+ h.update(t.digest)
94
+ end
95
+
96
+ @hash = h.to_s
97
+ end
98
+
99
+ def active_paths
100
+ Dir.glob(File.join(@source_path, '**/*')).select do |path|
101
+ !ignores.include?(path)
102
+ end
103
+ end
104
+
105
+ def build_path
106
+ if @build_path
107
+ unless File.directory?(@build_path)
108
+ FileUtils.mkdir_p(@build_path)
109
+ end
110
+ else
111
+ @build_path = File.join(Dir.tmpdir, 'lamma')
112
+ end
113
+
114
+ @build_path
115
+ end
116
+
117
+ def zipfile_path
118
+ return @zipfile_path if @zipfile_path
119
+
120
+ path = File.join(build_path, hash)
121
+ unless File.directory?(path)
122
+ FileUtils.mkdir_p(path)
123
+ end
124
+
125
+ @zipfile_path = path
126
+ end
127
+
128
+ def ignores
129
+ # TODO: impl
130
+ %w||
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,7 @@
1
+ module Lamma
2
+ class DeadLetterConfig
3
+ def initialize(yaml)
4
+ @target_arn = yaml.fetch('target_arn', nil)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Lamma
2
+ class Environment
3
+ def initialize(yaml)
4
+ @variables = yaml.fetch('variables', {})
5
+ end
6
+
7
+ def to_h
8
+ {
9
+ variables: @variables
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ require 'lamma'
2
+
3
+ module Lamma
4
+ class EventSourceMapping
5
+
6
+ end
7
+ end
@@ -0,0 +1,175 @@
1
+ require 'lamma'
2
+ require 'yaml'
3
+
4
+ require 'lamma/runtime'
5
+ require 'lamma/code'
6
+ require 'lamma/vpc_config'
7
+ require 'lamma/dead_letter_config'
8
+ require 'lamma/environment'
9
+
10
+ module Lamma
11
+ class Function
12
+ attr_reader :name, :region
13
+ attr_accessor :publish
14
+
15
+ def initialize(yaml_path)
16
+ path = Pathname.new(yaml_path)
17
+ @id = path.basename.sub_ext('').to_s
18
+ @root_path = path.parent
19
+ yaml = YAML.load(path.open)
20
+
21
+ @conf = yaml.fetch('function', {})
22
+
23
+ @publish = false
24
+ @name = @conf.fetch('name', nil)
25
+ @role_arn = @conf.fetch('role_arn', nil)
26
+ @description = @conf.fetch('description', nil)
27
+ @timeout = @conf.fetch('timeout', 3)
28
+ @memory_size = @conf.fetch('memory_size', 128)
29
+ # @dead_letter_config = dead_letter_config(@conf)
30
+ @region = @conf.fetch('region') { raise ValidationError.new('region must be set.') }
31
+ @kms_key_arn = @conf.fetch('kms_key_arn', nil)
32
+ end
33
+
34
+ def code
35
+ @code ||= Code.new(self, @conf.fetch('code', {}))
36
+ end
37
+
38
+ def create
39
+ Lamma.logger.info("Creating new function #{@name}...")
40
+
41
+ resp = lambda_client.create_function({
42
+ function_name: @name,
43
+ runtime: runtime.to_s,
44
+ role: @role_arn,
45
+ handler: handler,
46
+ code: code.to_h,
47
+ description: @description,
48
+ timeout: @timeout,
49
+ memory_size: @memory_size,
50
+ publish: publish,
51
+ vpc_config: vpc_config.to_h,
52
+ environment: environment.to_h,
53
+ kms_key_arn: @kms_key_arn
54
+ })
55
+
56
+ Lamma.logger.info(resp)
57
+ end
58
+
59
+ def update
60
+ Lamma.logger.info('Updating function configuration...')
61
+
62
+ resp = lambda_client.update_function_configuration({
63
+ function_name: @name,
64
+ runtime: runtime.to_s,
65
+ role: @role_arn,
66
+ handler: handler,
67
+ description: @description,
68
+ timeout: @timeout,
69
+ memory_size: @memory_size,
70
+ vpc_config: vpc_config.to_h,
71
+ environment: environment.to_h,
72
+ kms_key_arn: @kms_key_arn
73
+ })
74
+
75
+ Lamma.logger.info("Updated configuration for function: #{resp.function_arn}")
76
+
77
+ Lamma.logger.info('Updating function code...')
78
+
79
+ resp = lambda_client.update_function_code({
80
+ function_name: @name,
81
+ zip_file: code.to_h[:zip_file],
82
+ publish: publish
83
+ })
84
+
85
+ Lamma.logger.info("Updated code for function: #{resp.function_arn}")
86
+ end
87
+
88
+ def update_or_create
89
+ if remote_exists?
90
+ update
91
+ else
92
+ create
93
+ end
94
+ end
95
+
96
+ def aliases
97
+ lambda_client.list_aliases({
98
+ function_name: @name
99
+ }).aliases.map do |a|
100
+ Lamma::Alias.new(self, a.name, a.description)
101
+ end
102
+ end
103
+
104
+ def versions
105
+ lambda_client.list_versions_by_function({
106
+ function_name: @name
107
+ }).versions
108
+ end
109
+
110
+ def publish_version(v_desc, validate=nil)
111
+ Lamma.logger.info("Publishing...")
112
+ resp = lambda_client.publish_version({
113
+ function_name: @name,
114
+ code_sha_256: validate,
115
+ description: v_desc
116
+ })
117
+ Lamma.logger.info("Published $LATEST version as version #{resp.version} of funtion: #{resp.function_arn}")
118
+ resp
119
+ end
120
+
121
+ def remote_exists?
122
+ begin
123
+ lambda_client.get_function_configuration({
124
+ function_name: @name,
125
+ })
126
+ rescue Aws::Lambda::Errors::ResourceNotFoundException
127
+ false
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def runtime
134
+ runtime_str = @conf.fetch('runtime') { raise ValidationError.new('runtime must be set') }
135
+
136
+ @runtime ||= Lamma::Runtime.new(runtime_str)
137
+ end
138
+
139
+ def handler
140
+ @conf.fetch('handler', default_handler)
141
+ end
142
+
143
+ def dead_letter_config
144
+ DeadLetterConfig.new(@conf.fetch('dead_letter_config', {}))
145
+ end
146
+
147
+ def environment
148
+ Environment.new(@conf.fetch('environment', {}))
149
+ end
150
+
151
+ def vpc_config
152
+ VpcConfig.new(@conf.fetch('vpc_config', {}))
153
+ end
154
+
155
+ def default_handler
156
+ case runtime.type
157
+ when Runtime::C_SHARP
158
+ raise ValidationError.new('handler must be set for C# runtime')
159
+ when Runtime::JAVA_8
160
+ raise ValidationError.new('handler must be set for Java8 runtime')
161
+ when Runtime::NODE_43
162
+ 'index.handler'
163
+ when Runtime::EDGE_NODE_43
164
+ 'index.handler'
165
+ when Runtime::PYTHON_27
166
+ 'lambda_function.lambda_handler'
167
+ end
168
+ end
169
+
170
+
171
+ def lambda_client
172
+ @lambda_client ||= Aws::Lambda::Client.new(region: @region)
173
+ end
174
+ end
175
+ end