octopusci 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -0
- data/README +63 -0
- data/bin/octopusci +56 -0
- data/bin/octopusci-db-migrate +10 -0
- data/bin/octopusci-skel +52 -0
- data/bin/octopusci-tentacles +8 -0
- data/bin/pusci-stage +67 -0
- data/config.ru +5 -0
- data/db/migrate/0001_init.rb +29 -0
- data/lib/octopusci/config.rb +95 -0
- data/lib/octopusci/errors.rb +4 -0
- data/lib/octopusci/helpers.rb +60 -0
- data/lib/octopusci/job.rb +43 -0
- data/lib/octopusci/notifier/job_complete.html.erb +16 -0
- data/lib/octopusci/notifier/job_complete.text.erb +9 -0
- data/lib/octopusci/notifier.rb +37 -0
- data/lib/octopusci/queue.rb +41 -0
- data/lib/octopusci/schema.rb +9 -0
- data/lib/octopusci/server/public/images/noise.gif +0 -0
- data/lib/octopusci/server/public/images/status.png +0 -0
- data/lib/octopusci/server/public/images/status2.png +0 -0
- data/lib/octopusci/server/public/javascripts/jquery-1.6.4.min.js +4 -0
- data/lib/octopusci/server/views/hello_world.erb +1 -0
- data/lib/octopusci/server/views/index.erb +97 -0
- data/lib/octopusci/server/views/layout.erb +173 -0
- data/lib/octopusci/server.rb +67 -0
- data/lib/octopusci/stage_locker.rb +35 -0
- data/lib/octopusci/version.rb +3 -0
- data/lib/octopusci/worker_launcher.rb +18 -0
- data/lib/octopusci.rb +17 -0
- data/spec/lib/octopusci/notifier_spec.rb +27 -0
- data/spec/lib/octopusci/server_spec.rb +30 -0
- data/spec/lib/octopusci_spec.rb +4 -0
- data/spec/spec_helper.rb +11 -0
- metadata +247 -0
data/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
TODO: add license
|
data/README
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
The purpose of this project is provide a simple CI server that will work with
|
2
|
+
GitHub Post-Receive hook. It is also specifically designed to handle multiple
|
3
|
+
build/job queues for each branch.
|
4
|
+
|
5
|
+
This would basically allow you to every time code is pushed to the central
|
6
|
+
repository enqueue a build/job for the specific branch. That way if you
|
7
|
+
have topic branches that are being pushed to the central repository along
|
8
|
+
side the mainline branch they will get queue properly as well.
|
9
|
+
|
10
|
+
My idea for implementation at this point is that if I can detect the branch
|
11
|
+
from the GitHub Post-Receive hook then I can identify branch. If I can
|
12
|
+
identify branch then I can maintain individual queues for each branch.
|
13
|
+
|
14
|
+
Then the execution would look at config values for predefined branches and
|
15
|
+
priorities for order of running them so that the mainline branch running its
|
16
|
+
jobs could take precedence over non specified topic branches.
|
17
|
+
|
18
|
+
Install redis and get it starting up appropriately
|
19
|
+
|
20
|
+
gem install octopusci
|
21
|
+
sudo octopusci-skel
|
22
|
+
octopusci-db-migrate
|
23
|
+
|
24
|
+
Then update the /etc/octopusci/config.yml appropriately.
|
25
|
+
|
26
|
+
Add any jobs you would like to the /etc/octopusci/jobs directory as rb files
|
27
|
+
and octopusci will load them appropriately when started.
|
28
|
+
|
29
|
+
Figure out what directory the gem is installed in by running the following
|
30
|
+
command and stripping off the lib/octopusci.rb at the end.
|
31
|
+
|
32
|
+
gem which octopusci
|
33
|
+
|
34
|
+
Once you have the path we can use that path to setup Passenger with Apache
|
35
|
+
or something else like nginx as well as setup the database. Note: You will
|
36
|
+
need to setup a database user and a database for octopusci. The settings for
|
37
|
+
these should be stored in /etc/octopusci/config.yml.
|
38
|
+
|
39
|
+
rake -f /path/of/octpusci/we/got/before/Rakefile db:migrate
|
40
|
+
|
41
|
+
<VirtualHost *:80>
|
42
|
+
ServerName octopusci.example.com
|
43
|
+
PassengerAppRoot /path/of/octpusci/we/got/before
|
44
|
+
DocumentRoot /path/of/octpusci/we/got/before/lib/octopusci/server/public
|
45
|
+
<Directory /path/of/octpusci/we/got/before/lib/octopusci/server/public>
|
46
|
+
Order allow,deny
|
47
|
+
Allow from all
|
48
|
+
AllowOverride all
|
49
|
+
Options -MultiViews
|
50
|
+
</Directory>
|
51
|
+
</VirtualHost>
|
52
|
+
|
53
|
+
The above will give us the web Octopusci web interface.
|
54
|
+
|
55
|
+
If you are developing you can simply start this up by running
|
56
|
+
rackup -p whatever_port while inside the octopusci directory where the
|
57
|
+
config.ru file exists.
|
58
|
+
|
59
|
+
I recommend you setup the second half of Octopusci (octopusci-tentacles) with
|
60
|
+
God or some other monitoring system. However, for development you can simply
|
61
|
+
run octopusci-tentacles directoly as follows:
|
62
|
+
|
63
|
+
otopusci-tentacles
|
data/bin/octopusci
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
# This hash will hold all of the options
|
9
|
+
# parsed from the command-line by
|
10
|
+
# OptionParser.
|
11
|
+
options = {}
|
12
|
+
|
13
|
+
optparse = OptionParser.new do|opts|
|
14
|
+
# Set a banner, displayed at the top
|
15
|
+
# of the help screen.
|
16
|
+
opts.banner = "Usage: optparse1.rb [options] file1 file2 ..."
|
17
|
+
|
18
|
+
# Define the options, and what they do
|
19
|
+
options[:verbose] = false
|
20
|
+
opts.on( '-v', '--verbose', 'Output more information' ) do
|
21
|
+
options[:verbose] = true
|
22
|
+
end
|
23
|
+
|
24
|
+
options[:quick] = false
|
25
|
+
opts.on( '-q', '--quick', 'Perform the task quickly' ) do
|
26
|
+
options[:quick] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
options[:logfile] = nil
|
30
|
+
opts.on( '-l', '--logfile FILE', 'Write log to FILE' ) do |file|
|
31
|
+
options[:logfile] = file
|
32
|
+
end
|
33
|
+
|
34
|
+
# This displays the help screen, all programs are
|
35
|
+
# assumed to have this option.
|
36
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parse the command-line. Remember there are two forms
|
43
|
+
# of the parse method. The 'parse' method simply parses
|
44
|
+
# ARGV, while the 'parse!' method parses ARGV and removes
|
45
|
+
# any options found there, as well as any parameters for
|
46
|
+
# the options. What's left is the list of files to resize.
|
47
|
+
optparse.parse!
|
48
|
+
|
49
|
+
puts "Being verbose" if options[:verbose]
|
50
|
+
puts "Being quick" if options[:quick]
|
51
|
+
puts "Logging to file #{options[:logfile]}" if options[:logfile]
|
52
|
+
|
53
|
+
ARGV.each do|f|
|
54
|
+
puts "Resizing image #{f}..."
|
55
|
+
sleep 0.5
|
56
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'octopusci'
|
7
|
+
|
8
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
9
|
+
ActiveRecord::Migration.verbose = true
|
10
|
+
ActiveRecord::Migrator.migrate(File.expand_path(File.dirname(__FILE__) + "/../db/migrate"))
|
data/bin/octopusci-skel
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
if Process.uid != 0
|
9
|
+
puts "Must run as root"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
JOBS_PATH = '/etc/octopusci/jobs'
|
14
|
+
CONFIG_PATH = '/etc/octopusci/config.yml'
|
15
|
+
|
16
|
+
FileUtils.mkdir_p(JOBS_PATH)
|
17
|
+
|
18
|
+
if !File.exists?(CONFIG_PATH)
|
19
|
+
File.open(CONFIG_PATH, 'w') do |f|
|
20
|
+
f << "general:
|
21
|
+
jobs_path: \"/etc/octopusci/jobs\"
|
22
|
+
|
23
|
+
smtp:
|
24
|
+
notification_from_email: somefrom@example.com
|
25
|
+
address: smtp.gmail.com
|
26
|
+
port: 587
|
27
|
+
authentication: plain
|
28
|
+
enable_starttls_auto: true
|
29
|
+
user_name: someuser@example.com
|
30
|
+
password: somepassword
|
31
|
+
raise_delivery_errors: true
|
32
|
+
|
33
|
+
db:
|
34
|
+
adapter: mysql
|
35
|
+
host: localhost
|
36
|
+
database: octopusci
|
37
|
+
username: someusername
|
38
|
+
password: somepassword
|
39
|
+
|
40
|
+
projects:
|
41
|
+
- { name: octopusci, owner: cyphactor, job_klass: SomeJobClass, repo_uri: 'git@github.com:cyphactor/octopusci.git', default_email: devs@example.com }
|
42
|
+
|
43
|
+
stages:
|
44
|
+
- test_b
|
45
|
+
"
|
46
|
+
end
|
47
|
+
puts "Created example #{CONFIG_PATH}, please modify appropriately"
|
48
|
+
else
|
49
|
+
puts "#{CONFIG_PATH} already exists, exiting to avoid modification."
|
50
|
+
puts "If you would like to generated the example config again please rename the existing #{CONFIG_PATH}."
|
51
|
+
end
|
52
|
+
|
data/bin/pusci-stage
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'optparse'
|
7
|
+
require 'octopusci'
|
8
|
+
|
9
|
+
# This hash will hold all of the options
|
10
|
+
# parsed from the command-line by
|
11
|
+
# OptionParser.
|
12
|
+
options = {}
|
13
|
+
|
14
|
+
optparse = OptionParser.new do|opts|
|
15
|
+
# Set a banner, displayed at the top
|
16
|
+
# of the help screen.
|
17
|
+
opts.banner = "Usage: pusci-stage [options] stage_name"
|
18
|
+
|
19
|
+
# Define the options, and what they do
|
20
|
+
options[:add] = false
|
21
|
+
opts.on('-a', '--add', 'Add a stage back into the pool') do
|
22
|
+
options[:add] = true
|
23
|
+
end
|
24
|
+
|
25
|
+
options[:rem] = false
|
26
|
+
opts.on( '-r', '--rem', 'Rem a stage from the pool' ) do
|
27
|
+
options[:rem] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
options[:list] = false
|
31
|
+
opts.on( '-l', '--list', 'List all stages' ) do
|
32
|
+
options[:list] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
options[:pool] = false
|
36
|
+
opts.on( '-p', '--pool', 'List all stages currently in the pool' ) do
|
37
|
+
options[:pool] = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# This displays the help screen, all programs are
|
41
|
+
# assumed to have this option.
|
42
|
+
opts.on( '-h', '--help', 'Display the help screen' ) do
|
43
|
+
puts opts
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Parse the command-line. Remember there are two forms
|
49
|
+
# of the parse method. The 'parse' method simply parses
|
50
|
+
# ARGV, while the 'parse!' method parses ARGV and removes
|
51
|
+
# any options found there, as well as any parameters for
|
52
|
+
# the options. What's left is the list of files to resize.
|
53
|
+
optparse.parse!
|
54
|
+
|
55
|
+
if options[:list] == true
|
56
|
+
Octopusci::StageLocker.stages.each do |s|
|
57
|
+
puts s
|
58
|
+
end
|
59
|
+
elsif options[:pool] == true
|
60
|
+
puts Octopusci::StageLocker.pool
|
61
|
+
elsif options[:add] == true
|
62
|
+
Octopusci::StageLocker.push(ARGV[0])
|
63
|
+
elsif options[:rem] == true
|
64
|
+
Octopusci::StageLocker.rem(ARGV[0])
|
65
|
+
end
|
66
|
+
|
67
|
+
exit
|
data/config.ru
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Init < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :jobs do |t|
|
4
|
+
t.string :ref
|
5
|
+
t.string :compare
|
6
|
+
t.string :repo_name
|
7
|
+
t.string :repo_owner_name
|
8
|
+
t.string :repo_owner_email
|
9
|
+
t.timestamp :repo_pushed_at
|
10
|
+
t.timestamp :repo_created_at
|
11
|
+
t.text :repo_desc
|
12
|
+
t.string :repo_url
|
13
|
+
t.string :before_commit
|
14
|
+
t.boolean :forced
|
15
|
+
t.string :after_commit
|
16
|
+
t.boolean :running
|
17
|
+
t.boolean :successful
|
18
|
+
t.text :output
|
19
|
+
t.timestamps
|
20
|
+
t.timestamp :started_at
|
21
|
+
t.timestamp :ended_at
|
22
|
+
t.text :payload
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.down
|
27
|
+
drop_table :jobs
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'octopusci/notifier'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module Octopusci
|
5
|
+
class Config
|
6
|
+
class MissingConfigField < RuntimeError; end
|
7
|
+
class ConfigNotInitialized < RuntimeError; end
|
8
|
+
|
9
|
+
def initialize()
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# read the configuration values into the object from a YML file.
|
14
|
+
def load_yaml(yaml_file)
|
15
|
+
@options = YAML.load_file(yaml_file)
|
16
|
+
end
|
17
|
+
|
18
|
+
# allow options to be accessed as if this object is a Hash.
|
19
|
+
def [](key_name)
|
20
|
+
if @options.nil?
|
21
|
+
raise ConfigNotInitialized, "Can't access the '#{key_name}' field because the config hasn't been initialized."
|
22
|
+
end
|
23
|
+
if !@options.has_key?(key_name.to_s())
|
24
|
+
raise MissingConfigField, "'#{key_name}' is NOT defined as a config field."
|
25
|
+
end
|
26
|
+
return @options[key_name.to_s()]
|
27
|
+
end
|
28
|
+
|
29
|
+
# allow options to be set as if this object is a Hash.
|
30
|
+
def []=(key_name, value)
|
31
|
+
@options[key_name.to_s()] = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_key?(key_name)
|
35
|
+
return @options.has_key?(key_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
# allow options to be read and set using method calls. This capability is primarily for
|
39
|
+
# allowing the configuration to be defined through a block passed to the configure() function
|
40
|
+
# from an initializer or similar file.
|
41
|
+
def method_missing(key_name, *args)
|
42
|
+
key_name_str = key_name.to_s()
|
43
|
+
if key_name_str =~ /=$/ then
|
44
|
+
self[key_name_str.chop()] = args[0]
|
45
|
+
else
|
46
|
+
return self[key_name_str]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# On evaluation of the module it defines a new singleton of Config.
|
52
|
+
if (!defined?(CONFIG))
|
53
|
+
CONFIG = Config.new()
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.configure(yaml_file = nil, &block)
|
57
|
+
CONFIG.load_yaml(yaml_file) if !yaml_file.nil?
|
58
|
+
yield CONFIG if block
|
59
|
+
|
60
|
+
Notifier.default :from => Octopusci::CONFIG['smtp']['notification_from_email']
|
61
|
+
Notifier.delivery_method = :smtp
|
62
|
+
Notifier.smtp_settings = {
|
63
|
+
:address => Octopusci::CONFIG['smtp']['address'],
|
64
|
+
:port => Octopusci::CONFIG['smtp']['port'].to_s,
|
65
|
+
:authentication => Octopusci::CONFIG['smtp']['authentication'],
|
66
|
+
:enable_starttls_auto => Octopusci::CONFIG['smtp']['enable_starttls_auto'],
|
67
|
+
:user_name => Octopusci::CONFIG['smtp']['user_name'],
|
68
|
+
:password => Octopusci::CONFIG['smtp']['password'],
|
69
|
+
:raise_delivery_errors => Octopusci::CONFIG['smtp']['raise_delivery_errors']
|
70
|
+
}
|
71
|
+
Notifier.logger = Logger.new(STDOUT)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Load the actual config file
|
76
|
+
Octopusci.configure("/etc/octopusci/config.yml")
|
77
|
+
|
78
|
+
if Octopusci::CONFIG['stages'] == nil
|
79
|
+
raise "You have defined stages as an option but have no items in it."
|
80
|
+
end
|
81
|
+
|
82
|
+
ActiveRecord::Base.establish_connection(
|
83
|
+
:adapter => Octopusci::CONFIG['db']['adapter'],
|
84
|
+
:host => Octopusci::CONFIG['db']['host'],
|
85
|
+
:database => Octopusci::CONFIG['db']['database'],
|
86
|
+
:username => Octopusci::CONFIG['db']['username'],
|
87
|
+
:password => Octopusci::CONFIG['db']['password']
|
88
|
+
)
|
89
|
+
|
90
|
+
Dir.open(Octopusci::CONFIG['general']['jobs_path']) do |d|
|
91
|
+
job_file_names = d.entries.reject { |e| e == '..' || e == '.' }
|
92
|
+
job_file_names.each do |f_name|
|
93
|
+
require Octopusci::CONFIG['general']['jobs_path'] + "/#{f_name}"
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Octopusci
|
2
|
+
module Helpers
|
3
|
+
# Take the github payload hash and translate it to the Job model's attrs
|
4
|
+
# so that we can easily use the github payload hash to update_attributes
|
5
|
+
# on the Job mode.l
|
6
|
+
def self.gh_payload_to_job_attrs(gh_pl)
|
7
|
+
attrs = {}
|
8
|
+
|
9
|
+
# ref
|
10
|
+
attrs[:ref] = gh_pl["ref"]
|
11
|
+
# compare
|
12
|
+
attrs[:compare] = gh_pl["compare"]
|
13
|
+
# repo_name
|
14
|
+
attrs[:repo_name] = gh_pl["repository"]["name"]
|
15
|
+
# repo_owner_name
|
16
|
+
attrs[:repo_owner_name] = gh_pl["repository"]["owner"]["name"]
|
17
|
+
# repo_owner_email
|
18
|
+
attrs[:repo_owner_email] = gh_pl["repository"]["owner"]["email"]
|
19
|
+
# repo_pushed_at
|
20
|
+
attrs[:repo_pushed_at] = gh_pl["repository"]["pushed_at"]
|
21
|
+
# repo_created_at
|
22
|
+
attrs[:repo_created_at] = gh_pl["repository"]["created_at"]
|
23
|
+
# repo_desc
|
24
|
+
attrs[:repo_desc] = gh_pl["repository"]["description"]
|
25
|
+
# repo_url
|
26
|
+
attrs[:repo_url] = gh_pl["repository"]["url"]
|
27
|
+
# before_commit
|
28
|
+
attrs[:before_commit] = gh_pl["before"]
|
29
|
+
# forced
|
30
|
+
attrs[:forced] = gh_pl["forced"]
|
31
|
+
# after_commit
|
32
|
+
attrs[:after_commit] = gh_pl["after"]
|
33
|
+
|
34
|
+
attrs[:payload] = gh_pl
|
35
|
+
|
36
|
+
return attrs
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the information specified in the config about this project. If
|
40
|
+
# project info can't be found for the given project_name and project_owner
|
41
|
+
# this method returns nil. Otherwise, this project returns a hash of the
|
42
|
+
# project info that it found in the config.
|
43
|
+
def self.get_project_info(project_name, project_owner)
|
44
|
+
Octopusci::CONFIG["projects"].each do |proj|
|
45
|
+
if (proj['name'] == project_name) && (proj['owner'] == project_owner)
|
46
|
+
return proj
|
47
|
+
end
|
48
|
+
end
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.decode(str)
|
53
|
+
::MultiJson.decode(str)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.encode(str)
|
57
|
+
::MultiJson.encode(str)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Octopusci
|
2
|
+
class Job
|
3
|
+
def self.run(github_payload, stage, job_id, job_conf)
|
4
|
+
raise PureVirtualMethod, "The self.commit_run method needs to be defined on your Octopusci::Job."
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.perform(project_name, branch_name, job_id, job_conf)
|
8
|
+
if Octopusci::CONFIG.has_key?('stages')
|
9
|
+
# Get the next available stage from redis which locks it by removing it
|
10
|
+
# from the list of available
|
11
|
+
stage = Octopusci::StageLocker.pop
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
# Using redis to get the associated github_payload
|
16
|
+
github_payload = Octopusci::Queue.github_payload(project_name, branch_name)
|
17
|
+
|
18
|
+
job = ::Job.where("jobs.repo_name = ? && jobs.ref = ?", github_payload['repository']['name'], github_payload['ref']).order('jobs.created_at DESC').first
|
19
|
+
if job
|
20
|
+
job.started_at = Time.new
|
21
|
+
job.running = true
|
22
|
+
job.save
|
23
|
+
end
|
24
|
+
|
25
|
+
# Run the commit run and report about status and output
|
26
|
+
Bundler.with_clean_env {
|
27
|
+
self.run(github_payload, stage, job_id, job_conf)
|
28
|
+
}
|
29
|
+
|
30
|
+
if job
|
31
|
+
job.ended_at = Time.new
|
32
|
+
job.running = false
|
33
|
+
job.save
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
if Octopusci::CONFIG.has_key?('stages')
|
37
|
+
# Unlock the stage by adding it back to the list of available stages
|
38
|
+
Octopusci::StageLocker.push(stage)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>Octopusci Build (<%= @status_str %>) - <%= @job.repo_name %> / <%= @job.ref.gsub(/refs\/heads\//, '') %></h1>
|
8
|
+
<h2>Status: <%= @status_str %></h2>
|
9
|
+
<h2>Command Output:</h2>
|
10
|
+
<pre>
|
11
|
+
<code>
|
12
|
+
<%= @cmd_output %>
|
13
|
+
</code>
|
14
|
+
</pre>
|
15
|
+
</body>
|
16
|
+
</html>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'action_mailer'
|
2
|
+
|
3
|
+
# Set the ActionMailer view_path to lib where this library is so that when
|
4
|
+
# it searches for class_name/method for the templates it can find them when
|
5
|
+
# we don't use the standard Rails action mailer view locations.
|
6
|
+
ActionMailer::Base.view_paths = File.dirname(__FILE__) + '/../'
|
7
|
+
|
8
|
+
module Octopusci
|
9
|
+
class Notifier < ActionMailer::Base
|
10
|
+
def job_complete(recipient, cmd_output, cmd_status, github_payload, job_id)
|
11
|
+
@job = ::Job.find(job_id)
|
12
|
+
@job.output = cmd_output
|
13
|
+
@job.running = false
|
14
|
+
if cmd_status == 0
|
15
|
+
@job.successful = true
|
16
|
+
else
|
17
|
+
@job.successful = false
|
18
|
+
end
|
19
|
+
@job.save
|
20
|
+
|
21
|
+
if recipient
|
22
|
+
@cmd_output = cmd_output
|
23
|
+
@cmd_status = cmd_status
|
24
|
+
@github_payload = github_payload
|
25
|
+
if @cmd_status == 0
|
26
|
+
@status_str = 'success'
|
27
|
+
else
|
28
|
+
@status_str = 'failed'
|
29
|
+
end
|
30
|
+
mail(:to => recipient, :subject => "Octopusci Build (#{@status_str}) - #{@job.repo_name} / #{@job.ref.gsub(/refs\/heads\//, '')}") do |format|
|
31
|
+
format.text
|
32
|
+
format.html
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'resque'
|
2
|
+
|
3
|
+
module Octopusci
|
4
|
+
module Queue
|
5
|
+
def self.enqueue(job_klass, proj_name, branch_name, github_payload, job_conf)
|
6
|
+
resque_opts = { "class" => job_klass, "args" => [proj_name, branch_name] }
|
7
|
+
gh_pl_key = github_payload_key(proj_name, branch_name)
|
8
|
+
|
9
|
+
if lismember('commit', resque_opts)
|
10
|
+
Resque.redis.set(gh_pl_key, Resque::encode(github_payload))
|
11
|
+
# Get the most recent job for this project and update it with the data
|
12
|
+
job = ::Job.where("jobs.repo_name = ? && jobs.ref = ?", proj_name, '/refs/heads/' + branch_name).order('jobs.created_at DESC').first
|
13
|
+
if job
|
14
|
+
job.update_attributes(Octopusci::Helpers.gh_payload_to_job_attrs(github_payload))
|
15
|
+
end
|
16
|
+
else
|
17
|
+
# Create a new job for this project with the appropriate data
|
18
|
+
job = ::Job.create(Octopusci::Helpers.gh_payload_to_job_attrs(github_payload).merge({ :running => false }))
|
19
|
+
resque_opts["args"] << job.id
|
20
|
+
resque_opts["args"] << job_conf
|
21
|
+
Resque.redis.set(gh_pl_key, Resque::encode(github_payload))
|
22
|
+
Resque.push('commit', resque_opts)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.lismember(queue, item)
|
27
|
+
size = Resque.size(queue)
|
28
|
+
[Resque.peek(queue, 0, size)].flatten.any? { |v|
|
29
|
+
v == item
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.github_payload(project_name, branch_name)
|
34
|
+
Resque::decode(Resque.redis.get(github_payload_key(project_name, branch_name)))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.github_payload_key(proj_name, branch_name)
|
38
|
+
"octpusci:github_payload:#{proj_name}:#{branch_name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
Binary file
|
Binary file
|
Binary file
|