jackal-nellie 0.1.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 +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTING.md +25 -0
- data/LICENSE +13 -0
- data/README.md +17 -0
- data/jackal-nellie.gemspec +15 -0
- data/lib/jackal-nellie.rb +10 -0
- data/lib/jackal-nellie/formatter.rb +48 -0
- data/lib/jackal-nellie/formatter/github_commit_comment.rb +39 -0
- data/lib/jackal-nellie/formatter/slack_comment.rb +42 -0
- data/lib/jackal-nellie/processor.rb +187 -0
- data/lib/jackal-nellie/version.rb +6 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd3a2d03685f700eb013bedbeec6447f84aaa911
|
4
|
+
data.tar.gz: 652c480726a50171dea994886809953ce6ad219c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5d8057508bf29fa673cd03ba23eb29458686f4e3c153d955bb87d4d05d12b3f59ef8bd08005da816d1e214b4999eec44b9c62bef4cd3050bc1abda8cb789bf7
|
7
|
+
data.tar.gz: 864078a27dea8acf410a8ecb0e1cecdbec7a84a90a209dbb768aa70bfc1e3d209ef6a96bd753118cb3558967eaa10ffe5270de8a626b6402ca9dd554b9a86ad9
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Branches
|
4
|
+
|
5
|
+
### `master` branch
|
6
|
+
|
7
|
+
The master branch is the current stable released version.
|
8
|
+
|
9
|
+
### `develop` branch
|
10
|
+
|
11
|
+
The develop branch is the current edge of development.
|
12
|
+
|
13
|
+
## Pull requests
|
14
|
+
|
15
|
+
* https://github.com/carnivore-rb/jackal-nellie/pulls
|
16
|
+
|
17
|
+
Please base all pull requests of the `develop` branch. Merges to
|
18
|
+
`master` only occur through the `develop` branch. Pull requests
|
19
|
+
based on `master` will likely be cherry picked.
|
20
|
+
|
21
|
+
## Issues
|
22
|
+
|
23
|
+
Need to report an issue? Use the github issues:
|
24
|
+
|
25
|
+
* https://github.com/carnivore-rb/jackal-nellie/issues
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2014 Chris Roberts
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Jackal Nellie
|
2
|
+
|
3
|
+
Run commands against a repository. That's it.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
This service assumes the payload information provided via the code
|
8
|
+
fetcher service.
|
9
|
+
|
10
|
+
## Configuration
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
# Info
|
15
|
+
|
16
|
+
* Repository: https://github.com/carnivore-rb/jackal-nellie
|
17
|
+
* IRC: Freenode @ #carnivore
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'jackal-nellie/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'jackal-nellie'
|
5
|
+
s.version = Jackal::Nellie::VERSION.version
|
6
|
+
s.summary = 'Message processing helper'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'code@chrisroberts.org'
|
9
|
+
s.homepage = 'https://github.com/carnivore-rb/jackal-nellie'
|
10
|
+
s.description = 'Just do stuff'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.license = 'Apache 2.0'
|
13
|
+
s.add_dependency 'jackal'
|
14
|
+
s.files = Dir['lib/**/*'] + %w(jackal-nellie.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Jackal
|
2
|
+
module Nellie
|
3
|
+
module Formatter
|
4
|
+
module MessageExtract
|
5
|
+
|
6
|
+
# Message for successful results
|
7
|
+
#
|
8
|
+
# @param payload [Smash]
|
9
|
+
# @return [String]
|
10
|
+
def success_message(payload)
|
11
|
+
repo = [
|
12
|
+
payload.get(:data, :code_fetcher, :info, :owner),
|
13
|
+
payload.get(:data, :code_fetcher, :info, :name)
|
14
|
+
].join('/')
|
15
|
+
sha = payload.get(:data, :code_fetcher, :info, :commit_sha)
|
16
|
+
"[nellie]: Job completed successfully! (#{repo}@#{sha})"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Message for failure results
|
20
|
+
#
|
21
|
+
# @param payload [Smash]
|
22
|
+
# @return [String]
|
23
|
+
def failure_message(payload)
|
24
|
+
msg = ['[nellie]: Failure encountered:']
|
25
|
+
msg << ''
|
26
|
+
failed_history = payload.fetch(:data, :nellie, :history, {}).detect do |i|
|
27
|
+
i[:exit_code] != 0
|
28
|
+
end
|
29
|
+
if(failed_history)
|
30
|
+
msg << '* STDOUT:' << '' << '```'
|
31
|
+
msg << asset_store.get(failed_history.get(:logs, :stdout)).read
|
32
|
+
msg << '```' << ''
|
33
|
+
msg << '* STDERR:' << '' << '```'
|
34
|
+
msg << asset_store.get(failed_history.get(:logs, :stderr)).read
|
35
|
+
msg << '```'
|
36
|
+
else
|
37
|
+
msg << '```' << 'Failed to locate logs' << '```'
|
38
|
+
end
|
39
|
+
msg.join("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'jackal-nellie/formatter/github_commit_comment'
|
48
|
+
require 'jackal-nellie/formatter/slack_comment'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'jackal'
|
2
|
+
|
3
|
+
module Jackal
|
4
|
+
module Nellie
|
5
|
+
module Formatter
|
6
|
+
|
7
|
+
class GithubCommitComment < Jackal::Formatter
|
8
|
+
|
9
|
+
include MessageExtract
|
10
|
+
|
11
|
+
# Source service
|
12
|
+
SOURCE = :nellie
|
13
|
+
# Destination service
|
14
|
+
DESTINATION = :github_kit
|
15
|
+
|
16
|
+
# Format payload to provide output comment to GitHub
|
17
|
+
#
|
18
|
+
# @param payload [Smash]
|
19
|
+
def format(payload)
|
20
|
+
payload.set(:data, :github_kit, :commit_comment,
|
21
|
+
Smash.new(
|
22
|
+
:repository => [
|
23
|
+
payload.get(:data, :code_fetcher, :info, :name),
|
24
|
+
payload.get(:data, :code_fetcher, :info, :owner)
|
25
|
+
].join('/'),
|
26
|
+
:reference => payload.get(:data, :code_fetcher, :info, :commit_sha)
|
27
|
+
)
|
28
|
+
)
|
29
|
+
if(payload.get(:data, :nellie, :result, :success))
|
30
|
+
payload.set(:data, :github_kit, :commit_comment, :message, success_message(payload))
|
31
|
+
else
|
32
|
+
payload.set(:data, :github_kit, :commit_comment, :message, failure_message(payload))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'jackal'
|
2
|
+
|
3
|
+
module Jackal
|
4
|
+
module Nellie
|
5
|
+
module Formatter
|
6
|
+
|
7
|
+
class SlackComment < Jackal::Formatter
|
8
|
+
|
9
|
+
include MessageExtract
|
10
|
+
|
11
|
+
# Source service
|
12
|
+
SOURCE = :nellie
|
13
|
+
# Destination service
|
14
|
+
DESTINATION = :slack
|
15
|
+
|
16
|
+
# Format payload to provide output comment to GitHub
|
17
|
+
#
|
18
|
+
# @param payload [Smash]
|
19
|
+
def format(payload)
|
20
|
+
if(payload.get(:data, :nellie, :result))
|
21
|
+
msgs = payload.fetch(:data, :slack, :messages, [])
|
22
|
+
if(payload.get(:data, :nellie, :result, :complete))
|
23
|
+
msgs << Smash.new(
|
24
|
+
:description => 'Nellie job result:',
|
25
|
+
:message => success_message(payload),
|
26
|
+
:color => :good
|
27
|
+
)
|
28
|
+
else
|
29
|
+
msgs << Smash.new(
|
30
|
+
:description => 'Nellie job result:',
|
31
|
+
:message => failure_message(payload),
|
32
|
+
:color => :bad
|
33
|
+
)
|
34
|
+
end
|
35
|
+
payload.set(:data, :slack, :messages, msgs)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'jackal-nellie'
|
2
|
+
|
3
|
+
module Jackal
|
4
|
+
module Nellie
|
5
|
+
# Command processor
|
6
|
+
class Processor < Callback
|
7
|
+
|
8
|
+
# Default nellie file name
|
9
|
+
DEFAULT_SCRIPT_NAME = '.nellie'
|
10
|
+
# Default working directory
|
11
|
+
DEFAULT_WORKING_DIRECTORY = '/tmp/nellie'
|
12
|
+
|
13
|
+
# Setup callback
|
14
|
+
def setup(*_)
|
15
|
+
require 'fileutils'
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String] working directory
|
19
|
+
def working_directory
|
20
|
+
memoize(:working_directory) do
|
21
|
+
wd = config.fetch(:working_directory, DEFAULT_WORKING_DIRECTORY)
|
22
|
+
FileUtils.mkdir_p(wd)
|
23
|
+
wd
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] nellie command file name
|
28
|
+
def nellie_script_name
|
29
|
+
config.fetch(:script_name, DEFAULT_SCRIPT_NAME)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Determine validity of message
|
33
|
+
#
|
34
|
+
# @param message [Carnivore::Message]
|
35
|
+
# @return [Truthy, Falsey]
|
36
|
+
def valid?(message)
|
37
|
+
super do |payload|
|
38
|
+
payload.get(:data, :code_fetcher, :asset) &&
|
39
|
+
!payload.get(:data, :nellie, :result)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Run nellie!
|
44
|
+
#
|
45
|
+
# @param message [Carnivore::Message]
|
46
|
+
def execute(message)
|
47
|
+
failure_wrap(message) do |payload|
|
48
|
+
debug "Processing nellie payload!"
|
49
|
+
nellie_cwd = fetch_code(payload)
|
50
|
+
unless(payload.get(:data, :nellie, :commands))
|
51
|
+
extract_nellie_commands(nellie_cwd, payload)
|
52
|
+
end
|
53
|
+
if(payload.get(:data, :nellie, :commands))
|
54
|
+
execute_commands(nellie_cwd, payload)
|
55
|
+
else
|
56
|
+
warn "No nellie commands found for execution on message! (#{message})"
|
57
|
+
end
|
58
|
+
job_completed(:nellie, payload, message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Execute commands
|
63
|
+
#
|
64
|
+
# @param nellie_cwd [String] repository directory
|
65
|
+
# @param payload [Smash]
|
66
|
+
# @return [TrueClass]
|
67
|
+
def execute_commands(nellie_cwd, payload)
|
68
|
+
process_environment = payload.fetch(:data, :nellie, :environment, Smash.new).merge(
|
69
|
+
Smash.new(
|
70
|
+
'NELLIE_GIT_COMMIT_SHA' => payload.get(:data, :code_fetcher, :info, :commit_sha),
|
71
|
+
'NELLIE_GIT_REF' => payload.get(:data, :code_fetcher, :info, :reference)
|
72
|
+
)
|
73
|
+
)
|
74
|
+
commands = [payload.get(:data, :nellie, :commands)].flatten.compact
|
75
|
+
results = run_commands(commands, process_environment, payload, nellie_cwd)
|
76
|
+
payload.set(:data, :nellie, :history, results)
|
77
|
+
payload[:data][:nellie].delete(:commands)
|
78
|
+
payload[:data][:nellie].delete(:environment)
|
79
|
+
unless(payload.get(:data, :nellie, :result, :failed))
|
80
|
+
payload.set(:data, :nellie, :result, :complete, true)
|
81
|
+
end
|
82
|
+
payload.set(:data, :nellie, :status,
|
83
|
+
payload.get(:data, :nellie, :result, :complete) ? 'success' : 'error'
|
84
|
+
)
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
# Run collection of commands
|
89
|
+
#
|
90
|
+
# @param commands [Array<String>] commands to execute
|
91
|
+
# @param env [Hash] environment variables for process
|
92
|
+
# @param payload [Smash]
|
93
|
+
# @param process_cwd [String] working directory for process
|
94
|
+
# @return [Array<Smash>] command results ({:start_time, :stop_time, :exit_code, :logs, :timed_out})
|
95
|
+
def run_commands(commands, env, payload, process_cwd)
|
96
|
+
results = []
|
97
|
+
commands.each do |command|
|
98
|
+
process_manager.process(payload[:id], command) do |process|
|
99
|
+
result = Smash.new
|
100
|
+
stdout = process_manager.create_io_tmp(Celluloid.uuid, 'stdout')
|
101
|
+
stderr = process_manager.create_io_tmp(Celluloid.uuid, 'stderr')
|
102
|
+
process.io.stdout = stdout
|
103
|
+
process.io.stderr = stderr
|
104
|
+
process.cwd = process_cwd
|
105
|
+
process.environment.replace(env.dup)
|
106
|
+
process.leader = true
|
107
|
+
result[:start_time] = Time.now.to_i
|
108
|
+
process.start
|
109
|
+
begin
|
110
|
+
process.poll_for_exit(config.fetch(:max_execution_time, 60))
|
111
|
+
rescue ChildProcess::TimeoutError
|
112
|
+
process.stop
|
113
|
+
result[:timed_out] = true
|
114
|
+
end
|
115
|
+
result[:stop_time] = Time.now.to_i
|
116
|
+
result[:exit_code] = process.exit_code
|
117
|
+
[stdout, stderr].each do |io|
|
118
|
+
key = "nellie/#{File.basename(io.path)}"
|
119
|
+
type = io.path.split('-').last
|
120
|
+
io.rewind
|
121
|
+
asset_store.put(key, io)
|
122
|
+
result.set(:logs, type, key)
|
123
|
+
io.close
|
124
|
+
File.delete(io.path)
|
125
|
+
end
|
126
|
+
results << result
|
127
|
+
unless(process.exit_code == 0)
|
128
|
+
payload.set(:data, :nellie, :result, :failed, true)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
break if payload.get(:data, :nellie, :result, :failed)
|
132
|
+
end
|
133
|
+
results
|
134
|
+
end
|
135
|
+
|
136
|
+
# Extract nellie commands from repository file
|
137
|
+
#
|
138
|
+
# @param nellie_cwd [String] path to repository directory
|
139
|
+
# @param payload [Smash]
|
140
|
+
# @return [TrueClass, FalseClass]
|
141
|
+
def extract_nellie_commands(nellie_cwd, payload)
|
142
|
+
script_path = File.join(nellie_cwd, nellie_script_name)
|
143
|
+
if(File.exists?(script_path))
|
144
|
+
begin
|
145
|
+
nellie_cmds = MultiJson.load(File.read(script_path)).to_smash #Bogo::Config.new(script_path).data
|
146
|
+
debug "Nellie file is structured data. Populating commands into payload. (#{script_path})"
|
147
|
+
payload[:data].set(:nellie, :commands, nellie_cmds[:commands])
|
148
|
+
payload[:data].set(:nellie, :environment, nellie_cmds.fetch(:environment, {}))
|
149
|
+
rescue => e
|
150
|
+
debug "Parsing nellie file failed. Assuming direct execution. (#{script_path})"
|
151
|
+
payload[:data].set(:nellie, :commands,
|
152
|
+
File.executable?(script_path) ? script_path : "/bin/bash #{script_path}"
|
153
|
+
)
|
154
|
+
end
|
155
|
+
true
|
156
|
+
else
|
157
|
+
debug "Failed to locate nellie command file at: #{script_path}"
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Fetch code from asset store and unpack for local usage
|
163
|
+
#
|
164
|
+
# @param payload [Smash]
|
165
|
+
# @return [String] directory path
|
166
|
+
def fetch_code(payload)
|
167
|
+
repository_path = File.join(
|
168
|
+
working_directory,
|
169
|
+
payload[:message_id],
|
170
|
+
payload.get(:data, :code_fetcher, :asset)
|
171
|
+
)
|
172
|
+
if(File.directory?(repository_path))
|
173
|
+
warn "Existing path detected for repository unpack. Removing! (#{repository_path})"
|
174
|
+
FileUtils.rm_rf(repository_path)
|
175
|
+
end
|
176
|
+
FileUtils.mkdir_p(File.dirname(repository_path))
|
177
|
+
asset_store.unpack(
|
178
|
+
asset_store.get(payload.get(:data, :code_fetcher, :asset)),
|
179
|
+
repository_path,
|
180
|
+
:disable_overwrite
|
181
|
+
)
|
182
|
+
repository_path
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jackal-nellie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Roberts
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jackal
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Just do stuff
|
28
|
+
email: code@chrisroberts.org
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- CHANGELOG.md
|
34
|
+
- CONTRIBUTING.md
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- jackal-nellie.gemspec
|
38
|
+
- lib/jackal-nellie.rb
|
39
|
+
- lib/jackal-nellie/formatter.rb
|
40
|
+
- lib/jackal-nellie/formatter/github_commit_comment.rb
|
41
|
+
- lib/jackal-nellie/formatter/slack_comment.rb
|
42
|
+
- lib/jackal-nellie/processor.rb
|
43
|
+
- lib/jackal-nellie/version.rb
|
44
|
+
homepage: https://github.com/carnivore-rb/jackal-nellie
|
45
|
+
licenses:
|
46
|
+
- Apache 2.0
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.2.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Message processing helper
|
68
|
+
test_files: []
|
69
|
+
has_rdoc:
|