lamma 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +22 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/hello/lambda_function.py +11 -0
- data/examples/hello/lammda.yml +0 -0
- data/exe/lamma +17 -0
- data/lamma.gemspec +29 -0
- data/lib/lamma.rb +4 -0
- data/lib/lamma/alias.rb +74 -0
- data/lib/lamma/cli.rb +63 -0
- data/lib/lamma/cli/create.rb +16 -0
- data/lib/lamma/cli/deploy.rb +79 -0
- data/lib/lamma/cli/init.rb +61 -0
- data/lib/lamma/cli/rollback.rb +60 -0
- data/lib/lamma/cli/show.rb +20 -0
- data/lib/lamma/code.rb +133 -0
- data/lib/lamma/dead_letter_config.rb +7 -0
- data/lib/lamma/environment.rb +13 -0
- data/lib/lamma/event_source_mapping.rb +7 -0
- data/lib/lamma/function.rb +175 -0
- data/lib/lamma/logger.rb +14 -0
- data/lib/lamma/runtime.rb +48 -0
- data/lib/lamma/templates/python2_7/lambda_function.py.erb +11 -0
- data/lib/lamma/templates/python2_7/lamma.yml.erb +7 -0
- data/lib/lamma/version.rb +3 -0
- data/lib/lamma/vpc_config.rb +15 -0
- metadata +162 -0
@@ -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
|
data/lib/lamma/code.rb
ADDED
@@ -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,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
|