banacle 0.1.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 +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +81 -0
- data/LICENSE +22 -0
- data/README.md +53 -0
- data/Rakefile +6 -0
- data/banacle.gemspec +34 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +3 -0
- data/docs/demo1.png +0 -0
- data/docs/demo2.png +0 -0
- data/docs/nacl.png +0 -0
- data/lib/banacle/app.rb +21 -0
- data/lib/banacle/aws_wrapper/error.rb +5 -0
- data/lib/banacle/aws_wrapper/nacl.rb +127 -0
- data/lib/banacle/aws_wrapper/result.rb +6 -0
- data/lib/banacle/aws_wrapper/vpc.rb +51 -0
- data/lib/banacle/handler.rb +61 -0
- data/lib/banacle/interactive_message/parser.rb +24 -0
- data/lib/banacle/interactive_message/renderer.rb +138 -0
- data/lib/banacle/slack.rb +191 -0
- data/lib/banacle/slack_validator.rb +32 -0
- data/lib/banacle/slash_command/builder.rb +100 -0
- data/lib/banacle/slash_command/command.rb +75 -0
- data/lib/banacle/slash_command/error.rb +6 -0
- data/lib/banacle/slash_command/parser.rb +42 -0
- data/lib/banacle/slash_command/renderer.rb +56 -0
- data/lib/banacle/version.rb +3 -0
- data/lib/banacle.rb +2 -0
- metadata +188 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'banacle/slack'
|
2
|
+
require 'banacle/slash_command/command'
|
3
|
+
|
4
|
+
module Banacle
|
5
|
+
module InteractiveMessage
|
6
|
+
class Renderer
|
7
|
+
def self.render(params, command)
|
8
|
+
new(params, command).render
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(params, command)
|
12
|
+
@params = params
|
13
|
+
@command = command
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :params, :command
|
17
|
+
|
18
|
+
def render
|
19
|
+
payload = JSON.parse(params["payload"], symbolize_names: true)
|
20
|
+
action = Slack::Action.new(payload[:actions].first)
|
21
|
+
|
22
|
+
if action.approved?
|
23
|
+
render_approved_message(payload, command)
|
24
|
+
elsif action.rejected?
|
25
|
+
render_rejected_message(payload, command)
|
26
|
+
elsif action.cancelled?
|
27
|
+
render_cancelled_message(payload, command)
|
28
|
+
else
|
29
|
+
# Do nothing
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# override
|
36
|
+
def authenticated_user?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def render_approved_message(payload, command)
|
43
|
+
unless valid_approver?
|
44
|
+
return render_error("you cannot approve the request by yourself")
|
45
|
+
end
|
46
|
+
|
47
|
+
if authenticated_user?
|
48
|
+
result = command.execute
|
49
|
+
|
50
|
+
text = original_message_text
|
51
|
+
text += ":white_check_mark: *<@#{actioner_id}> approved this request*\n"
|
52
|
+
text += "Result:\n"
|
53
|
+
text += "```\n"
|
54
|
+
text += result
|
55
|
+
text += "```"
|
56
|
+
|
57
|
+
render_replacing_message(text)
|
58
|
+
else
|
59
|
+
render_error("you are not permitted to approve the request")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def render_rejected_message(payload, command)
|
64
|
+
unless valid_rejector?
|
65
|
+
return render_error("you cannot reject the request by yourself")
|
66
|
+
end
|
67
|
+
|
68
|
+
if authenticated_user?
|
69
|
+
text = original_message_text
|
70
|
+
text += ":no_entry_sign: *<@#{actioner_id}> rejected this request*"
|
71
|
+
|
72
|
+
render_replacing_message(text)
|
73
|
+
else
|
74
|
+
render_error("you are not permitted to reject the request")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def render_cancelled_message(payload, command)
|
79
|
+
unless valid_canceller?
|
80
|
+
return render_error("you cannot cancel the request by other than the requester")
|
81
|
+
end
|
82
|
+
|
83
|
+
text = original_message_text
|
84
|
+
text += "\nThe request was cancelled."
|
85
|
+
|
86
|
+
render_replacing_message(text)
|
87
|
+
end
|
88
|
+
|
89
|
+
def render_replacing_message(text)
|
90
|
+
Slack::Response.new(
|
91
|
+
response_type: "in_channel",
|
92
|
+
replace_original: true,
|
93
|
+
text: text,
|
94
|
+
).to_json
|
95
|
+
end
|
96
|
+
|
97
|
+
def render_error(error)
|
98
|
+
Slack::Response.new(
|
99
|
+
response_type: "ephemeral",
|
100
|
+
replace_original: false,
|
101
|
+
text: "An error occurred: #{error}",
|
102
|
+
).to_json
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_approver?
|
106
|
+
ENV['BANACLE_SKIP_VALIDATION'] || !self_actioned?
|
107
|
+
end
|
108
|
+
|
109
|
+
def valid_rejector?
|
110
|
+
ENV['BANACLE_SKIP_VALIDATION'] || !self_actioned?
|
111
|
+
end
|
112
|
+
|
113
|
+
def valid_canceller?
|
114
|
+
ENV['BANACLE_SKIP_VALIDATION'] || self_actioned?
|
115
|
+
end
|
116
|
+
|
117
|
+
def self_actioned?
|
118
|
+
requester_id == actioner_id
|
119
|
+
end
|
120
|
+
|
121
|
+
def requester_id
|
122
|
+
original_message_text.match(/\A<@([^>]+)>/)[1]
|
123
|
+
end
|
124
|
+
|
125
|
+
def actioner_id
|
126
|
+
payload[:user][:id]
|
127
|
+
end
|
128
|
+
|
129
|
+
def original_message_text
|
130
|
+
payload[:original_message][:text]
|
131
|
+
end
|
132
|
+
|
133
|
+
def payload
|
134
|
+
@payload ||= JSON.parse(params["payload"], symbolize_names: true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module Banacle
|
2
|
+
module Slack
|
3
|
+
Response = Struct.new(:response_type, :replace_original, :text, :attachments, keyword_init: true) do
|
4
|
+
class ValidationError < StandardError; end
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
self.set_default!
|
9
|
+
self.validate!
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_default!
|
14
|
+
self.response_type ||= "in_channel"
|
15
|
+
self.replace_original = true if self.replace_original.nil?
|
16
|
+
self.text ||= ""
|
17
|
+
self.attachments ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
unless self.replace_original.is_a?(TrueClass) || self.replace_original.is_a?(FalseClass)
|
22
|
+
raise ValidationError.new("replace_original must be TrueClass or FalseClass")
|
23
|
+
end
|
24
|
+
|
25
|
+
%i(response_type text).each do |label|
|
26
|
+
unless self.send(label).is_a?(String)
|
27
|
+
raise ValidationError.new("#{attr} must be String")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attachments.each do |a|
|
32
|
+
unless a.is_a?(Slack::Attachment)
|
33
|
+
raise ValidationError.new("One of attachments #{a.inspect} must be Slack::Attachment")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_json
|
39
|
+
self.to_h.tap { |h| h[:attachments] = h[:attachments].map(&:as_json) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_json
|
43
|
+
as_json.to_json
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Attachment = Struct.new(:text, :fallback, :callback_id, :color, \
|
48
|
+
:attachment_type, :actions, keyword_init: true) do
|
49
|
+
class ValidationError < StandardError; end
|
50
|
+
|
51
|
+
def initialize(*args)
|
52
|
+
super
|
53
|
+
self.set_default!
|
54
|
+
self.validate!
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_default!
|
59
|
+
self.text ||= ''
|
60
|
+
self.fallback ||= ''
|
61
|
+
self.callback_id ||= ''
|
62
|
+
self.color ||= ''
|
63
|
+
self.attachment_type ||= ''
|
64
|
+
self.actions ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate!
|
68
|
+
%i(text fallback callback_id color attachment_type).each do |label|
|
69
|
+
unless self.send(label).is_a?(String)
|
70
|
+
raise ValidationError.new("#{attr} must be String")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
self.actions.each do |a|
|
75
|
+
unless a.is_a?(Slack::Action)
|
76
|
+
raise ValidationError.new("One of actions #{a.inspect} must be Slack::Action")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def as_json
|
82
|
+
self.to_h.tap { |h| h[:actions] = h[:actions].map(&:as_json) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Action = Struct.new(:name, :text, :style, :type, :value, :confirm, keyword_init: true) do
|
87
|
+
class ValidationError < StandardError; end
|
88
|
+
|
89
|
+
def self.approve_button
|
90
|
+
self.build_button('approve', style: 'primary', confirm: Confirm.approve)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.reject_button
|
94
|
+
self.build_button('reject', style: 'danger')
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.cancel_button
|
98
|
+
self.build_button('cancel')
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.build_button(value, style: 'default', confirm: nil)
|
102
|
+
self.build(value, style, 'button', confirm)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.build(value, style, type, confirm)
|
106
|
+
self.new(name: value, text: value.capitalize, style: style, type: type, value: value, confirm: confirm)
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize(*args)
|
110
|
+
super
|
111
|
+
self.set_default!
|
112
|
+
self.validate!
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_default!
|
117
|
+
self.name ||= ''
|
118
|
+
self.text ||= ''
|
119
|
+
self.style ||= ''
|
120
|
+
self.type ||= ''
|
121
|
+
self.value ||= ''
|
122
|
+
end
|
123
|
+
|
124
|
+
def validate!
|
125
|
+
%i(name text style type value).each do |label|
|
126
|
+
unless self.send(label).is_a?(String)
|
127
|
+
raise ValidationError.new("#{attr} must be String")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if self.confirm && !self.confirm.is_a?(Confirm)
|
132
|
+
raise ValidationError.new("confirm must be Slack::Confirm")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def approved?
|
137
|
+
self.value == 'approve'
|
138
|
+
end
|
139
|
+
|
140
|
+
def rejected?
|
141
|
+
self.value == 'reject'
|
142
|
+
end
|
143
|
+
|
144
|
+
def cancelled?
|
145
|
+
self.value == 'cancel'
|
146
|
+
end
|
147
|
+
|
148
|
+
def as_json
|
149
|
+
self.to_h.tap do |h|
|
150
|
+
if h[:confirm]
|
151
|
+
h[:confirm] = h[:confirm].as_json
|
152
|
+
else
|
153
|
+
h.delete(:confirm)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
Confirm = Struct.new(:title, :text, :ok_text, :dismiss_text, keyword_init: true) do
|
160
|
+
def self.approve
|
161
|
+
self.new(text: 'The operation will be performed immediately.')
|
162
|
+
end
|
163
|
+
|
164
|
+
def initialize(*args)
|
165
|
+
super
|
166
|
+
self.set_default!
|
167
|
+
self.validate!
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
def set_default!
|
172
|
+
self.title ||= 'Are you sure?'
|
173
|
+
self.text ||= ''
|
174
|
+
self.ok_text ||= 'Yes'
|
175
|
+
self.dismiss_text ||= 'No'
|
176
|
+
end
|
177
|
+
|
178
|
+
def validate!
|
179
|
+
%i(title text ok_text dismiss_text).each do |label|
|
180
|
+
unless self.send(label).is_a?(String)
|
181
|
+
raise ValidationError.new("#{attr} must be String")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def as_json
|
187
|
+
self.to_h
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Banacle
|
5
|
+
class SlackValidator
|
6
|
+
SLACK_SIGNING_SECRET_VERSION = 'v0'.freeze
|
7
|
+
|
8
|
+
def self.valid_signature?(request)
|
9
|
+
new.valid_signature?(request)
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid_signature?(request)
|
13
|
+
body = request.env["rack.request.form_vars"]
|
14
|
+
slack_signature = request.env["HTTP_X_SLACK_SIGNATURE"]
|
15
|
+
slack_timestamp = request.env["HTTP_X_SLACK_REQUEST_TIMESTAMP"]
|
16
|
+
|
17
|
+
# https://api.slack.com/docs/verifying-requests-from-slack#verification_token_deprecation
|
18
|
+
if (slack_timestamp.to_i - Time.now.to_i).abs > 60 * 5
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
sig_basestring = "#{SLACK_SIGNING_SECRET_VERSION}:#{slack_timestamp}:#{body}"
|
23
|
+
digest = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, sig_basestring)
|
24
|
+
|
25
|
+
slack_signature == "#{SLACK_SIGNING_SECRET_VERSION}=#{digest}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def signing_secret
|
29
|
+
ENV.fetch('BANACLE_SLACK_SIGNING_SECRET')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
require 'banacle/aws_wrapper/vpc'
|
4
|
+
require 'banacle/slash_command/error'
|
5
|
+
require 'banacle/slash_command/command'
|
6
|
+
|
7
|
+
module Banacle
|
8
|
+
module SlashCommand
|
9
|
+
class Builder
|
10
|
+
class InvalidActionError < Error; end
|
11
|
+
class InvalidRegionError < Error; end
|
12
|
+
class InvalidVpcError < Error; end
|
13
|
+
class InvalidCidrBlockError < Error; end
|
14
|
+
|
15
|
+
def self.build(action:, region:, vpc_id_or_name:, cidr_blocks:)
|
16
|
+
new(action: action, region: region, vpc_id_or_name: vpc_id_or_name, cidr_blocks: cidr_blocks).build
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(action:, region:, vpc_id_or_name:, cidr_blocks:)
|
20
|
+
@action = action
|
21
|
+
@region = region
|
22
|
+
@vpc_id_or_name = vpc_id_or_name
|
23
|
+
@cidr_blocks = cidr_blocks
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :action, :region, :vpc_id_or_name, :cidr_blocks
|
27
|
+
attr_accessor :vpc_id
|
28
|
+
|
29
|
+
def build
|
30
|
+
validate!
|
31
|
+
set_vpc_id!
|
32
|
+
normalize_cidr_blocks!
|
33
|
+
|
34
|
+
Command.new(action: action, region: region, vpc_id: vpc_id, cidr_blocks: cidr_blocks)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate!
|
38
|
+
validate_action!
|
39
|
+
validate_region!
|
40
|
+
validate_vpc_id_or_name!
|
41
|
+
validate_cidr_blocks!
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_action!
|
45
|
+
if !action || action.empty?
|
46
|
+
raise InvalidActionError.new("action is required")
|
47
|
+
end
|
48
|
+
|
49
|
+
unless Command::PERMITTED_ACTIONS.include?(action)
|
50
|
+
raise InvalidActionError.new("permitted actions are: (#{Command::PERMITTED_ACTIONS.join("|")})")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_region!
|
55
|
+
if !region || region.empty?
|
56
|
+
raise InvalidRegionError.new("region is required")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_vpc_id_or_name!
|
61
|
+
unless vpc_id_or_name
|
62
|
+
raise InvalidVpcError.new("vpc_id or vpc_name is required with #{action} action")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_cidr_blocks!
|
67
|
+
if !cidr_blocks || cidr_blocks.empty?
|
68
|
+
raise InvalidVpcError.new("at least one cidr_block is required with #{action} action")
|
69
|
+
end
|
70
|
+
|
71
|
+
cidr_blocks.each do |cidr_block|
|
72
|
+
begin
|
73
|
+
IPAddr.new(cidr_block)
|
74
|
+
rescue IPAddr::InvalidAddressError
|
75
|
+
raise InvalidCidrBlockError.new("#{cidr_block} is invalid address")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_vpc_id!
|
81
|
+
begin
|
82
|
+
self.vpc_id = AwsWrapper::Vpc.resolve_vpc_id(region, vpc_id_or_name)
|
83
|
+
rescue AwsWrapper::Vpc::InvalidRegionError
|
84
|
+
raise InvalidRegionError.new("specified region: #{region} is invalid")
|
85
|
+
end
|
86
|
+
|
87
|
+
unless vpc_id
|
88
|
+
raise InvalidVpcError.new("specified vpc: #{vpc_id_or_name} in #{region} not found")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def normalize_cidr_blocks!
|
93
|
+
cidr_blocks.map! do |c|
|
94
|
+
ip = IPAddr.new(c)
|
95
|
+
"#{ip}/#{ip.prefix}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'banacle/aws_wrapper/nacl'
|
2
|
+
require 'banacle/aws_wrapper/vpc'
|
3
|
+
|
4
|
+
module Banacle
|
5
|
+
module SlashCommand
|
6
|
+
class Command
|
7
|
+
CREATE_ACTION = 'create'.freeze
|
8
|
+
DELETE_ACTION = 'delete'.freeze
|
9
|
+
|
10
|
+
PERMITTED_ACTIONS = [CREATE_ACTION, DELETE_ACTION].freeze
|
11
|
+
|
12
|
+
def initialize(action:, region:, vpc_id:, cidr_blocks:)
|
13
|
+
@action = action
|
14
|
+
@region = region
|
15
|
+
@vpc_id = vpc_id
|
16
|
+
@cidr_blocks = cidr_blocks
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :action, :region, :vpc_id, :cidr_blocks
|
20
|
+
|
21
|
+
def execute
|
22
|
+
case action
|
23
|
+
when CREATE_ACTION
|
24
|
+
create_nacl
|
25
|
+
when DELETE_ACTION
|
26
|
+
delete_nacl
|
27
|
+
else
|
28
|
+
# Do nothing
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
{
|
34
|
+
action: action,
|
35
|
+
region: region,
|
36
|
+
vpc_id: vpc_id,
|
37
|
+
cidr_blocks: cidr_blocks,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def create_nacl
|
44
|
+
results = AwsWrapper::Nacl.create_network_acl_ingress_entries(
|
45
|
+
region: region,
|
46
|
+
vpc_id: vpc_id,
|
47
|
+
cidr_blocks: cidr_blocks,
|
48
|
+
)
|
49
|
+
|
50
|
+
format_results(results)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_nacl
|
54
|
+
results = AwsWrapper::Nacl.delete_network_acl_entries(
|
55
|
+
region: region,
|
56
|
+
vpc_id: vpc_id,
|
57
|
+
cidr_blocks: cidr_blocks,
|
58
|
+
)
|
59
|
+
|
60
|
+
format_results(results)
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_results(results)
|
64
|
+
results.map do |cidr_block, result|
|
65
|
+
t = "#{action} DENY #{cidr_block} => "
|
66
|
+
if result.status
|
67
|
+
t += "succeeded"
|
68
|
+
else
|
69
|
+
t += "error: #{result.error}"
|
70
|
+
end
|
71
|
+
end.join("\n")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'banacle/slash_command/error'
|
2
|
+
require 'banacle/slash_command/builder'
|
3
|
+
|
4
|
+
module Banacle
|
5
|
+
module SlashCommand
|
6
|
+
class Parser
|
7
|
+
class ParseError < Error; end
|
8
|
+
|
9
|
+
def self.parse(text)
|
10
|
+
new.parse(text)
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# /banacle (create|delete) [region] [vpc_id or vpc_name] [cidr_block1,cidr_block2,...]
|
15
|
+
#
|
16
|
+
def parse(text)
|
17
|
+
elems = text.split(" ")
|
18
|
+
action, region, vpc_id_or_name, cidr_blocks_str = elems
|
19
|
+
|
20
|
+
unless action
|
21
|
+
raise ParseError.new("action is required")
|
22
|
+
end
|
23
|
+
|
24
|
+
unless region
|
25
|
+
raise ParseError.new("region is required")
|
26
|
+
end
|
27
|
+
|
28
|
+
cidr_blocks = []
|
29
|
+
if cidr_blocks_str
|
30
|
+
cidr_blocks = cidr_blocks_str.split(",")
|
31
|
+
end
|
32
|
+
|
33
|
+
SlashCommand::Builder.build(
|
34
|
+
action: action,
|
35
|
+
region: region,
|
36
|
+
vpc_id_or_name: vpc_id_or_name,
|
37
|
+
cidr_blocks: cidr_blocks,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'banacle/slack'
|
2
|
+
require 'banacle/slash_command/builder'
|
3
|
+
require 'banacle/slash_command/command'
|
4
|
+
|
5
|
+
module Banacle
|
6
|
+
module SlashCommand
|
7
|
+
class Renderer
|
8
|
+
def self.render(params, command)
|
9
|
+
new.render(params, command)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.render_error(error)
|
13
|
+
new.render_error(error)
|
14
|
+
end
|
15
|
+
|
16
|
+
def render(params, command)
|
17
|
+
render_approval_request(params, command)
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_error(error)
|
21
|
+
Slack::Response.new(
|
22
|
+
response_type: "ephemeral",
|
23
|
+
text: "An error occurred: #{error}",
|
24
|
+
).to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_approval_request(params, command)
|
28
|
+
text = <<-EOS
|
29
|
+
<@#{params["user_id"]}> wants to *#{command.action} NACL DENY entry* under the following conditions:
|
30
|
+
```
|
31
|
+
#{JSON.pretty_generate(command.to_h)}
|
32
|
+
```
|
33
|
+
EOS
|
34
|
+
|
35
|
+
Slack::Response.new(
|
36
|
+
response_type: "in_channel",
|
37
|
+
text: text,
|
38
|
+
attachments: [
|
39
|
+
Slack::Attachment.new(
|
40
|
+
text: "*Approval Request*",
|
41
|
+
fallback: "TBD",
|
42
|
+
callback_id: "banacle_approval_request",
|
43
|
+
color: "#3AA3E3",
|
44
|
+
attachment_type: "default",
|
45
|
+
actions: [
|
46
|
+
Slack::Action.approve_button,
|
47
|
+
Slack::Action.reject_button,
|
48
|
+
Slack::Action.cancel_button,
|
49
|
+
]
|
50
|
+
),
|
51
|
+
],
|
52
|
+
).to_json
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/banacle.rb
ADDED