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.
- 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
|