octopusci 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +132 -0
- data/bin/octopusci-reset-redis +26 -0
- data/bin/octopusci-skel +2 -7
- data/bin/octopusci-tentacles +2 -2
- data/config.ru +1 -1
- data/lib/octopusci.rb +3 -7
- data/lib/octopusci/config.rb +63 -49
- data/lib/octopusci/errors.rb +2 -0
- data/lib/octopusci/helpers.rb +16 -15
- data/lib/octopusci/io.rb +70 -0
- data/lib/octopusci/job.rb +145 -34
- data/lib/octopusci/job_store.rb +67 -0
- data/lib/octopusci/notifier.rb +7 -17
- data/lib/octopusci/notifier/job_complete.html.erb +76 -3
- data/lib/octopusci/queue.rb +14 -10
- data/lib/octopusci/server.rb +17 -20
- data/lib/octopusci/server/views/index.erb +3 -4
- data/lib/octopusci/server/views/job.erb +3 -3
- data/lib/octopusci/server/views/job_summary.erb +18 -18
- data/lib/octopusci/server/views/layout.erb +6 -5
- data/lib/octopusci/stage_locker.rb +11 -7
- data/lib/octopusci/version.rb +1 -1
- data/lib/octopusci/worker_launcher.rb +1 -1
- data/spec/lib/octopusci/config_spec.rb +195 -0
- data/spec/lib/octopusci/io_spec.rb +64 -0
- data/spec/lib/octopusci/job_spec.rb +122 -0
- data/spec/lib/octopusci/job_store_spec.rb +155 -0
- data/spec/lib/octopusci/notifier_spec.rb +0 -15
- data/spec/lib/octopusci/queue_spec.rb +122 -0
- data/spec/lib/octopusci/server_spec.rb +92 -1
- data/spec/lib/octopusci/stage_locker_spec.rb +94 -0
- data/spec/spec_helper.rb +8 -0
- metadata +39 -58
- data/README +0 -63
- data/bin/octopusci-db-migrate +0 -10
- data/db/migrate/0001_init.rb +0 -29
- data/db/migrate/0002_add_status_job.rb +0 -19
- data/lib/octopusci/notifier/job_complete.text.erb +0 -5
- data/lib/octopusci/schema.rb +0 -140
data/README.markdown
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
Octopusci
|
2
|
+
=========
|
3
|
+
|
4
|
+
Octopusci is fresh new take on a continuous integration server centralized
|
5
|
+
around the concept of getting the same great CI benefits when using a
|
6
|
+
multi-branch workflow.
|
7
|
+
|
8
|
+
How's it Different?
|
9
|
+
-------------------
|
10
|
+
|
11
|
+
The impetus that brought Octopusci into being was simply the lack of CI servers
|
12
|
+
that cleanly supported a software development workflow based on multiple
|
13
|
+
branches. Secondarily, it was the excessive amount of effort necessary to get a
|
14
|
+
basic CI server up and running.
|
15
|
+
|
16
|
+
Octopsuci fills this gap by providing intelligent multi-branch queueing
|
17
|
+
and multi-server job distribution. Beyond that it provides a
|
18
|
+
solid continous integration server that is trivial to get setup and running. A number
|
19
|
+
of the concepts used in Octopusci are pulled from
|
20
|
+
[Continuous Delivery](http://continuousdelivery.com/),
|
21
|
+
[Continuous Integration](http://martinfowler.com/articles/continuousIntegration.html)
|
22
|
+
as well as Scott Chacon's post on the
|
23
|
+
[GitHub Flow](http://scottchacon.com/2011/08/31/github-flow.html).
|
24
|
+
|
25
|
+
The following is a listing of a number of some of its more significant features.
|
26
|
+
|
27
|
+
### Dynamic Multi-Branch Triggering
|
28
|
+
|
29
|
+
Octopusci detects branch creation/modification and dynamically generates a build for
|
30
|
+
that branch based on the project the pushed branch belongs to. Most existing CI servers
|
31
|
+
that I have used force you to manually define jobs for each branch you would like it to
|
32
|
+
manage.
|
33
|
+
|
34
|
+
### Multi-Server Job Distribution
|
35
|
+
|
36
|
+
Octopusci allows you to configure it to run "remote jobs" on numerous servers and it
|
37
|
+
keeps track of which servers are currently busy as well as handing new jobs to the
|
38
|
+
correct servers as they become available. This is extremely valuable if you are
|
39
|
+
interested in running automated acceptance tests that take a long time to run such
|
40
|
+
as Selenium/Cucumber & Capybara Tests.
|
41
|
+
|
42
|
+
### Intelligent Multi-Branch Queueing
|
43
|
+
|
44
|
+
Octopusci intelligently manages its job queue by by simply updating any pending jobs with
|
45
|
+
the newly pushed branch data. This means that at any given point in time there is only
|
46
|
+
ever one pending job for each branch. When, a code push comes into Octopusci it
|
47
|
+
first looks to see if there is already a pending job for the branch that was pushed. If
|
48
|
+
there is, it simply updates the jobs associated branch data. If there is not already a
|
49
|
+
pending job then it queues a new job for that branch.
|
50
|
+
|
51
|
+
### GitHub Integration ###
|
52
|
+
|
53
|
+
Octopusci was designed specifically to integrate cleanly with GitHub's push notifications
|
54
|
+
system. At some point in the future Octopusci may support more than just GitHub but for
|
55
|
+
the time being GitHub is our primary focus.
|
56
|
+
|
57
|
+
Install Guide
|
58
|
+
-------------
|
59
|
+
|
60
|
+
### Install Dependencies ###
|
61
|
+
|
62
|
+
Octopusci has one major dependency at the moment, [Redis](http://redis.io/).
|
63
|
+
[Redis](http://redis.io/) needs to be installed and configured to startup appropriately
|
64
|
+
on the box you plan to run Octopusci on.
|
65
|
+
|
66
|
+
On Debian/Ubuntu machines this is to my knowledge as easy as `apt-get install redis-server`.
|
67
|
+
|
68
|
+
On Mac OS X machines this can easly be installed via [brew](http://mxcl.github.com/homebrew/)
|
69
|
+
using `brew install redis`. Follow the on screen instructions to configure it to auto
|
70
|
+
startup when you boot up as well as simply how to run the server manually.
|
71
|
+
|
72
|
+
### Gem & Init Skel ###
|
73
|
+
|
74
|
+
$ gem install octopusci
|
75
|
+
$ sudo octopusci-skel
|
76
|
+
|
77
|
+
The `octopusci-skel` command will make sure the `/etc/octopusci` path exists and its
|
78
|
+
underlying structure. It will also create a default example `/etc/ocotpusci/config.yml`
|
79
|
+
if one is not found.
|
80
|
+
|
81
|
+
### Update Example Config ###
|
82
|
+
|
83
|
+
Now that the `/etc/octopusci/config.yml` example config has been created for you it is
|
84
|
+
time to go check it out and update some of the values in it.
|
85
|
+
|
86
|
+
TODO: Fill this out with details on the config, required fields, optional fields, etc.
|
87
|
+
|
88
|
+
### Jobs ###
|
89
|
+
|
90
|
+
Add any jobs you would like to the `/etc/octopusci/jobs` directory as .rb files
|
91
|
+
and Octopusci will load them appropriately when started.
|
92
|
+
|
93
|
+
### Web Interface ###
|
94
|
+
|
95
|
+
Figure out what directory the gem is installed in by running the following
|
96
|
+
command and stripping off the `lib/octopusci.rb` at the end.
|
97
|
+
|
98
|
+
gem which octopusci
|
99
|
+
|
100
|
+
Once you have the path we can use that path to setup Passenger with Apache
|
101
|
+
or something else like nginx.
|
102
|
+
|
103
|
+
Apache virtual host example
|
104
|
+
|
105
|
+
<VirtualHost *:80>
|
106
|
+
ServerName octopusci.example.com
|
107
|
+
PassengerAppRoot /path/of/octpusci/we/got/before
|
108
|
+
DocumentRoot /path/of/octpusci/we/got/before/lib/octopusci/server/public
|
109
|
+
<Directory /path/of/octpusci/we/got/before/lib/octopusci/server/public>
|
110
|
+
Order allow,deny
|
111
|
+
Allow from all
|
112
|
+
AllowOverride all
|
113
|
+
Options -MultiViews
|
114
|
+
</Directory>
|
115
|
+
</VirtualHost>
|
116
|
+
|
117
|
+
The above will give us the web Octopusci web interface.
|
118
|
+
|
119
|
+
If you are developing you can simply start this up by running
|
120
|
+
`rackup -p whatever_port` while inside the octopusci directory where the
|
121
|
+
`config.ru` file exists.
|
122
|
+
|
123
|
+
I recommend you setup the second half of Octopusci (`octopusci-tentacles`) with
|
124
|
+
God or some other monitoring system. However, for development you can simply
|
125
|
+
run `octopusci-tentacles` directoly as follows:
|
126
|
+
|
127
|
+
otopusci-tentacles
|
128
|
+
|
129
|
+
Screenshots
|
130
|
+
-----------
|
131
|
+
|
132
|
+
![Octopusci - Dashboard](https://img.skitch.com/20111005-tfxgw59mec5msnfu3pd6is3btf.jpg)
|
@@ -0,0 +1,26 @@
|
|
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
|
+
num_jobs = Octopusci::JobStore.size
|
9
|
+
jobs = Octopusci::JobStore.list(0, num_jobs)
|
10
|
+
|
11
|
+
jobs.each do |j|
|
12
|
+
# delete the actual job record
|
13
|
+
Octopusci::JobStore.redis.del("octopusci:jobs:#{j['id']}")
|
14
|
+
|
15
|
+
# delete the repo_name, branch_name job references
|
16
|
+
Octopusci::JobStore.redis.del("octopusci:#{j['repo_name']}:#{j['branch_name']}:jobs")
|
17
|
+
|
18
|
+
# delete the github payload for the repo_name, branch_name
|
19
|
+
Octopusci::JobStore.redis.del("octopusci:github_payload:#{j['repo_name']}:#{j['branch_name']}")
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
Octopusci::JobStore.redis.del('octopusci:job_count')
|
24
|
+
Octopusci::JobStore.redis.del('octopusci:jobs')
|
25
|
+
Octopusci::JobStore.redis.del('octopusci:stagelocker')
|
26
|
+
Octopusci::JobStore.redis.del('queue:octopusci:commit')
|
data/bin/octopusci-skel
CHANGED
@@ -19,6 +19,8 @@ if !File.exists?(CONFIG_PATH)
|
|
19
19
|
File.open(CONFIG_PATH, 'w') do |f|
|
20
20
|
f << "general:
|
21
21
|
jobs_path: \"/etc/octopusci/jobs\"
|
22
|
+
base_url: \"http://localhost:9998\"
|
23
|
+
workspace_base_path: \"/Users/octopusci/.octopusci\"
|
22
24
|
|
23
25
|
http_basic:
|
24
26
|
username: admin
|
@@ -34,13 +36,6 @@ smtp:
|
|
34
36
|
password: somepassword
|
35
37
|
raise_delivery_errors: true
|
36
38
|
|
37
|
-
db:
|
38
|
-
adapter: mysql
|
39
|
-
host: localhost
|
40
|
-
database: octopusci
|
41
|
-
username: someusername
|
42
|
-
password: somepassword
|
43
|
-
|
44
39
|
projects:
|
45
40
|
- { name: octopusci, owner: cyphactor, job_klass: SomeJobClass, repo_uri: 'git@github.com:cyphactor/octopusci.git', default_email: devs@example.com }
|
46
41
|
|
data/bin/octopusci-tentacles
CHANGED
@@ -26,8 +26,8 @@ require 'octopusci'
|
|
26
26
|
# valid initial state. But, also in the case that octopusci-tentacles was
|
27
27
|
# killed it nukes the potentially screwed up state the redis data is in and
|
28
28
|
# initializes it the proper state based on the config.
|
29
|
-
if Octopusci::
|
30
|
-
Octopusci::StageLocker.load(Octopusci::
|
29
|
+
if Octopusci::Config.has_key?('stages')
|
30
|
+
Octopusci::StageLocker.load(Octopusci::Config['stages'])
|
31
31
|
end
|
32
32
|
|
33
33
|
case ARGV[0]
|
data/config.ru
CHANGED
@@ -4,7 +4,7 @@ require 'octopusci/server'
|
|
4
4
|
|
5
5
|
# Set the AUTH env variable to your basic auth password to protect Resque.
|
6
6
|
Resque::Server.use Rack::Auth::Basic do |username, password|
|
7
|
-
(username == Octopusci::
|
7
|
+
(username == Octopusci::Config['http_basic']['username']) && (password == Octopusci::Config['http_basic']['password'])
|
8
8
|
end
|
9
9
|
|
10
10
|
run Rack::URLMap.new("/" => Octopusci::Server.new, "/resque" => Resque::Server.new)
|
data/lib/octopusci.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
require 'time-ago-in-words'
|
2
2
|
|
3
3
|
require 'octopusci/version'
|
4
|
+
require 'octopusci/errors'
|
4
5
|
require 'octopusci/helpers'
|
5
|
-
require 'octopusci/
|
6
|
+
require 'octopusci/io'
|
7
|
+
require 'octopusci/job_store'
|
6
8
|
require 'octopusci/notifier'
|
7
9
|
require 'octopusci/queue'
|
8
10
|
require 'octopusci/stage_locker'
|
9
11
|
require 'octopusci/job'
|
10
12
|
require 'octopusci/config'
|
11
13
|
require 'octopusci/worker_launcher'
|
12
|
-
|
13
|
-
module Octopusci
|
14
|
-
def self.greet
|
15
|
-
return "Hello RSpec!"
|
16
|
-
end
|
17
|
-
end
|
data/lib/octopusci/config.rb
CHANGED
@@ -1,25 +1,34 @@
|
|
1
1
|
require 'octopusci/notifier'
|
2
|
-
require 'active_record'
|
3
2
|
|
4
3
|
module Octopusci
|
5
|
-
class
|
4
|
+
class ConfigStore
|
6
5
|
class MissingConfigField < RuntimeError; end
|
7
|
-
class ConfigNotInitialized < RuntimeError; end
|
8
6
|
|
9
|
-
def initialize
|
7
|
+
def initialize
|
8
|
+
reset()
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset
|
10
12
|
@options = {}
|
11
13
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
def options
|
16
|
+
@options
|
17
|
+
end
|
18
|
+
|
19
|
+
def load(yaml_file = nil, &block)
|
20
|
+
load_yaml(yaml_file) if !yaml_file.nil?
|
21
|
+
yield self if block
|
22
|
+
after_load()
|
23
|
+
end
|
24
|
+
|
25
|
+
def reload(yaml_file = nil, &block)
|
26
|
+
reset()
|
27
|
+
load(yaml_file, &block)
|
16
28
|
end
|
17
29
|
|
18
30
|
# allow options to be accessed as if this object is a Hash.
|
19
31
|
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
32
|
if !@options.has_key?(key_name.to_s())
|
24
33
|
raise MissingConfigField, "'#{key_name}' is NOT defined as a config field."
|
25
34
|
end
|
@@ -46,50 +55,55 @@ module Octopusci
|
|
46
55
|
return self[key_name_str]
|
47
56
|
end
|
48
57
|
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
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
def after_load(&block)
|
60
|
+
if block
|
61
|
+
@after_load = block
|
62
|
+
elsif @after_load
|
63
|
+
@after_load.call
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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)
|
69
|
+
# read the configuration values into the object from a YML file.
|
70
|
+
def load_yaml(yaml_file)
|
71
|
+
@options.merge!(YAML.load_file(yaml_file))
|
72
|
+
end
|
72
73
|
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Load the actual config file
|
76
|
-
Octopusci.configure("/etc/octopusci/config.yml")
|
77
74
|
|
78
|
-
|
79
|
-
|
75
|
+
# On evaluation of the module it defines a new singleton of Config.
|
76
|
+
if (!defined?(::Octopusci::Config))
|
77
|
+
::Octopusci::Config = ConfigStore.new()
|
78
|
+
end
|
80
79
|
end
|
81
80
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
81
|
+
# Setup the config after load callback
|
82
|
+
Octopusci::Config.after_load do
|
83
|
+
if Octopusci::Config['stages'] == nil
|
84
|
+
raise "You have defined stages as an option but have no items in it."
|
85
|
+
end
|
86
|
+
|
87
|
+
Octopusci::Notifier.default :from => Octopusci::Config['smtp']['notification_from_email']
|
88
|
+
Octopusci::Notifier.delivery_method = :smtp
|
89
|
+
Octopusci::Notifier.smtp_settings = {
|
90
|
+
:address => Octopusci::Config['smtp']['address'],
|
91
|
+
:port => Octopusci::Config['smtp']['port'].to_s,
|
92
|
+
:authentication => Octopusci::Config['smtp']['authentication'],
|
93
|
+
:enable_starttls_auto => Octopusci::Config['smtp']['enable_starttls_auto'],
|
94
|
+
:user_name => Octopusci::Config['smtp']['user_name'],
|
95
|
+
:password => Octopusci::Config['smtp']['password'],
|
96
|
+
:raise_delivery_errors => Octopusci::Config['smtp']['raise_delivery_errors']
|
97
|
+
}
|
98
|
+
Octopusci::Notifier.logger = Logger.new(STDOUT)
|
89
99
|
|
90
|
-
Dir.open(Octopusci::
|
91
|
-
|
92
|
-
|
93
|
-
|
100
|
+
Dir.open(Octopusci::Config['general']['jobs_path']) do |d|
|
101
|
+
job_file_names = d.entries.reject { |e| e == '..' || e == '.' }
|
102
|
+
job_file_names.each do |f_name|
|
103
|
+
load Octopusci::Config['general']['jobs_path'] + "/#{f_name}"
|
104
|
+
end
|
94
105
|
end
|
95
106
|
end
|
107
|
+
|
108
|
+
# Load the actual config file
|
109
|
+
Octopusci::Config.load("/etc/octopusci/config.yml")
|
data/lib/octopusci/errors.rb
CHANGED
data/lib/octopusci/helpers.rb
CHANGED
@@ -7,31 +7,32 @@ module Octopusci
|
|
7
7
|
attrs = {}
|
8
8
|
|
9
9
|
# ref
|
10
|
-
attrs[
|
10
|
+
attrs['ref'] = gh_pl["ref"]
|
11
|
+
attrs['branch_name'] = gh_pl["ref"].split('/').last
|
11
12
|
# compare
|
12
|
-
attrs[
|
13
|
+
attrs['compare'] = gh_pl["compare"]
|
13
14
|
# repo_name
|
14
|
-
attrs[
|
15
|
+
attrs['repo_name'] = gh_pl["repository"]["name"]
|
15
16
|
# repo_owner_name
|
16
|
-
attrs[
|
17
|
+
attrs['repo_owner_name'] = gh_pl["repository"]["owner"]["name"]
|
17
18
|
# repo_owner_email
|
18
|
-
attrs[
|
19
|
+
attrs['repo_owner_email'] = gh_pl["repository"]["owner"]["email"]
|
19
20
|
# repo_pushed_at
|
20
|
-
attrs[
|
21
|
+
attrs['repo_pushed_at'] = gh_pl["repository"]["pushed_at"]
|
21
22
|
# repo_created_at
|
22
|
-
attrs[
|
23
|
+
attrs['repo_created_at'] = gh_pl["repository"]["created_at"]
|
23
24
|
# repo_desc
|
24
|
-
attrs[
|
25
|
+
attrs['repo_desc'] = gh_pl["repository"]["description"]
|
25
26
|
# repo_url
|
26
|
-
attrs[
|
27
|
+
attrs['repo_url'] = gh_pl["repository"]["url"]
|
27
28
|
# before_commit
|
28
|
-
attrs[
|
29
|
+
attrs['before_commit'] = gh_pl["before"]
|
29
30
|
# forced
|
30
|
-
attrs[
|
31
|
+
attrs['forced'] = gh_pl["forced"]
|
31
32
|
# after_commit
|
32
|
-
attrs[
|
33
|
+
attrs['after_commit'] = gh_pl["after"]
|
33
34
|
|
34
|
-
attrs[
|
35
|
+
attrs['payload'] = gh_pl
|
35
36
|
|
36
37
|
return attrs
|
37
38
|
end
|
@@ -41,7 +42,7 @@ module Octopusci
|
|
41
42
|
# this method returns nil. Otherwise, this project returns a hash of the
|
42
43
|
# project info that it found in the config.
|
43
44
|
def self.get_project_info(project_name, project_owner)
|
44
|
-
Octopusci::
|
45
|
+
Octopusci::Config["projects"].each do |proj|
|
45
46
|
if (proj['name'] == project_name) && (proj['owner'] == project_owner)
|
46
47
|
return proj
|
47
48
|
end
|
@@ -58,7 +59,7 @@ module Octopusci
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def self.workspace_path(stage)
|
61
|
-
return Octopusci::
|
62
|
+
return Octopusci::Config['general']['workspace_base_path'] + "/#{stage}"
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
data/lib/octopusci/io.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Octopusci
|
2
|
+
class IO
|
3
|
+
attr_accessor :job
|
4
|
+
|
5
|
+
def initialize(job)
|
6
|
+
@job = job
|
7
|
+
end
|
8
|
+
|
9
|
+
def read_all_out
|
10
|
+
if File.exists?(abs_output_file_path)
|
11
|
+
cont = ""
|
12
|
+
f = File.open(abs_output_file_path, 'r')
|
13
|
+
cont = f.read()
|
14
|
+
f.close
|
15
|
+
return cont
|
16
|
+
else
|
17
|
+
return ""
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_all_log
|
22
|
+
if File.exists?(abs_log_file_path)
|
23
|
+
return File.open(abs_log_file_path, 'r').read()
|
24
|
+
else
|
25
|
+
return ""
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_out(msg="", &block)
|
30
|
+
write_raw_output(false, msg, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_log(msg="", &block)
|
34
|
+
write_raw_output(true, msg, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_raw_output(silently=false, msg="")
|
38
|
+
# Make sure that the directory structure is in place for the job output.
|
39
|
+
if !File.directory?(abs_output_base_path)
|
40
|
+
FileUtils.mkdir_p(abs_output_base_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Run the command and output the output to the job file
|
44
|
+
out_f = if silently
|
45
|
+
File.open(abs_log_file_path, 'a')
|
46
|
+
else
|
47
|
+
File.open(abs_output_file_path, 'a')
|
48
|
+
end
|
49
|
+
|
50
|
+
yield(out_f) if block_given?
|
51
|
+
out_f << msg unless msg.nil? || msg.empty?
|
52
|
+
|
53
|
+
out_f.close
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def abs_output_file_path
|
59
|
+
return "#{abs_output_base_path}/output.txt"
|
60
|
+
end
|
61
|
+
|
62
|
+
def abs_log_file_path
|
63
|
+
return "#{abs_output_base_path}/silent_output.txt"
|
64
|
+
end
|
65
|
+
|
66
|
+
def abs_output_base_path
|
67
|
+
return "#{Octopusci::Config['general']['workspace_base_path']}/jobs/#{@job['id']}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|