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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +41 -0
  3. data/LICENSE.txt +22 -0
  4. data/Procfile +4 -0
  5. data/README.md +40 -0
  6. data/Rakefile +6 -0
  7. data/altria.gemspec +50 -0
  8. data/app/assets/images/rails.png +0 -0
  9. data/app/assets/javascripts/altria.js.coffee +1 -0
  10. data/app/assets/javascripts/altria/build_view.js.coffee +13 -0
  11. data/app/assets/javascripts/altria/job_view.js.coffee +10 -0
  12. data/app/assets/javascripts/altria/server_event.js.coffee +8 -0
  13. data/app/assets/javascripts/application.js +20 -0
  14. data/app/assets/javascripts/events.js.coffee +13 -0
  15. data/app/assets/stylesheets/application.css +14 -0
  16. data/app/assets/stylesheets/builds.scss +35 -0
  17. data/app/assets/stylesheets/common.scss +69 -0
  18. data/app/assets/stylesheets/jobs.scss +129 -0
  19. data/app/assets/stylesheets/layout.scss +19 -0
  20. data/app/assets/stylesheets/mixin.scss +26 -0
  21. data/app/controllers/application_controller.rb +24 -0
  22. data/app/controllers/builds_controller.rb +48 -0
  23. data/app/controllers/events_controller.rb +27 -0
  24. data/app/controllers/jobs_controller.rb +50 -0
  25. data/app/helpers/application_helper.rb +5 -0
  26. data/app/models/build.rb +72 -0
  27. data/app/models/job.rb +141 -0
  28. data/app/views/builds/show.html.slim +36 -0
  29. data/app/views/jobs/_form.html.slim +22 -0
  30. data/app/views/jobs/edit.html.slim +9 -0
  31. data/app/views/jobs/index.html.slim +19 -0
  32. data/app/views/jobs/new.html.slim +7 -0
  33. data/app/views/jobs/show.html.slim +28 -0
  34. data/app/views/layouts/application.html.slim +11 -0
  35. data/app/workers/build_worker.rb +13 -0
  36. data/app/workers/job_enqueue_worker.rb +13 -0
  37. data/bin/altria +6 -0
  38. data/config.ru +4 -0
  39. data/config/application.rb +52 -0
  40. data/config/boot.rb +6 -0
  41. data/config/database.yml +29 -0
  42. data/config/environment.rb +5 -0
  43. data/config/environments/development.rb +23 -0
  44. data/config/environments/production.rb +71 -0
  45. data/config/environments/test.rb +33 -0
  46. data/config/initializers/backtrace_silencers.rb +7 -0
  47. data/config/initializers/inflections.rb +15 -0
  48. data/config/initializers/mime_types.rb +5 -0
  49. data/config/initializers/redis.rb +1 -0
  50. data/config/initializers/secret_token.rb +8 -0
  51. data/config/initializers/session_store.rb +8 -0
  52. data/config/initializers/wrap_parameters.rb +14 -0
  53. data/config/locales/en.yml +5 -0
  54. data/config/routes.rb +9 -0
  55. data/db/migrate/20130531143239_create_jobs.rb +10 -0
  56. data/db/migrate/20130531155155_create_builds.rb +14 -0
  57. data/db/migrate/20130608153135_change_job_column_name_from_config_to_properties.rb +9 -0
  58. data/db/migrate/20130608153655_add_column_properties_to_builds.rb +5 -0
  59. data/db/schema.rb +36 -0
  60. data/db/seeds.rb +7 -0
  61. data/lib/altria.rb +13 -0
  62. data/lib/altria/command.rb +61 -0
  63. data/lib/altria/configuration.rb +9 -0
  64. data/lib/altria/executer.rb +37 -0
  65. data/lib/altria/proprietary.rb +56 -0
  66. data/lib/altria/responder.rb +12 -0
  67. data/lib/altria/scheduler.rb +55 -0
  68. data/lib/altria/version.rb +3 -0
  69. data/lib/altria/workspace.rb +26 -0
  70. data/lib/tasks/resque.rake +3 -0
  71. data/public/404.html +26 -0
  72. data/public/422.html +26 -0
  73. data/public/500.html +25 -0
  74. data/public/favicon.ico +0 -0
  75. data/public/robots.txt +5 -0
  76. data/script/clock.rb +5 -0
  77. data/script/rails +6 -0
  78. data/spec/altria/command_spec.rb +53 -0
  79. data/spec/factories/build.rb +7 -0
  80. data/spec/factories/job.rb +7 -0
  81. data/spec/models/build_spec.rb +35 -0
  82. data/spec/models/job_spec.rb +101 -0
  83. data/spec/requests/builds_spec.rb +81 -0
  84. data/spec/requests/jobs_spec.rb +88 -0
  85. data/spec/spec_helper.rb +48 -0
  86. metadata +417 -0
@@ -0,0 +1,19 @@
1
+ header {
2
+ background: #333;
3
+
4
+ a {
5
+ color: white;
6
+ font-weight: normal;
7
+ }
8
+
9
+ .inner {
10
+ width: 920px;
11
+ margin: 0 auto;
12
+ }
13
+ }
14
+
15
+ section {
16
+ width: 920px;
17
+ margin: 0 auto;
18
+ padding: 20px;
19
+ }
@@ -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
@@ -0,0 +1,5 @@
1
+ module ApplicationHelper
2
+ def action_classes
3
+ %W[#{controller_name}_controller #{action_name}_action]
4
+ end
5
+ end
@@ -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