jackal-nellie 0.1.0

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