jackal-cfn 0.2.16 → 0.2.18
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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +71 -0
- data/lib/jackal-cfn/resource/orchestration_unit.rb +188 -0
- data/lib/jackal-cfn/version.rb +1 -1
- data/lib/jackal-cfn.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d68462a480bb426c377656d7bc2ba63725dd1e61
|
|
4
|
+
data.tar.gz: 35b702d77313eaead8aac1d61e66ddbdbfb55f5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a49947efa919dc54ded09b699239054a7648747b474d2317ae7bef30f82a980c2a06ec6274462956abbbb4824c56d62502365f3368bc9984230a01b789a857fd
|
|
7
|
+
data.tar.gz: 0012138672f9a8c7eaccadba908166d0a053b441c292f11c6fa4de86b9caf1189efa8fa2af68ef72956f93208a2900718d5365114ee091570f10da4f06b61533
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -316,6 +316,77 @@ Configuration:
|
|
|
316
316
|
}
|
|
317
317
|
```
|
|
318
318
|
|
|
319
|
+
#### `Jackal::Cfn::OrchestrationUnit`
|
|
320
|
+
|
|
321
|
+
This resource provides a custom "orchestration unit". The "orchestration unit" consists
|
|
322
|
+
of a piece of code. It is similar to the `AWS::Lambda::Function` resource but is more
|
|
323
|
+
freeform. The command to be executed can be provided as an inline string, or as a remote
|
|
324
|
+
URL to a compressed zip file containing a `run.sh` file to execute. If the return value
|
|
325
|
+
is JSON, the values will be accessible using the `Fn::GetAtt` intrinsic function on the
|
|
326
|
+
resource.
|
|
327
|
+
|
|
328
|
+
Resource Usage:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"Type": "Custom::OrchestrationUnit",
|
|
333
|
+
"Properties": {
|
|
334
|
+
"Exec": "STRING_COMMAND",
|
|
335
|
+
"ExecZip": "REMOTE_URL_TO_ZIP",
|
|
336
|
+
"Env": {
|
|
337
|
+
"CUSTOM_ENV_VARS": "FOR_COMMAND"
|
|
338
|
+
},
|
|
339
|
+
"OnCreate": {
|
|
340
|
+
"Exec": "STRING_COMMAND",
|
|
341
|
+
"ExecZip": "REMOTE_URL_TO_ZIP",
|
|
342
|
+
"Env": {
|
|
343
|
+
"CUSTOM_ENV_VARS": "FOR_COMMAND"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
"OnUpdate": {
|
|
347
|
+
"Exec": "STRING_COMMAND",
|
|
348
|
+
"ExecZip": "REMOTE_URL_TO_ZIP",
|
|
349
|
+
"Env": {
|
|
350
|
+
"CUSTOM_ENV_VARS": "FOR_COMMAND"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
"OnDelete": {
|
|
354
|
+
"Exec": "STRING_COMMAND",
|
|
355
|
+
"ExecZip": "REMOTE_URL_TO_ZIP",
|
|
356
|
+
"Env": {
|
|
357
|
+
"CUSTOM_ENV_VARS": "FOR_COMMAND"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The `Exec` inline string command has precedence over the `ExecZip` if both are provided. The
|
|
366
|
+
root `Exec` or `ExecZip` are the default commands to be run on _any action_. Customized
|
|
367
|
+
commands per action can be provided using the `OnCreate`, `OnUpdate`, or `OnDelete` properties.
|
|
368
|
+
Environment variables defined in the root properties will be merged with environment variables
|
|
369
|
+
defined for explicit action commands.
|
|
370
|
+
|
|
371
|
+
Resource response:
|
|
372
|
+
|
|
373
|
+
If command result is a non-JSON value:
|
|
374
|
+
|
|
375
|
+
```json
|
|
376
|
+
{
|
|
377
|
+
"OrchestrationUnitValue": "RESULT_OF_COMMAND"
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
If the result of the command is a JSON value (for example `{"MyKey": "MyValue"}`):
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"MyKey": "MyValue",
|
|
386
|
+
"OrchestrationUnitValue": "{\"MyKey\": \"MyValue\"}"
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
319
390
|
## Info
|
|
320
391
|
|
|
321
392
|
* Repository: https://github.com/carnviore-rb/jackal-cfn
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require 'jackal-cfn'
|
|
2
|
+
|
|
3
|
+
module Jackal
|
|
4
|
+
module Cfn
|
|
5
|
+
# Execute arbitrary actions
|
|
6
|
+
#
|
|
7
|
+
# Expected resource:
|
|
8
|
+
# {
|
|
9
|
+
# "Type": "Custom::OrchestrationUnit",
|
|
10
|
+
# "Properties": {
|
|
11
|
+
# "Parameters": {
|
|
12
|
+
# "OnCreate": {
|
|
13
|
+
# "Exec": "SHELL_COMMAND",
|
|
14
|
+
# "ExecZip": "DOWNLOAD_URI",
|
|
15
|
+
# "Env": {
|
|
16
|
+
# }
|
|
17
|
+
# },
|
|
18
|
+
# "OnUpdate": {
|
|
19
|
+
# "Exec": "SHELL_COMMAND",
|
|
20
|
+
# "ExecZip": "DOWNLOAD_URI",
|
|
21
|
+
# "Env": {
|
|
22
|
+
# }
|
|
23
|
+
# },
|
|
24
|
+
# "OnDelete": {
|
|
25
|
+
# "Exec": "SHELL_COMMAND",
|
|
26
|
+
# "ExecZip": "DOWNLOAD_URI",
|
|
27
|
+
# "Env": {
|
|
28
|
+
# }
|
|
29
|
+
# },
|
|
30
|
+
# "Exec": "SHELL_COMMAND",
|
|
31
|
+
# "ExecZip": "DOWNLOAD_URI",
|
|
32
|
+
# "Env": {
|
|
33
|
+
# }
|
|
34
|
+
# }
|
|
35
|
+
# }
|
|
36
|
+
# }
|
|
37
|
+
#
|
|
38
|
+
class OrchestrationUnit < Jackal::Cfn::Resource
|
|
39
|
+
|
|
40
|
+
# Execute orchestration unit
|
|
41
|
+
#
|
|
42
|
+
# @param message [Carnivore::Message]
|
|
43
|
+
def execute(message)
|
|
44
|
+
failure_wrap(message) do |payload|
|
|
45
|
+
cfn_resource = rekey_hash(payload.get(:data, :cfn_resource))
|
|
46
|
+
properties = rekey_hash(cfn_resource[:resource_properties])
|
|
47
|
+
parameters = rekey_hash(properties[:parameters])
|
|
48
|
+
cfn_response = build_response(cfn_resource)
|
|
49
|
+
unit = unit_for(cfn_resource[:request_type], parameters)
|
|
50
|
+
working_dir = create_working_directory(payload[:id])
|
|
51
|
+
run_unit(unit, working_dir, cfn_response)
|
|
52
|
+
FileUtils.rm_rf(working_dir)
|
|
53
|
+
respond_to_stack(cfn_response, cfn_resource[:response_url])
|
|
54
|
+
job_completed(:jackal_cfn, payload, message)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Create a working directory for command execution
|
|
59
|
+
#
|
|
60
|
+
# @param uuid [String] unique identifier
|
|
61
|
+
# @return [String] path
|
|
62
|
+
def create_working_directory(uuid)
|
|
63
|
+
dir_path = File.join(
|
|
64
|
+
config.fetch(
|
|
65
|
+
:working_directory,
|
|
66
|
+
'/tmp/jackal-cfn'
|
|
67
|
+
),
|
|
68
|
+
uuid
|
|
69
|
+
)
|
|
70
|
+
FileUtils.mkdir_p(dir_path)
|
|
71
|
+
dir_path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Fetch compressed zip file from remote location and unpack into
|
|
75
|
+
# provided working directory
|
|
76
|
+
#
|
|
77
|
+
# @param unit [Hash] orchestration unit
|
|
78
|
+
# @param working_directory [String] local path to working directory
|
|
79
|
+
# @return [Hash] unit
|
|
80
|
+
# @note will automatically set `unit['Exec'] = './run.sh'`
|
|
81
|
+
def fetch_and_unpack_exec(unit, working_directory)
|
|
82
|
+
result = HTTP.get(unit[:exec_zip])
|
|
83
|
+
file = Tempfile.new('orchestration-unit')
|
|
84
|
+
while(data = result.body.readpartial(2048))
|
|
85
|
+
file.write data
|
|
86
|
+
end
|
|
87
|
+
file.flush
|
|
88
|
+
file.rewind
|
|
89
|
+
asset_store.unpack(file, working_directory)
|
|
90
|
+
unit[:exec] = File.join(working_directory, 'run.sh')
|
|
91
|
+
unit
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Run the unit and set result information into response
|
|
95
|
+
#
|
|
96
|
+
# @param unit [Hash] orchestration unit
|
|
97
|
+
# @param working_directory [String] path to local working directory
|
|
98
|
+
# @param response [Hash] CFN response
|
|
99
|
+
# @return [Hash] CFN response
|
|
100
|
+
def run_unit(unit, working_directory, response)
|
|
101
|
+
if(unit[:exec_zip])
|
|
102
|
+
fetch_and_unpack_exec(unit, working_directory)
|
|
103
|
+
end
|
|
104
|
+
if(unit[:exec])
|
|
105
|
+
result = Smash.new
|
|
106
|
+
process_manager.process(unit.hash, unit[:exec]) do |process|
|
|
107
|
+
stdout = process_manager.create_io_tmp(Carnivore.uuid, 'stdout')
|
|
108
|
+
stderr = process_manager.create_io_tmp(Carnivore.uuid, 'stderr')
|
|
109
|
+
process.io.stdout = stdout
|
|
110
|
+
process.io.stderr = stderr
|
|
111
|
+
process.cwd = working_directory
|
|
112
|
+
if(unit[:env])
|
|
113
|
+
process.environment.replace(unit[:env])
|
|
114
|
+
end
|
|
115
|
+
process.leader = true
|
|
116
|
+
result[:start_time] = Time.now.to_i
|
|
117
|
+
process.start
|
|
118
|
+
begin
|
|
119
|
+
process.poll_for_exit(config.fetch(:max_execution_time, 60))
|
|
120
|
+
rescue ChildProcess::TimeoutError
|
|
121
|
+
process.stop
|
|
122
|
+
result[:timed_out] = true
|
|
123
|
+
end
|
|
124
|
+
result[:stop_time] = Time.now.to_i
|
|
125
|
+
result[:exit_code] = process.exit_code
|
|
126
|
+
stdout.rewind
|
|
127
|
+
# TODO: size check
|
|
128
|
+
result[:content] = stdout.read
|
|
129
|
+
if(process.exit_code != 0)
|
|
130
|
+
stderr.rewind
|
|
131
|
+
result[:error_message] = stderr.read
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
if(result[:exit_code] == 0)
|
|
135
|
+
response['Data']['OrchestrationUnitResult'] = result[:content]
|
|
136
|
+
begin
|
|
137
|
+
j_result = MultiJson.load(result[:content])
|
|
138
|
+
response['Data'] = j_result.merge(response['Data'])
|
|
139
|
+
rescue MultiJson::ParseError => e
|
|
140
|
+
debug 'Command result not JSON data'
|
|
141
|
+
end
|
|
142
|
+
response
|
|
143
|
+
else
|
|
144
|
+
raise "Execution failed! Exit code: #{result[:exit_code]} Reason: #{result[:error_message]}"
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
response['Data']['OrchestrationUnitResult'] = 'No command executed!'
|
|
148
|
+
response
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Extract unit information based on received request type
|
|
153
|
+
#
|
|
154
|
+
# @param request_type [String] CFN request type
|
|
155
|
+
# @param parameters [Hash] resource parameters
|
|
156
|
+
# @return [Hash] orchestration unit
|
|
157
|
+
def unit_for(request_type, parameters)
|
|
158
|
+
base_key = "on_#{request_type.to_s.downcase}"
|
|
159
|
+
result = Smash.new
|
|
160
|
+
if(direct_unit = parameters[base_key])
|
|
161
|
+
[:exec, :exec_zip, :env].each do |p_key|
|
|
162
|
+
if(direct_unit[p_key])
|
|
163
|
+
result[p_key] = direct[p_key]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
unless(result[:exec] || result[:exec_zip])
|
|
168
|
+
if(parameters[:exec])
|
|
169
|
+
result[:exec] = parameters[:exec]
|
|
170
|
+
elsif(parameters[:exec_zip])
|
|
171
|
+
result[:exec_zip] = parameters[:exec_zip]
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
if(parameters[:env])
|
|
175
|
+
if(result[:env])
|
|
176
|
+
result[:env] = parameters[:env].merge(result[:env])
|
|
177
|
+
else
|
|
178
|
+
result[:env] = parameters[:env]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
result[:env] ||= Smash.new
|
|
182
|
+
result[:env]['CFN_REQUEST_TYPE'] = request_type.to_s.upcase
|
|
183
|
+
result
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
data/lib/jackal-cfn/version.rb
CHANGED
data/lib/jackal-cfn.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Jackal
|
|
|
13
13
|
autoload :AmiManager, 'jackal-cfn/resource/ami_manager'
|
|
14
14
|
autoload :AmiRegister, 'jackal-cfn/resource/ami_register'
|
|
15
15
|
autoload :JackalStack, 'jackal-cfn/resource/jackal_stack'
|
|
16
|
+
autoload :OrchestrationUnit, 'jackal-cfn/resource/orchestration_unit'
|
|
16
17
|
autoload :Scrubber, 'jackal-cfn/resource/scrubber'
|
|
17
18
|
end
|
|
18
19
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jackal-cfn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.18
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Roberts
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jackal
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- lib/jackal-cfn/resource/ami_register.rb
|
|
72
72
|
- lib/jackal-cfn/resource/hash_extractor.rb
|
|
73
73
|
- lib/jackal-cfn/resource/jackal_stack.rb
|
|
74
|
+
- lib/jackal-cfn/resource/orchestration_unit.rb
|
|
74
75
|
- lib/jackal-cfn/resource/scrubber.rb
|
|
75
76
|
- lib/jackal-cfn/utils.rb
|
|
76
77
|
- lib/jackal-cfn/utils/fog.rb
|