octopusci 0.2.3 → 0.3.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.
- 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
|
+

|
@@ -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
|