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 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
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial release
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,10 @@
1
+ require 'jackal'
2
+
3
+ module Jackal
4
+ module Nellie
5
+ autoload :Processor, 'jackal-nellie/processor'
6
+ end
7
+ end
8
+
9
+ require 'jackal-nellie/version'
10
+ require 'jackal-nellie/formatter'
@@ -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
@@ -0,0 +1,6 @@
1
+ module Jackal
2
+ module Nellie
3
+ # Current library version
4
+ VERSION = Gem::Version.new('0.1.0')
5
+ end
6
+ 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: