mos-eisley-lambda 0.6.0 → 0.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e38c9c1ecdcab343fc118677a1f7b515d4fe4ce19a0b4a056b50f515f81f87b
4
- data.tar.gz: a0b90f7bfb4c67bf383ce059fab48a7ce93d0588bd0766603dc879ea14783789
3
+ metadata.gz: 1c7bb756dd5b280ff9fb49dc6d7a52776d1e86eea6d0295585317d8bcf36c079
4
+ data.tar.gz: 3747893c11d3255af90bc925700020ce8b4be4b77c2fd29c9de009c3e436bb8e
5
5
  SHA512:
6
- metadata.gz: 31a6c405c9b10616063c4c7d99579d16bbfbd816ba1476640662a9123aa631d8d51886ee39a1f1007ff7fa1b2b7c46ecf10cbf24dd7f6b45629bbc102c47f43f
7
- data.tar.gz: 3dca76f821c4d4be95544eccdc599be61eef6715cdf0f7083649b3ac8ac8cc934d138c7757a31d2d3461491070d6327f8444abc143028f8d438b14a3b3540606
6
+ metadata.gz: 12d43754b4fafe23f9b83997b014a3884fecda7d269f65e10ef33799bcf7f9dce20748196252757b2cffe2cb3b3e6a9f82ced3cab5aa32b6d90ed2c6a2bcb2ca
7
+ data.tar.gz: 88801d7784949fdb5cedb9ff44cfcac774447ade46c844f9c23ff59b83a42e8615d1c989928520b680a9d4022cec81037fc958fa8e426c3caeca52360c4dbae0
data/README.md CHANGED
@@ -22,6 +22,7 @@ Configure Lambda environment variable.
22
22
  - `SLACK_CREDENTIALS_SSMPS_PATH`: hierarchy path to System Managers Parameter Store; e.g., `/slack/credentials/` would reference two parameters:
23
23
  - `/slack/credetials/signing_secret`
24
24
  - `/slack/credetials/bot_access_token`
25
+ - `MOSEISLEY_HANDLERS_DIR`: _optional_, if other than `./handlers`
25
26
  - `MOSEISLEY_LOG_LEVEL`: _optional_, could be `DEBUG`, `INFO`, `WARN`, or `ERROR`
26
27
  - `SLACK_LOG_CHANNEL_ID`: _optional_, if you want to use `ME::SlackWeb.post_log()`
27
28
 
@@ -32,10 +33,6 @@ require 'mos-eisley-lambda'
32
33
  # Or, you can just copy the `lib` directory to your Lambda and...
33
34
  # require_relative './lib/mos-eisley-lambda'
34
35
 
35
- MosEisley::Handler.import
36
- # Or, if you store your handlers in a non-default location, dictate by...
37
- # MosEisley::Handler.import_from_path('./my-handlers')
38
-
39
36
  def lambda_handler(event:, context:)
40
37
  MosEisley::lambda_event(event, context)
41
38
  end
@@ -52,21 +49,26 @@ Create a Slack app and configure the following.
52
49
 
53
50
  ### Handlers
54
51
 
55
- Create your own Mos Eisley handlers as blocks and register them. By default, store these Ruby files in the `handlers` directory.
52
+ Create your own Mos Eisley handlers as blocks and register them. By default, store these Ruby files in the `handlers` directory. Add handlers by passing a block to `MosEisley::Handler.add()` for the types below.
53
+
54
+ ```ruby
55
+ :action
56
+ :command_response
57
+ :command
58
+ :event
59
+ :menu
60
+ :nonslack
61
+ ```
56
62
 
57
- `ME::Handler.command_acks` holds `[Hash<String, Hash>]` which are Slack command keyword and response pair. The response is sent as-is back to Slack as an [immediate response](https://api.slack.com/interactivity/slash-commands#responding_immediate_response).
63
+ `:command_response` types are Slack command keyword and response pair. The response is sent as-is back to Slack as an [immediate response](https://api.slack.com/interactivity/slash-commands#responding_immediate_response). `ME` is an alias to `MosEisley`.
58
64
 
59
65
  ```ruby
60
- ME::Handler.command_acks.merge!({
61
- '/command' => {
62
- response_type: 'in_channel',
63
- text: '_Working on it…_',
64
- },
65
- '/secret' => {
66
- response_type: 'ephemeral',
67
- text: '_Just for you…_',
68
- },
69
- })
66
+ ME::Handler.add(:command_response, '/sample') do |event, myself|
67
+ {
68
+ response_type: "in_channel",
69
+ text: "_Working on `#{event[:command]}`..._",
70
+ }
71
+ end
70
72
  ```
71
73
 
72
74
  Add handlers to process the Slack event.
@@ -85,25 +87,48 @@ ME::Handler.add(:command, 'A Slack command') do |event, myself|
85
87
  end
86
88
  ```
87
89
 
90
+ If your function receives non-Slack events, you can add handlers for that as well.
91
+
92
+ ```ruby
93
+ ME::Handler.add(:nonslack, 'A CloudWatch event') do |event, myself|
94
+ next unless event['source'] == 'aws.events'
95
+ myself.stop
96
+ channel = 'C123SLCK'
97
+ txt = 'Shceduled event was received.'
98
+ ME::SlackWeb.chat_postmessage(channel: channel, text: txt)
99
+ end
100
+ ```
101
+
88
102
  ### Helpers
89
103
 
90
- - `ME::S3PO` – collection of helpers to analyze/create Slack messages.
91
- - `ME::SlackWeb` – methods for sending payloads to Slack Web API calls.
104
+ - `MosEisley::S3PO` – collection of helpers to analyze/create Slack messages.
105
+ - `MosEisley::SlackWeb` – methods for sending payloads to Slack Web API calls.
92
106
 
93
107
  ## Event Lifecycle
94
108
 
95
109
  ### Inbound
96
110
 
97
- 1. Slack event is sent to Mos Eisley Lambda function via API Gateway
98
- 1. Slack event is verified and produces a parsed object
99
- 1. If it's a slash command, MosEisley::Handler.command_acks is referenced and immediate response is sent
100
- 1. The original Slack event JSON is sent to the function in a recursive fashion (this is to return the inital response ASAP)
101
-
102
- ### Event Processing
103
-
104
- 1. Lambda function is invoked by itself with the original Slack event
105
- 1. Handlers are called and processed according to original endpoint the event was sent to; actions, commands, events, menus
106
- 1. Send a Slack message as necessary and the Slack event cycle is complete
111
+ To an incoming Slack event, Mos Eisley will quickly respond with a blank HTTP 200. This is to keep [Slack's 3-second rule](https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events). To do this, handlers are not called yet, but the Slack event is passed on to a recursive asynchronous invoke and then the handlers are called.
112
+
113
+ The exception is when the incoming Slack event is for a slash command. You can define `:command_response` handlers for the purpose of generating a simple response message, but nothing more.
114
+
115
+ ```mermaid
116
+ sequenceDiagram
117
+ participant S as Slack
118
+ participant L as Lambda MosEisley
119
+ S->>+L: Slack event via API Gateway
120
+ alt Slash command
121
+ L-->>S: Response message
122
+ Note left of L: If a response handler is defined
123
+ else All other events
124
+ L-->>-S: HTTP 200 (blank)
125
+ end
126
+ L->>+L: Slack event
127
+ Note right of L: Handlers are called
128
+ opt
129
+ L-->>-S: E.g., chat.postMessage
130
+ end
131
+ ```
107
132
 
108
133
  <!-- ### Outbound, Messaging Only
109
134
 
@@ -0,0 +1,55 @@
1
+ ##
2
+ ## Sample handlers for Mos Eisley
3
+ ##
4
+ ME::Handler.add(:event, 'DEBUG') do |event, myself|
5
+ l = ME.logger
6
+ l.debug("[Slack-Event]\n#{event}")
7
+ end
8
+
9
+ ME::Handler.add(:event, 'Request - diagnostics') do |event, myself|
10
+ se = event[:event]
11
+ next unless se[:type] == 'app_mention' && /\bdiag/i =~ se[:text]
12
+ myself.stop
13
+ l = ME.logger
14
+ bk = ME::S3PO::BlockKit
15
+ fs = []
16
+ ME.config.info[:handlers].each{ |k, v| fs << "*#{k}*\n#{v}" }
17
+ blks = [
18
+ bk.sec_text('Handler Count'),
19
+ bk.sec_fields(fs),
20
+ ]
21
+ fs = []
22
+ ME.config.info[:versions].each{ |k, v| fs << "*#{k}*\n#{v}" }
23
+ blks << bk.sec_text('Software Versions')
24
+ blks << bk.sec_fields(fs)
25
+ ME::SlackWeb.chat_postmessage(channel: se[:channel], text: "Diagnostics", blocks: blks)
26
+ end
27
+
28
+ ME::Handler.add(:nonslack, 'DEBUG') do |event, myself|
29
+ l = ME.logger
30
+ l.debug("[Non-Slack]\n#{event}")
31
+ end
32
+
33
+ ME::Handler.add(:command_response, '/sample') do |event, myself|
34
+ {
35
+ response_type: "in_channel",
36
+ text: "_Working on `#{event[:command]}`..._",
37
+ }
38
+ end
39
+
40
+ ME::Handler.add(:command, 'DEBUG') do |event, myself|
41
+ l = ME.logger
42
+ l.debug("[Slack-Command]\n#{event}")
43
+ end
44
+
45
+ ME::Handler.add(:command, 'Request - /sample') do |event, myself|
46
+ next unless event[:command] == '/sample'
47
+ myself.stop
48
+ bk = ME::S3PO::BlockKit
49
+ t = "`S A M P L E` I did it!"
50
+ blks = [
51
+ bk.sec_text(t),
52
+ bk.con_text('By: Mos Eisley sampler'),
53
+ ]
54
+ ME::SlackWeb.chat_postmessage(channel: event[:command], text: t, blocks: blks)
55
+ end
data/lib/handler.rb CHANGED
@@ -31,6 +31,7 @@ module MosEisley
31
31
  command: [],
32
32
  event: [],
33
33
  menu: [],
34
+ nonslack: [],
34
35
  }
35
36
  h = MosEisley::Handler.new(type, name, &block)
36
37
  if type == :command_response
@@ -38,13 +39,7 @@ module MosEisley
38
39
  else
39
40
  @handlers[type] << h
40
41
  end
41
- MosEisley.logger.debug("Added #{type} handler: #{h}")
42
- end
43
-
44
- # Example: {'/command' => {response_type: 'ephemeral', text: nil}}
45
- # @return [Hash<String, Hash>] commands to acknowledge
46
- def self.command_acks
47
- @command_acks ||= {}
42
+ MosEisley.logger.debug("Added handler: #{h}")
48
43
  end
49
44
 
50
45
  # @return [Hash<Symbol, Array>] containing all the handlers
@@ -106,7 +101,7 @@ module MosEisley
106
101
  end
107
102
 
108
103
  def to_s
109
- "#<#{self.class}:#{self.object_id.to_s(16)}(#{name})>"
104
+ "#<#{self.class}:#{self.object_id.to_s(16)}(#{type}:#{name})>"
110
105
  end
111
106
  end
112
107
  end
@@ -2,6 +2,7 @@ require_relative './logger'
2
2
  require_relative './slack'
3
3
  require_relative './s3po/s3po'
4
4
  require_relative './handler'
5
+ require_relative './version'
5
6
  require 'aws-sdk-lambda'
6
7
  require 'aws-sdk-ssm'
7
8
  require 'base64'
@@ -10,13 +11,24 @@ require 'json'
10
11
  ME = MosEisley
11
12
 
12
13
  module MosEisley
13
- def self.config
14
- @config ||= {}
14
+ def self.config(context = nil, data = nil)
15
+ if data
16
+ unless @config
17
+ @config = Config.new(context, data)
18
+ MosEisley.logger.info('Config loaded')
19
+ else
20
+ MosEisley.logger.warn('Ignored, already configured')
21
+ end
22
+ end
23
+ @config
15
24
  end
16
25
 
17
26
  def self.lambda_event(event, context)
18
- raise 'Pre-flight check failed!' unless preflightcheck
27
+ raise 'Pre-flight check failed!' unless preflightcheck(context)
19
28
  case
29
+ when event['initializeOnly']
30
+ MosEisley.logger.info('Dry run, initializing only')
31
+ return
20
32
  when event['routeKey']
21
33
  # Inbound Slack event (via API GW)
22
34
  MosEisley.logger.info('API GW event')
@@ -26,22 +38,28 @@ module MosEisley
26
38
  MosEisley.logger.info('Invoke event')
27
39
  MosEisley.logger.debug("#{event}")
28
40
  return invoke_event(event)
41
+ when event.dig('Records',0,'eventSource').start_with?('MosEisley:Slack_message:')
42
+ # Outbound Slack messaging request (via invoke)
43
+ MosEisley.logger.info('Messaging event')
44
+ MosEisley.logger.debug("#{event}")
45
+ return # TODO implement
29
46
  else
30
- # Unknown event
31
- MosEisley.logger.info('Unknown event')
32
- return unknown_event(event)
47
+ # Non-Slack event
48
+ MosEisley.logger.info('Non-Slack event')
49
+ return nonslack_event(event)
33
50
  end
34
51
  end
35
52
 
36
- def self.preflightcheck
37
- if config[:timestamp]
38
- MosEisley.logger.debug("Confing already loaded at: #{config[:timestamp]}")
53
+ def self.preflightcheck(context)
54
+ if config
55
+ MosEisley.logger.debug("Confing already loaded at: #{config.timestamp}")
39
56
  return true
40
57
  end
41
58
  env_required = [
42
59
  'SLACK_CREDENTIALS_SSMPS_PATH',
43
60
  ]
44
61
  env_optional = [
62
+ 'MOSEISLEY_HANDLERS_DIR',
45
63
  'MOSEISLEY_LOG_LEVEL',
46
64
  'SLACK_LOG_CHANNEL_ID',
47
65
  ]
@@ -53,6 +71,11 @@ module MosEisley
53
71
  if String === l && ['DEBUG', 'INFO', 'WARN', 'ERROR'].include?(l.upcase)
54
72
  MosEisley.logger.level = eval("Logger::#{l.upcase}")
55
73
  end
74
+ if dir = ENV['MOSEISLEY_HANDLERS_DIR']
75
+ MosEisley::Handler.import_from_path(dir)
76
+ else
77
+ MosEisley::Handler.import
78
+ end
56
79
  env_required.each do |v|
57
80
  if ENV[v].nil?
58
81
  MosEisley.logger.error("Missing environment variable: #{v}")
@@ -65,20 +88,20 @@ module MosEisley
65
88
  path: ENV['SLACK_CREDENTIALS_SSMPS_PATH'],
66
89
  with_decryption: true,
67
90
  }
91
+ c = {}
68
92
  ssm.get_parameters_by_path(rparams).parameters.each do |prm|
69
93
  k = prm[:name].split('/').last.to_sym
70
- config[k] = prm[:value]
94
+ c[k] = prm[:value]
71
95
  config_required.delete(k)
72
96
  end
97
+ unless config_required.empty?
98
+ t = "Missing config values: #{config_required.join(', ')}"
99
+ MosEisley.logger.error(t)
100
+ return false
101
+ end
102
+ config(context, c)
73
103
  end
74
104
  end
75
- unless config_required.empty?
76
- t = "Missing config values: #{config_required.join(', ')}"
77
- MosEisley.logger.error(t)
78
- return false
79
- end
80
- config[:timestamp] = Time.now
81
- MosEisley.logger.info('Config loaded')
82
105
  return true
83
106
  end
84
107
 
@@ -163,7 +186,42 @@ module MosEisley
163
186
  end
164
187
  end
165
188
 
166
- def self.unknown_event(event)
167
- # TODO hand off to a handler
189
+ def self.nonslack_event(event)
190
+ MosEisley::Handler.run(:nonslack, event)
191
+ end
192
+
193
+ class Config
194
+ attr_reader :context, :info, :timestamp
195
+ attr_reader :bot_access_token, :signing_secret
196
+
197
+ def initialize(context, data)
198
+ data.each do |k, v|
199
+ instance_variable_set("@#{k}", v)
200
+ end
201
+ @context = context
202
+ @info = {
203
+ handlers: {
204
+ action: MosEisley.handlers[:action].length,
205
+ command_response: MosEisley.handlers[:command_response].length,
206
+ command: MosEisley.handlers[:command].length,
207
+ event: MosEisley.handlers[:event].length,
208
+ },
209
+ versions: {
210
+ mos_eisley: MosEisley::VERSION,
211
+ neko_http: Neko::HTTP::VERSION,
212
+ s3po: MosEisley::S3PO::VERSION,
213
+ s3po_blockkit: MosEisley::S3PO::BlockKit::VERSION,
214
+ },
215
+ }
216
+ @timestamp = Time.now
217
+ end
218
+
219
+ def arn
220
+ context.invoked_function_arn
221
+ end
222
+
223
+ def remaining_time
224
+ context.get_remaining_time_in_millis
225
+ end
168
226
  end
169
227
  end
data/lib/neko-http.rb CHANGED
@@ -1,7 +1,6 @@
1
+ #
1
2
  # NekoHTTP - Pure Ruby HTTP client using net/http
2
- #
3
- # v.20200629
4
-
3
+ #
5
4
  require 'json'
6
5
  require 'logger'
7
6
  require 'net/http'
@@ -17,6 +16,8 @@ module Neko
17
16
  end
18
17
 
19
18
  class HTTP
19
+ VERSION = '20220224'.freeze
20
+
20
21
  METHOD_HTTP_CLASS = {
21
22
  get: Net::HTTP::Get,
22
23
  put: Net::HTTP::Put,
@@ -25,6 +26,11 @@ module Neko
25
26
  delete: Net::HTTP::Delete
26
27
  }
27
28
 
29
+ # Simple GET request
30
+ # @param url [String] full URL string
31
+ # @param params [Array, Hash] it will be converted to URL encoded query
32
+ # @param hdrs [Hash] HTTP headers
33
+ # @return [Hash] contains: :code, :headers, :body, :message
28
34
  def self.get(url, params, hdrs = nil)
29
35
  h = HTTP.new(url, hdrs)
30
36
  data = h.get(params: params)
@@ -32,6 +38,11 @@ module Neko
32
38
  return data
33
39
  end
34
40
 
41
+ # Send POST request with form data URL encoded body
42
+ # @param url [String] full URL string
43
+ # @param params [Array, Hash] it will be converted to URL encoded body
44
+ # @param hdrs [Hash] HTTP headers
45
+ # @return (see #self.get)
35
46
  def self.post_form(url, params, hdrs = nil)
36
47
  h = HTTP.new(url, hdrs)
37
48
  data = h.post(params: params)
@@ -43,7 +54,8 @@ module Neko
43
54
  # It will set the Content-Type to application/json.
44
55
  # @param url [String] full URL string
45
56
  # @param obj [Array, Hash, String] Array/Hash will be converted to JSON
46
- # @param hdrs [Array, Hash, String] Array/Hash will be converted to JSON
57
+ # @param hdrs [Hash] HTTP headers
58
+ # @return (see #self.get)
47
59
  def self.post_json(url, obj, hdrs = {})
48
60
  hdrs['Content-Type'] = 'application/json'
49
61
  h = HTTP.new(url, hdrs)
@@ -63,6 +75,9 @@ module Neko
63
75
  attr_reader :init_uri, :http
64
76
  attr_accessor :logger, :headers
65
77
 
78
+ # Instance constructor for tailored use
79
+ # @param url [String] full URL string
80
+ # @param hdrs [Hash] HTTP headers
66
81
  def initialize(url, hdrs = nil)
67
82
  @logger = Neko.logger
68
83
  @init_uri = URI(url)
data/lib/s3po/blockkit.rb CHANGED
@@ -1,11 +1,12 @@
1
+ #
1
2
  # S3PO - Slack protocol droid in Mos Eisley
2
3
  # ::BlockKit - Block Kit tools
3
4
  #
4
- # v.20220201
5
-
6
5
  module MosEisley
7
6
  module S3PO
8
7
  module BlockKit
8
+ VERSION = '20220224'.freeze
9
+
9
10
  # @param txt [String]
10
11
  # @param type [Symbol] :plain | :emoji | :mrkdwn
11
12
  # @return [Hash] Block Kit section object
@@ -37,6 +38,16 @@ module MosEisley
37
38
  }
38
39
  end
39
40
 
41
+ # @param fields [Array<String>]
42
+ # @param type [Symbol] :plain | :emoji | :mrkdwn
43
+ # @return [Hash] Block Kit section object
44
+ def self.sec_fields(fields, type = :mrkdwn)
45
+ {
46
+ type: :section,
47
+ fields: fields.map{ |txt| text(txt, type) },
48
+ }
49
+ end
50
+
40
51
  # @param txt [String]
41
52
  # @param type [Symbol] :plain | :emoji | :mrkdwn
42
53
  # @return [Hash] Block Kit text object
data/lib/s3po/s3po.rb CHANGED
@@ -1,13 +1,14 @@
1
+ #
1
2
  # S3PO - Slack protocol droid in Mos Eisley
2
3
  #
3
- # v.20210626
4
-
5
4
  require 'json'
6
5
  require 'time'
7
6
  require_relative './blockkit'
8
7
 
9
8
  module MosEisley
10
9
  module S3PO
10
+ VERSION = '20210626'.freeze
11
+
11
12
  def self.parse_json(json)
12
13
  return JSON.parse(json, {symbolize_names: true})
13
14
  rescue => e
data/lib/slack.rb CHANGED
@@ -15,7 +15,7 @@ module MosEisley
15
15
  end
16
16
  b = e['isBase64Encoded'] ? Base64.decode64(e['body']) : e['body']
17
17
  s = "v0:#{t}:#{b}"
18
- k = MosEisley.config[:signing_secret]
18
+ k = MosEisley.config.signing_secret
19
19
  sig = "v0=#{OpenSSL::HMAC.hexdigest('sha256', k, s)}"
20
20
  if e.dig('headers', 'x-slack-signature') != sig
21
21
  return {valid?: false, msg: 'Invalid signature.'}
@@ -155,7 +155,7 @@ module MosEisley
155
155
  def self.get_from_slack(m, params)
156
156
  l = MosEisley.logger
157
157
  url ||= BASE_URL + m
158
- head = {authorization: "Bearer #{MosEisley.config[:bot_access_token]}"}
158
+ head = {authorization: "Bearer #{MosEisley.config.bot_access_token}"}
159
159
  r = Neko::HTTP.get(url, params, head)
160
160
  if r[:code] != 200
161
161
  l.warn("#{m} HTTP failed: #{r[:message]}")
@@ -178,7 +178,7 @@ module MosEisley
178
178
  def self.post_to_slack(method, data, url = nil)
179
179
  l = MosEisley.logger
180
180
  url ||= BASE_URL + method
181
- head = {authorization: "Bearer #{MosEisley.config[:bot_access_token]}"}
181
+ head = {authorization: "Bearer #{MosEisley.config.bot_access_token}"}
182
182
  r = Neko::HTTP.post_json(url, data, head)
183
183
  if r[:code] != 200
184
184
  l.warn("post_to_slack HTTP failed: #{r[:message]}")
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MosEisley
2
+ VERSION = '0.7.0'.freeze
3
+ end
@@ -1,6 +1,8 @@
1
+ require_relative './lib/version'
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = 'mos-eisley-lambda'
3
- s.version = '0.6.0'
5
+ s.version = MosEisley::VERSION
4
6
  s.authors = ['Ken J.']
5
7
  s.email = ['kenjij@gmail.com']
6
8
  s.summary = %q{Ruby based Slack bot framework, for AWS Lambda use}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mos-eisley-lambda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken J.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-24 00:00:00.000000000 Z
11
+ date: 2022-03-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby based Slack bot framework, for AWS Lambda; event queue based. Also
14
14
  provides Block Kit helper.
@@ -21,6 +21,7 @@ files:
21
21
  - LICENSE
22
22
  - Makefile
23
23
  - README.md
24
+ - handlers/sample.rb
24
25
  - lib/handler.rb
25
26
  - lib/logger.rb
26
27
  - lib/mos-eisley-lambda.rb
@@ -28,6 +29,7 @@ files:
28
29
  - lib/s3po/blockkit.rb
29
30
  - lib/s3po/s3po.rb
30
31
  - lib/slack.rb
32
+ - lib/version.rb
31
33
  - mos-eisley-lambda.gemspec
32
34
  - openapi3.yaml
33
35
  homepage: https://github.com/kenjij/mos-eisley-lambda