altria 0.2.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 +7 -0
- data/Gemfile +41 -0
- data/LICENSE.txt +22 -0
- data/Procfile +4 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/altria.gemspec +50 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/javascripts/altria.js.coffee +1 -0
- data/app/assets/javascripts/altria/build_view.js.coffee +13 -0
- data/app/assets/javascripts/altria/job_view.js.coffee +10 -0
- data/app/assets/javascripts/altria/server_event.js.coffee +8 -0
- data/app/assets/javascripts/application.js +20 -0
- data/app/assets/javascripts/events.js.coffee +13 -0
- data/app/assets/stylesheets/application.css +14 -0
- data/app/assets/stylesheets/builds.scss +35 -0
- data/app/assets/stylesheets/common.scss +69 -0
- data/app/assets/stylesheets/jobs.scss +129 -0
- data/app/assets/stylesheets/layout.scss +19 -0
- data/app/assets/stylesheets/mixin.scss +26 -0
- data/app/controllers/application_controller.rb +24 -0
- data/app/controllers/builds_controller.rb +48 -0
- data/app/controllers/events_controller.rb +27 -0
- data/app/controllers/jobs_controller.rb +50 -0
- data/app/helpers/application_helper.rb +5 -0
- data/app/models/build.rb +72 -0
- data/app/models/job.rb +141 -0
- data/app/views/builds/show.html.slim +36 -0
- data/app/views/jobs/_form.html.slim +22 -0
- data/app/views/jobs/edit.html.slim +9 -0
- data/app/views/jobs/index.html.slim +19 -0
- data/app/views/jobs/new.html.slim +7 -0
- data/app/views/jobs/show.html.slim +28 -0
- data/app/views/layouts/application.html.slim +11 -0
- data/app/workers/build_worker.rb +13 -0
- data/app/workers/job_enqueue_worker.rb +13 -0
- data/bin/altria +6 -0
- data/config.ru +4 -0
- data/config/application.rb +52 -0
- data/config/boot.rb +6 -0
- data/config/database.yml +29 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +23 -0
- data/config/environments/production.rb +71 -0
- data/config/environments/test.rb +33 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/inflections.rb +15 -0
- data/config/initializers/mime_types.rb +5 -0
- data/config/initializers/redis.rb +1 -0
- data/config/initializers/secret_token.rb +8 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20130531143239_create_jobs.rb +10 -0
- data/db/migrate/20130531155155_create_builds.rb +14 -0
- data/db/migrate/20130608153135_change_job_column_name_from_config_to_properties.rb +9 -0
- data/db/migrate/20130608153655_add_column_properties_to_builds.rb +5 -0
- data/db/schema.rb +36 -0
- data/db/seeds.rb +7 -0
- data/lib/altria.rb +13 -0
- data/lib/altria/command.rb +61 -0
- data/lib/altria/configuration.rb +9 -0
- data/lib/altria/executer.rb +37 -0
- data/lib/altria/proprietary.rb +56 -0
- data/lib/altria/responder.rb +12 -0
- data/lib/altria/scheduler.rb +55 -0
- data/lib/altria/version.rb +3 -0
- data/lib/altria/workspace.rb +26 -0
- data/lib/tasks/resque.rake +3 -0
- data/public/404.html +26 -0
- data/public/422.html +26 -0
- data/public/500.html +25 -0
- data/public/favicon.ico +0 -0
- data/public/robots.txt +5 -0
- data/script/clock.rb +5 -0
- data/script/rails +6 -0
- data/spec/altria/command_spec.rb +53 -0
- data/spec/factories/build.rb +7 -0
- data/spec/factories/job.rb +7 -0
- data/spec/models/build_spec.rb +35 -0
- data/spec/models/job_spec.rb +101 -0
- data/spec/requests/builds_spec.rb +81 -0
- data/spec/requests/jobs_spec.rb +88 -0
- data/spec/spec_helper.rb +48 -0
- metadata +417 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
@mixin box {
|
2
|
+
border-radius: .4em;
|
3
|
+
border: solid 2px #999;
|
4
|
+
padding: .2em .6em;
|
5
|
+
margin-bottom: .5em;
|
6
|
+
}
|
7
|
+
|
8
|
+
@mixin stripe {
|
9
|
+
background-size: 30px 30px;
|
10
|
+
background-color: #ddd;
|
11
|
+
background-image: linear-gradient(
|
12
|
+
135deg,
|
13
|
+
rgba(255, 255, 255, .30) 25%,
|
14
|
+
transparent 25%,
|
15
|
+
transparent 50%,
|
16
|
+
rgba(255, 255, 255, .30) 50%,
|
17
|
+
rgba(255, 255, 255, .30) 75%,
|
18
|
+
transparent 75%,
|
19
|
+
transparent
|
20
|
+
);
|
21
|
+
-webkit-animation: progress 3s linear infinite;
|
22
|
+
}
|
23
|
+
|
24
|
+
@-webkit-keyframes progress {
|
25
|
+
0% { background-position: 0 0; } 100% { background-position: 60px 0; }
|
26
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class ApplicationController < ActionController::Base
|
2
|
+
protect_from_forgery
|
3
|
+
|
4
|
+
respond_to :html, :json
|
5
|
+
|
6
|
+
self.responder = Altria::Responder
|
7
|
+
|
8
|
+
rescue_from WeakParameters::ValidationError do
|
9
|
+
head 400
|
10
|
+
end
|
11
|
+
|
12
|
+
# Cache to use `view_context.content_for` from controller.
|
13
|
+
# This hack is nice to hook view_context from a plugin.
|
14
|
+
#
|
15
|
+
# Examples
|
16
|
+
#
|
17
|
+
# JobsController.before_filter do
|
18
|
+
# view_context.content_for :jobs_show, "This is a footer."
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
def view_context
|
22
|
+
@view_context ||= super
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class BuildsController < ApplicationController
|
2
|
+
before_filter :require_job
|
3
|
+
before_filter :require_resources, only: :index
|
4
|
+
before_filter :require_resource, only: [:show, :update, :destroy]
|
5
|
+
|
6
|
+
validates :update do
|
7
|
+
integer :status
|
8
|
+
end
|
9
|
+
|
10
|
+
def index
|
11
|
+
respond_with @resources
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
respond_with @resource
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
resource = @job.enqueue
|
20
|
+
respond_with resource, location: [@job, resource]
|
21
|
+
end
|
22
|
+
|
23
|
+
def update
|
24
|
+
respond_with @resource.update_attributes(params.permit(:status))
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy
|
28
|
+
respond_with @resource.destroy
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def require_job
|
34
|
+
@job = Job.find(params[:job_id])
|
35
|
+
end
|
36
|
+
|
37
|
+
def scope
|
38
|
+
@job.builds
|
39
|
+
end
|
40
|
+
|
41
|
+
def require_resources
|
42
|
+
@resources = scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def require_resource
|
46
|
+
@resource = scope.find(params[:id])
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class EventsController < ApplicationController
|
2
|
+
include ActionController::Live
|
3
|
+
|
4
|
+
before_filter :require_event_stream
|
5
|
+
|
6
|
+
def index
|
7
|
+
redis = Redis.new
|
8
|
+
redis.psubscribe("build.*") do |on|
|
9
|
+
on.pmessage do |pattern, event, data|
|
10
|
+
response.stream.write("event: #{event}\n")
|
11
|
+
response.stream.write("data: #{data}\n\n")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue IOError
|
15
|
+
rescue Redis::CannotConnectError
|
16
|
+
head 503
|
17
|
+
ensure
|
18
|
+
redis.quit rescue nil
|
19
|
+
response.stream.close
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def require_event_stream
|
25
|
+
response.headers["Content-Type"] = "text/event-stream"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class JobsController < ApplicationController
|
2
|
+
before_filter :require_resources, only: :index
|
3
|
+
before_filter :require_resource, only: [:show, :edit, :update, :destroy]
|
4
|
+
|
5
|
+
validates :create do
|
6
|
+
string :name, required: true
|
7
|
+
end
|
8
|
+
|
9
|
+
validates :update do
|
10
|
+
string :name
|
11
|
+
end
|
12
|
+
|
13
|
+
def index
|
14
|
+
respond_with @resources
|
15
|
+
end
|
16
|
+
|
17
|
+
def show
|
18
|
+
respond_with @resource
|
19
|
+
end
|
20
|
+
|
21
|
+
def new
|
22
|
+
respond_with @resource = scope.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def create
|
26
|
+
respond_with @resource = scope.create_with_properties(params)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update
|
30
|
+
respond_with @resource.update_attributes_with_properties(params)
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy
|
34
|
+
respond_with @resource.destroy
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def scope
|
40
|
+
Job.all
|
41
|
+
end
|
42
|
+
|
43
|
+
def require_resources
|
44
|
+
@resources = scope
|
45
|
+
end
|
46
|
+
|
47
|
+
def require_resource
|
48
|
+
@resource = scope.find(params[:id])
|
49
|
+
end
|
50
|
+
end
|
data/app/models/build.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class Build < ActiveRecord::Base
|
2
|
+
include Altria::Proprietary
|
3
|
+
|
4
|
+
serialize :properties, Hash
|
5
|
+
|
6
|
+
validates :job_id, presence: true
|
7
|
+
|
8
|
+
belongs_to :job, touch: true
|
9
|
+
|
10
|
+
scope :recent, -> { order("created_at DESC") }
|
11
|
+
|
12
|
+
scope :finished, -> { where("finished_at IS NOT NULL") }
|
13
|
+
|
14
|
+
scope :unfinished, -> { where("finished_at IS NULL") }
|
15
|
+
|
16
|
+
scope :started, -> { where("started_at IS NOT NULL") }
|
17
|
+
|
18
|
+
scope :running, -> { started.unfinished }
|
19
|
+
|
20
|
+
paginates_per 10
|
21
|
+
|
22
|
+
def self.latest
|
23
|
+
recent.first
|
24
|
+
end
|
25
|
+
|
26
|
+
# Pushes this build to worker's queue.
|
27
|
+
def enqueue
|
28
|
+
BuildWorker.perform_async(id)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Runs this build, usually called from BuildWorker#perform.
|
32
|
+
def run
|
33
|
+
start
|
34
|
+
finish(job.run)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns elapsed sec as a Float or nil.
|
38
|
+
def sec
|
39
|
+
finished_at - started_at if finished_at && started_at
|
40
|
+
end
|
41
|
+
|
42
|
+
def status_name
|
43
|
+
case status
|
44
|
+
when true
|
45
|
+
"success"
|
46
|
+
when false
|
47
|
+
"failure"
|
48
|
+
when nil
|
49
|
+
"unfinished"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def notify(type)
|
56
|
+
$redis.publish("build.#{type}", notified_data)
|
57
|
+
end
|
58
|
+
|
59
|
+
def notified_data
|
60
|
+
{ id: id, job_id: job.id, status: status_name, finished_at: finished_at }.to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
update_attributes!(started_at: Time.now)
|
65
|
+
notify(:started)
|
66
|
+
end
|
67
|
+
|
68
|
+
def finish(result)
|
69
|
+
reload.update_attributes!(finished_at: Time.now, output: result[:output], status: result[:status])
|
70
|
+
notify(:finished)
|
71
|
+
end
|
72
|
+
end
|
data/app/models/job.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
class Job < ActiveRecord::Base
|
2
|
+
include Altria::Proprietary
|
3
|
+
|
4
|
+
serialize :properties, Hash
|
5
|
+
|
6
|
+
validates :name, presence: true
|
7
|
+
|
8
|
+
has_many :builds
|
9
|
+
|
10
|
+
scope :recent, -> { order("updated_at DESC") }
|
11
|
+
|
12
|
+
delegate :status, :status_name, :status_icon_css_class, to: :last_finished_build, allow_nil: true
|
13
|
+
|
14
|
+
delegate :scheduled?, to: :scheduler, allow_nil: true
|
15
|
+
|
16
|
+
property(:description, type: :text)
|
17
|
+
|
18
|
+
property(:script, type: :text)
|
19
|
+
|
20
|
+
property(:schedule, placeholder: "* * * * *")
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def create_with_properties(params)
|
24
|
+
new.update_attributes_with_properties(params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def scheduled
|
28
|
+
select(&:scheduled?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def before_enqueue(&block)
|
32
|
+
before_enqueues << block
|
33
|
+
end
|
34
|
+
|
35
|
+
def before_enqueues
|
36
|
+
@before_enqueues ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_enqueue(&block)
|
40
|
+
after_enqueues << block
|
41
|
+
end
|
42
|
+
|
43
|
+
def after_enqueues
|
44
|
+
@after_enqueues ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def before_execute(&block)
|
48
|
+
before_executes << block
|
49
|
+
end
|
50
|
+
|
51
|
+
def before_executes
|
52
|
+
@before_executes ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
def after_execute(&block)
|
56
|
+
after_executes << block
|
57
|
+
end
|
58
|
+
|
59
|
+
def after_executes
|
60
|
+
@after_executes ||= []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def run
|
65
|
+
script ? execute : raise_script_not_found
|
66
|
+
end
|
67
|
+
|
68
|
+
def scheduler
|
69
|
+
Altria::Scheduler.new(schedule) if schedule.present?
|
70
|
+
end
|
71
|
+
|
72
|
+
def enqueue
|
73
|
+
builds.create.tap(&:enqueue).tap { execute_after_enqueues }
|
74
|
+
end
|
75
|
+
|
76
|
+
def enqueue_with_before_enqueues
|
77
|
+
enqueue if execute_before_enqueues
|
78
|
+
end
|
79
|
+
|
80
|
+
def status_name
|
81
|
+
last_finished_build.try(:status_name) || "unfinished"
|
82
|
+
end
|
83
|
+
|
84
|
+
def current_build
|
85
|
+
builds.running.latest
|
86
|
+
end
|
87
|
+
|
88
|
+
def last_finished_build
|
89
|
+
builds.finished.order(:finished_at).last
|
90
|
+
end
|
91
|
+
|
92
|
+
def update_attributes_with_properties(params)
|
93
|
+
params.slice(:name, *self.class.property_names).each {|key, value| send("#{key}=", value) }
|
94
|
+
tap(&:save)
|
95
|
+
end
|
96
|
+
|
97
|
+
def workspace
|
98
|
+
@workspace ||= Altria::Workspace.new(workspace_path)
|
99
|
+
end
|
100
|
+
|
101
|
+
def workspace_path
|
102
|
+
Altria.configuration.workspace_path + "jobs/#{id}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def execute
|
106
|
+
execute_before_executes
|
107
|
+
execute_without_before_executes.tap { execute_after_executes }
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def execute_without_before_executes
|
113
|
+
workspace.chdir { execute_script }
|
114
|
+
end
|
115
|
+
|
116
|
+
def execute_script
|
117
|
+
Altria::Executer.execute(script)
|
118
|
+
end
|
119
|
+
|
120
|
+
def execute_before_enqueues
|
121
|
+
self.class.before_enqueues.all? {|hook| instance_exec(&hook) != false }
|
122
|
+
end
|
123
|
+
|
124
|
+
def execute_after_enqueues
|
125
|
+
self.class.after_enqueues.all? {|hook| instance_exec(&hook) != false }
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute_before_executes
|
129
|
+
self.class.before_executes.all? {|hook| instance_exec(&hook) != false }
|
130
|
+
end
|
131
|
+
|
132
|
+
def execute_after_executes
|
133
|
+
self.class.after_executes.all? {|hook| instance_exec(&hook) != false }
|
134
|
+
end
|
135
|
+
|
136
|
+
def raise_script_not_found
|
137
|
+
raise ScriptNotFound, 'You must set properties["script"]'
|
138
|
+
end
|
139
|
+
|
140
|
+
class ScriptNotFound < StandardError; end
|
141
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
section.breadcrumbs
|
2
|
+
ul
|
3
|
+
li= link_to "Home", root_path
|
4
|
+
li.delimiter
|
5
|
+
li= link_to @resource.job.name, @resource.job
|
6
|
+
li.delimiter
|
7
|
+
li= link_to "##{@resource.id}", [@resource.job, @resource]
|
8
|
+
|
9
|
+
section.build class = @resource.status_name
|
10
|
+
h1
|
11
|
+
span ##{@resource.id}
|
12
|
+
|
13
|
+
table
|
14
|
+
tr
|
15
|
+
td started_at
|
16
|
+
td= ":"
|
17
|
+
td= @resource.started_at
|
18
|
+
|
19
|
+
tr
|
20
|
+
td finished_at
|
21
|
+
td= ":"
|
22
|
+
td= @resource.finished_at
|
23
|
+
|
24
|
+
tr
|
25
|
+
td sec
|
26
|
+
td= ":"
|
27
|
+
td= @resource.sec
|
28
|
+
|
29
|
+
tr
|
30
|
+
td status
|
31
|
+
td= ":"
|
32
|
+
td= @resource.status_name
|
33
|
+
|
34
|
+
pre
|
35
|
+
code
|
36
|
+
= @resource.output
|