mos-eisley-lambda 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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