lambchop 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 90f7063b4c0006042d5adcd83ad566cba3e68044
4
+ data.tar.gz: d7ff894f8e56e429fa0979840236109b1f445d6a
5
+ SHA512:
6
+ metadata.gz: b34f002a00f6e12631f911401b98b528841caac03f95b22e90bb4feb46148a2af5cc18fb712060e1e3322dea9625ee03bfc3b4d460bdc2bd3dc74da820513479
7
+ data.tar.gz: 872d87cca199322ac09a4af79913d322e523167a517d2ebd8ae79ddd9d6b9884e6e341f157c57237b69b0f1c563c6972f061700a0ada2d5ffbe0a2f75dddd188
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ test.rb
16
+ test.js
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lambchop.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Genki Sugawara
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Lambchop
2
+
3
+ It is a tool that invoke AWS Lambda function from the local machine as a normally script.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'lambchop'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install lambchop
20
+
21
+ ## Usage
22
+
23
+ **Terminal 1**:
24
+ ```sh
25
+ $ cat test.js
26
+ #!/usr/bin/env lambchop
27
+ /*
28
+ function_name: test # default: file name without ext
29
+ runtime: nodejs # default: nodejs
30
+ mode: event # default: event
31
+ description: '' # default: (empty)
32
+ timeout: 3 # default: 3
33
+ memory_size: 128 # default: 128
34
+ role: arn:aws:iam::NNNNNNNNNNNN:role/lambda_exec_role
35
+ handler: test.handler
36
+ */
37
+ console.log('Loading event');
38
+
39
+ exports.handler = function(event, context) {
40
+ console.log('value1 = ' + event.key1);
41
+ console.log('value2 = ' + event.key2);
42
+ console.log('value3 = ' + event.key3);
43
+ context.done(null, 'Hello World'); // SUCCESS with message
44
+ };
45
+
46
+ $ ./test.js
47
+ (Wait event...)
48
+ ```
49
+
50
+ **Terminal 2**:
51
+ ```sh
52
+ $ lambchop-cat
53
+ usage: lambchop-cat <function-name>
54
+ $ lambchop-cat
55
+ $ echo '{"key1":100, "key2":200, "key3":300}' | bundle exec ./bin/lambchop-cat test
56
+ ```
57
+
58
+ **Terminal 1**:
59
+ ```sh
60
+ 2014-11-23T08:06:53.212Z xxxxxxxxxxxxxxxx Loading event
61
+ START RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
62
+ 2014-11-23T08:06:53.330Z xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx value1 = 100
63
+ 2014-11-23T08:06:53.330Z xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx value3 = 300
64
+ 2014-11-23T08:06:53.330Z xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx value2 = 200
65
+ END RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
66
+ REPORT RequestId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Duration: 117.54 ms Billed Duration: 200 ms Memory Size: 128 MB Max Memory Used: 9 MB
67
+ ```
68
+
69
+ ### Dump function
70
+
71
+ ```sh
72
+ $ lambchop-dump
73
+ usage: lambchop-dump <function-name>
74
+
75
+ $ lambchop-dump test
76
+ #!/usr/bin/env lambchop
77
+ /*
78
+ function_name: test
79
+ runtime: nodejs
80
+ role: arn:aws:iam::NNNNNNNNNNNN:role/lambda_exec_role
81
+ handler: test.handler
82
+ mode: event
83
+ description: ''
84
+ timeout: 3
85
+ memory_size: 128
86
+ */
87
+ console.log('Loading event');
88
+
89
+ exports.handler = function(event, context) {
90
+ console.log('value1 = ' + event.key1);
91
+ console.log('value2 = ' + event.key2);
92
+ console.log('value3 = ' + event.key3);
93
+ context.done(null, 'Hello World'); // SUCCESS with message
94
+ };
95
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
data/bin/lambchop ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("#{File.dirname __FILE__}/../lib")
3
+ require 'rubygems'
4
+ require 'lambchop'
5
+
6
+ Lambchop::Client.start(ARGF.read, ARGF.path)
data/bin/lambchop-cat ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("#{File.dirname __FILE__}/../lib")
3
+ require 'rubygems'
4
+ require 'lambchop'
5
+
6
+ if ARGV.length != 1
7
+ puts 'usage: lambchop-cat <function-name>'
8
+ exit 1
9
+ end
10
+
11
+ Lambchop::Cat.cat(ARGV[0], $stdin)
data/bin/lambchop-dump ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("#{File.dirname __FILE__}/../lib")
3
+ require 'rubygems'
4
+ require 'lambchop'
5
+
6
+ if ARGV.length != 1
7
+ puts 'usage: lambchop-dump <function-name>'
8
+ exit 1
9
+ end
10
+
11
+ Lambchop::Dump.dump(ARGV[0])
data/lambchop.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lambchop/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'lambchop'
8
+ spec.version = Lambchop::VERSION
9
+ spec.authors = ['Genki Sugawara']
10
+ spec.email = ['sgwr_dts@yahoo.co.jp']
11
+ spec.summary = %q{It is a tool that invoke AWS Lambda function from the local machine as a normally script.}
12
+ spec.description = %q{It is a tool that invoke AWS Lambda function from the local machine as a normally script.}
13
+ spec.homepage = 'https://github.com/winebarrel/lambchop'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+
22
+ spec.add_dependency 'aws-sdk-core', '~> 2.0.9'
23
+ spec.add_dependency 'rubyzip', '>= 1.0.0'
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,25 @@
1
+ class Lambchop::Cat
2
+ def self.cat(function_name, invoke_args, options = {})
3
+ self.new(function_name, invoke_args, options).cat
4
+ end
5
+
6
+ def initialize(function_name, invoke_args, options = {})
7
+ @function_name = function_name
8
+ @invoke_args = invoke_args
9
+ @client = options[:client] || Aws::Lambda::Client.new
10
+ @options = options
11
+ end
12
+
13
+ def cat
14
+ invoke_args = @invoke_args
15
+
16
+ if invoke_args.kind_of?(IO)
17
+ invoke_args = invoke_args.read
18
+ end
19
+
20
+ @client.invoke_async(
21
+ :function_name => @function_name,
22
+ :invoke_args => invoke_args
23
+ )
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ class Lambchop::Client
2
+ def self.start(source, path, options = {})
3
+ self.new(source, path, options).start
4
+ end
5
+
6
+ def initialize(source, path, options = {})
7
+ @source = source
8
+ @path = path
9
+ @client = options[:client] || Aws::Lambda::Client.new
10
+ @options = options
11
+ end
12
+
13
+ def start
14
+ src = remove_shebang(@source)
15
+ config, src = parse_magic_comment(src)
16
+
17
+ config['function_name'] ||= File.basename(@path, '.js')
18
+ function_name = config['function_name']
19
+
20
+ config['mode'] ||= 'event'
21
+ config['runtime'] ||= 'nodejs'
22
+
23
+ resp = upload_function(config, src)
24
+ $stderr.puts('Function was uploaded:')
25
+ $stderr.puts(JSON.pretty_generate(resp.to_h))
26
+
27
+ Lambchop::WatchDog.start(function_name, @options[:watch_dog] || {})
28
+
29
+ sleep
30
+ end
31
+
32
+ private
33
+
34
+ def upload_function(config, src)
35
+ params = {}
36
+ config.each {|k, v| params[k.to_sym] = v }
37
+ params[:function_zip] = zip_source(src).string
38
+ @client.upload_function(params)
39
+ end
40
+
41
+ def zip_source(src)
42
+ Zip::OutputStream.write_buffer do |out|
43
+ out.put_next_entry(File.basename(@path))
44
+ out.write(src)
45
+ end
46
+ end
47
+
48
+ def remove_shebang(src)
49
+ src.sub(/\A#![^\n]*\n/, '')
50
+ end
51
+
52
+ def parse_magic_comment(src)
53
+ ss = StringScanner.new(src)
54
+
55
+ unless ss.scan(%r|\A\s*/\*|)
56
+ raise 'Cannot find magic comment'
57
+ end
58
+
59
+ unless comment = ss.scan_until(%r|\*/|)
60
+ raise 'Cannot find magic comment'
61
+ end
62
+
63
+ comment.sub!(%r|\*/\z|, '')
64
+
65
+ [YAML.load(comment), ss.rest.sub(/\A\n/, '')]
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ class Lambchop::Dump
2
+ def self.dump(function_name, options = {})
3
+ self.new(function_name, options).dump
4
+ end
5
+
6
+ def initialize(function_name, options = {})
7
+ @function_name = function_name
8
+ @client = options[:client] || Aws::Lambda::Client.new
9
+ @out = options[:out] || $stdout
10
+ @options = options
11
+ end
12
+
13
+ def dump
14
+ page = @client.get_function(:function_name => @function_name).first
15
+ puts_shebang
16
+ puts_magic_comment(page.configuration)
17
+ puts_source(page.code.location)
18
+ end
19
+
20
+ def puts_shebang
21
+ @out.puts('#!/usr/bin/env lambchop')
22
+ end
23
+
24
+ def puts_magic_comment(configuration)
25
+ comment_attrs = {}
26
+
27
+ %w(
28
+ function_name
29
+ runtime
30
+ role
31
+ handler
32
+ mode
33
+ description
34
+ timeout
35
+ memory_size
36
+ ).each do |name|
37
+ comment_attrs[name] = configuration.send(name)
38
+ end
39
+
40
+ yaml = YAML.dump(comment_attrs).sub(/\A---\n/, '')
41
+ @out.puts("/*\n#{yaml}*/")
42
+ end
43
+
44
+ def puts_source(location)
45
+ open(location) do |f|
46
+ Zip::InputStream.open(f) do |zis|
47
+ while entry = zis.get_next_entry
48
+ @out.puts(entry.get_input_stream.read)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Lambchop
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,85 @@
1
+ Thread::abort_on_exception = true
2
+
3
+ class Lambchop::WatchDog
4
+ def self.start(function_name, options = {})
5
+ self.new(function_name, options).start
6
+ end
7
+
8
+ def initialize(function_name, options = {})
9
+ @function_name = function_name
10
+ @log_group_name = "/aws/lambda/#{@function_name}"
11
+ @client = options[:client] || Aws::CloudWatchLogs::Client.new(region: 'us-east-1')
12
+ @start_time = options[:start_time] || Time.now
13
+ @out = options[:out] || $stdout
14
+ @last_timestamp = time_to_timestamp(@start_time) - 1
15
+ @interval = options[:interval] || 1
16
+ @options = options
17
+ end
18
+
19
+ def start
20
+ @running = true
21
+
22
+ Thread.start do
23
+ while @running
24
+ follow_logs
25
+ sleep @interval
26
+ end
27
+ end
28
+ end
29
+
30
+ def stop
31
+ @running = false
32
+ end
33
+
34
+ private
35
+
36
+ def follow_logs
37
+ all_events = []
38
+ log_streams.each do |log_stream|
39
+ last_ingestion_time = log_stream.last_ingestion_time
40
+
41
+ if last_ingestion_time.nil? or last_ingestion_time < @last_timestamp
42
+ next
43
+ end
44
+
45
+ log_events(log_stream.log_stream_name).each do |event|
46
+ next if event.timestamp <= @last_timestamp
47
+ all_events << event
48
+ end
49
+ end
50
+
51
+ all_events.sort_by(&:timestamp).each do |event|
52
+ @out.puts(event.message)
53
+ @last_timestamp = event.timestamp
54
+ end
55
+ end
56
+
57
+ def log_streams
58
+ pages = @client.describe_log_streams(:log_group_name => @log_group_name)
59
+ pages.map(&:log_streams).flatten(1)
60
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
61
+ []
62
+ end
63
+
64
+ def log_events(log_stream_name)
65
+ events = []
66
+
67
+ pages = @client.get_log_events(
68
+ :log_group_name => @log_group_name,
69
+ :log_stream_name => log_stream_name,
70
+ :start_time => @last_timestamp
71
+ )
72
+
73
+ pages.each do |page|
74
+ break if page.events.empty?
75
+ events.concat(page.events)
76
+ end
77
+
78
+ events
79
+ end
80
+
81
+ def time_to_timestamp(time)
82
+ ts = time.to_i * 1000
83
+ ts + time.usec / 1000
84
+ end
85
+ end
data/lib/lambchop.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'open-uri'
2
+ require 'yaml'
3
+
4
+ require 'aws-sdk-core'
5
+ require 'zip'
6
+
7
+ module Lambchop; end
8
+
9
+ require 'lambchop/cat'
10
+ require 'lambchop/client'
11
+ require 'lambchop/dump'
12
+ require 'lambchop/watch_dog'
13
+ require 'lambchop/version'
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lambchop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Genki Sugawara
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyzip
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: It is a tool that invoke AWS Lambda function from the local machine as
70
+ a normally script.
71
+ email:
72
+ - sgwr_dts@yahoo.co.jp
73
+ executables:
74
+ - lambchop
75
+ - lambchop-cat
76
+ - lambchop-dump
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - .gitignore
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/lambchop
86
+ - bin/lambchop-cat
87
+ - bin/lambchop-dump
88
+ - lambchop.gemspec
89
+ - lib/lambchop.rb
90
+ - lib/lambchop/cat.rb
91
+ - lib/lambchop/client.rb
92
+ - lib/lambchop/dump.rb
93
+ - lib/lambchop/version.rb
94
+ - lib/lambchop/watch_dog.rb
95
+ homepage: https://github.com/winebarrel/lambchop
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.0.14
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: It is a tool that invoke AWS Lambda function from the local machine as a
119
+ normally script.
120
+ test_files: []