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