magi 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +12 -3
  3. data/Procfile +4 -0
  4. data/README.md +25 -1
  5. data/app/assets/stylesheets/application.css +1 -0
  6. data/app/assets/stylesheets/builds.scss +35 -0
  7. data/app/assets/stylesheets/common.scss +69 -0
  8. data/app/assets/stylesheets/jobs.scss +121 -0
  9. data/app/assets/stylesheets/layout.scss +19 -0
  10. data/app/assets/stylesheets/mixin.scss +6 -0
  11. data/app/controllers/application_controller.rb +16 -0
  12. data/app/controllers/builds_controller.rb +45 -0
  13. data/app/controllers/jobs_controller.rb +43 -0
  14. data/app/models/build.rb +39 -0
  15. data/app/models/job.rb +88 -0
  16. data/app/views/builds/show.html.slim +36 -0
  17. data/app/views/jobs/_form.html.slim +27 -0
  18. data/app/views/jobs/edit.html.slim +9 -0
  19. data/app/views/jobs/index.html.slim +19 -0
  20. data/app/views/jobs/new.html.slim +7 -0
  21. data/app/views/jobs/show.html.slim +24 -0
  22. data/app/views/layouts/application.html.slim +10 -0
  23. data/app/workers/build_worker.rb +8 -0
  24. data/bin/magi +6 -0
  25. data/config/application.rb +1 -1
  26. data/config/database.yml +21 -17
  27. data/config/routes.rb +4 -55
  28. data/db/migrate/20130531143239_create_jobs.rb +10 -0
  29. data/db/migrate/20130531155155_create_builds.rb +14 -0
  30. data/db/schema.rb +35 -0
  31. data/lib/magi/command.rb +53 -0
  32. data/lib/magi/executer.rb +31 -0
  33. data/lib/magi/scheduler.rb +55 -0
  34. data/lib/magi/version.rb +1 -1
  35. data/lib/magi.rb +1 -4
  36. data/magi.gemspec +29 -1
  37. data/script/clock.rb +5 -0
  38. data/spec/factories/build.rb +6 -0
  39. data/spec/factories/job.rb +7 -0
  40. data/spec/models/build_spec.rb +35 -0
  41. data/spec/models/job_spec.rb +54 -0
  42. data/spec/requests/builds_spec.rb +68 -0
  43. data/spec/requests/jobs_spec.rb +93 -0
  44. data/spec/spec_helper.rb +28 -0
  45. metadata +324 -50
  46. data/.gitignore +0 -19
  47. data/app/mailers/.gitkeep +0 -0
  48. data/app/models/.gitkeep +0 -0
  49. data/app/views/layouts/application.html.erb +0 -14
  50. data/doc/README_FOR_APP +0 -2
  51. data/lib/assets/.gitkeep +0 -0
  52. data/lib/tasks/.gitkeep +0 -0
  53. data/log/.gitkeep +0 -0
  54. data/public/index.html +0 -241
  55. data/test/fixtures/.gitkeep +0 -0
  56. data/test/functional/.gitkeep +0 -0
  57. data/test/integration/.gitkeep +0 -0
  58. data/test/performance/browsing_test.rb +0 -12
  59. data/test/test_helper.rb +0 -13
  60. data/test/unit/.gitkeep +0 -0
  61. data/vendor/assets/javascripts/.gitkeep +0 -0
  62. data/vendor/assets/stylesheets/.gitkeep +0 -0
  63. data/vendor/plugins/.gitkeep +0 -0
@@ -0,0 +1,10 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title magi
5
+ = csrf_meta_tags
6
+ = stylesheet_link_tag "application", media: "all"
7
+ = javascript_include_tag "application"
8
+
9
+ body
10
+ = yield
@@ -0,0 +1,8 @@
1
+ class BuildWorker
2
+ include Sidekiq::Worker
3
+
4
+ # Takes a build id and invokes it.
5
+ def perform(id)
6
+ Build.find(id).start
7
+ end
8
+ end
data/bin/magi ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "magi"
5
+
6
+ Magi::Command.call(ARGV)
@@ -16,7 +16,7 @@ module Magi
16
16
  # -- all .rb files in that directory are automatically loaded.
17
17
 
18
18
  # Custom directories with classes and modules you want to be autoloadable.
19
- # config.autoload_paths += %W(#{config.root}/extras)
19
+ config.autoload_paths += %W(#{config.root}/lib)
20
20
 
21
21
  # Only load the plugins named here, in the order given (default is alphabetical).
22
22
  # :all can be used as a placeholder for all plugins not explicitly named.
data/config/database.yml CHANGED
@@ -1,25 +1,29 @@
1
- # SQLite version 3.x
2
- # gem install sqlite3
3
- #
4
- # Ensure the SQLite 3 gem is defined in your Gemfile
5
- # gem 'sqlite3'
6
1
  development:
7
- adapter: sqlite3
8
- database: db/development.sqlite3
2
+ adapter: mysql2
3
+ encoding: utf8
4
+ reconnect: false
5
+ database: magi_development
9
6
  pool: 5
10
- timeout: 5000
7
+ username: root
8
+ password:
9
+ host: localhost
11
10
 
12
- # Warning: The database defined as "test" will be erased and
13
- # re-generated from your development database when you run "rake".
14
- # Do not set this db to the same as development or production.
15
11
  test:
16
- adapter: sqlite3
17
- database: db/test.sqlite3
12
+ adapter: mysql2
13
+ encoding: utf8
14
+ reconnect: false
15
+ database: magi_test
18
16
  pool: 5
19
- timeout: 5000
17
+ username: root
18
+ password:
19
+ host: localhost
20
20
 
21
21
  production:
22
- adapter: sqlite3
23
- database: db/production.sqlite3
22
+ adapter: mysql2
23
+ encoding: utf8
24
+ reconnect: false
25
+ database: magi_production
24
26
  pool: 5
25
- timeout: 5000
27
+ username: root
28
+ password:
29
+ host: localhost
data/config/routes.rb CHANGED
@@ -1,58 +1,7 @@
1
1
  Magi::Application.routes.draw do
2
- # The priority is based upon order of creation:
3
- # first created -> highest priority.
2
+ root to: "jobs#index"
4
3
 
5
- # Sample of regular route:
6
- # match 'products/:id' => 'catalog#view'
7
- # Keep in mind you can assign values other than :controller and :action
8
-
9
- # Sample of named route:
10
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
11
- # This route can be invoked with purchase_url(:id => product.id)
12
-
13
- # Sample resource route (maps HTTP verbs to controller actions automatically):
14
- # resources :products
15
-
16
- # Sample resource route with options:
17
- # resources :products do
18
- # member do
19
- # get 'short'
20
- # post 'toggle'
21
- # end
22
- #
23
- # collection do
24
- # get 'sold'
25
- # end
26
- # end
27
-
28
- # Sample resource route with sub-resources:
29
- # resources :products do
30
- # resources :comments, :sales
31
- # resource :seller
32
- # end
33
-
34
- # Sample resource route with more complex sub-resources
35
- # resources :products do
36
- # resources :comments
37
- # resources :sales do
38
- # get 'recent', :on => :collection
39
- # end
40
- # end
41
-
42
- # Sample resource route within a namespace:
43
- # namespace :admin do
44
- # # Directs /admin/products/* to Admin::ProductsController
45
- # # (app/controllers/admin/products_controller.rb)
46
- # resources :products
47
- # end
48
-
49
- # You can have the root of your site routed with "root"
50
- # just remember to delete public/index.html.
51
- # root :to => 'welcome#index'
52
-
53
- # See how all your routes lay out with "rake routes"
54
-
55
- # This is a legacy wild controller route that's not recommended for RESTful applications.
56
- # Note: This route will make all actions in every controller accessible via GET requests.
57
- # match ':controller(/:action(/:id))(.:format)'
4
+ resources :jobs do
5
+ resources :builds, only: [:index, :show, :create, :update, :destroy]
6
+ end
58
7
  end
@@ -0,0 +1,10 @@
1
+ class CreateJobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :jobs do |t|
4
+ t.string :name
5
+ t.text :config
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ class CreateBuilds < ActiveRecord::Migration
2
+ def change
3
+ create_table :builds do |t|
4
+ t.boolean :status
5
+ t.datetime :started_at
6
+ t.datetime :finished_at
7
+ t.references :job
8
+ t.text :output
9
+
10
+ t.timestamps
11
+ end
12
+ add_index :builds, :job_id
13
+ end
14
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,35 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20130531155155) do
15
+
16
+ create_table "builds", :force => true do |t|
17
+ t.boolean "status"
18
+ t.datetime "started_at"
19
+ t.datetime "finished_at"
20
+ t.integer "job_id"
21
+ t.text "output"
22
+ t.datetime "created_at", :null => false
23
+ t.datetime "updated_at", :null => false
24
+ end
25
+
26
+ add_index "builds", ["job_id"], :name => "index_builds_on_job_id"
27
+
28
+ create_table "jobs", :force => true do |t|
29
+ t.string "name"
30
+ t.text "config"
31
+ t.datetime "created_at", :null => false
32
+ t.datetime "updated_at", :null => false
33
+ end
34
+
35
+ end
@@ -0,0 +1,53 @@
1
+ # Provides command line interface for Magi.
2
+ #
3
+ # Examples
4
+ #
5
+ # # Takes ARGV and invokes a job.
6
+ # Magi::Command.call(ARGV)
7
+ #
8
+ # # Invokes "setup" command to set up your database.
9
+ # Magi::Command.call(["setup"])
10
+ #
11
+ # # Invokes "start" command to start redis, rails, and sidekiq processes.
12
+ # Magi::Command.call(["start"])
13
+ #
14
+ module Magi
15
+ class Command
16
+ def self.call(*args)
17
+ new(*args).call
18
+ end
19
+
20
+ def initialize(argv)
21
+ @argv = argv
22
+ end
23
+
24
+ def call
25
+ case name
26
+ when "setup"
27
+ setup
28
+ when "start"
29
+ start
30
+ else
31
+ puts "Usage: magi {setup|start}"
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def name
38
+ @argv[0]
39
+ end
40
+
41
+ def setup
42
+ system("cd #{root_path} && bundle exec rake db:create db:migrate")
43
+ end
44
+
45
+ def start
46
+ system("cd #{root_path} && bundle exec foreman start")
47
+ end
48
+
49
+ def root_path
50
+ File.expand_path("../../..", __FILE__)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ require "tempfile"
2
+
3
+ module Magi
4
+ class Executer
5
+ def self.execute(*args)
6
+ new(*args).execute
7
+ end
8
+
9
+ def initialize(script)
10
+ @script = script
11
+ end
12
+
13
+ def execute
14
+ { status: status, output: output }
15
+ end
16
+
17
+ private
18
+
19
+ def tempfile
20
+ @tempfile ||= Tempfile.new("")
21
+ end
22
+
23
+ def status
24
+ system(@script, out: tempfile, err: tempfile)
25
+ end
26
+
27
+ def output
28
+ tempfile.tap(&:close).open.read
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ module Magi
2
+ class Scheduler
3
+ CRON_REGEXP = /\A(\d+|\*) (\d+|\*) (\d+|\*) (\d+|\*) (\d+|\*)\s*\z/
4
+
5
+ # Takes a schedule as a String.
6
+ def initialize(schedule)
7
+ @result, @min, @hour, @day, @month, @wday = *schedule.match(CRON_REGEXP)
8
+ validate
9
+ end
10
+
11
+ def scheduled?
12
+ [:min, :hour, :day, :month, :wday].all? do |key|
13
+ send(key).nil? || send(key) == now.send(key)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def min
20
+ @min.to_i if @min != "*"
21
+ end
22
+
23
+ def hour
24
+ @hour.to_i if @hour != "*"
25
+ end
26
+
27
+ def day
28
+ @day.to_i if @day != "*"
29
+ end
30
+
31
+ def month
32
+ @month.to_i if @month != "*"
33
+ end
34
+
35
+ def wday
36
+ @wday.to_i if @wday != "*"
37
+ end
38
+
39
+ def now
40
+ @now ||= Time.now
41
+ end
42
+
43
+ def validate
44
+ raise_argument_error unless valid?
45
+ end
46
+
47
+ def valid?
48
+ !!@result
49
+ end
50
+
51
+ def raise_argument_error
52
+ raise ArgumentError, "Input must match with #{CRON_REGEXP}"
53
+ end
54
+ end
55
+ end
data/lib/magi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Magi
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/magi.rb CHANGED
@@ -1,5 +1,2 @@
1
+ require "magi/command"
1
2
  require "magi/version"
2
-
3
- module Magi
4
- # Your code goes here...
5
- end
data/magi.gemspec CHANGED
@@ -12,11 +12,39 @@ Gem::Specification.new do |spec|
12
12
  spec.homepage = "https://github.com/r7kamura/magi"
13
13
  spec.license = "MIT"
14
14
 
15
- spec.files = `git ls-files`.split($/)
15
+ spec.files = Dir["{app,bin,config,db,lib,public,script,spec}/**/*"]
16
+ spec.files += %w[
17
+ Gemfile
18
+ LICENSE.txt
19
+ Procfile
20
+ README.md
21
+ Rakefile
22
+ config.ru
23
+ magi.gemspec
24
+ ]
16
25
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
26
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
27
  spec.require_paths = ["lib"]
19
28
 
29
+ spec.add_dependency "clockwork"
30
+ spec.add_dependency "coffee-rails", "~> 3.2.1"
31
+ spec.add_dependency "font-awesome-rails"
32
+ spec.add_dependency "foreman"
33
+ spec.add_dependency "jquery-rails"
34
+ spec.add_dependency "mysql2"
35
+ spec.add_dependency "quiet_assets"
36
+ spec.add_dependency "rails", "3.2.13"
37
+ spec.add_dependency "sass-rails", "~> 3.2.3"
38
+ spec.add_dependency "sidekiq"
39
+ spec.add_dependency "slim"
40
+ spec.add_dependency "uglifier", ">= 1.0.3"
20
41
  spec.add_development_dependency "bundler", "~> 1.3"
42
+ spec.add_development_dependency "factory_girl_rails"
43
+ spec.add_development_dependency "pry-rails"
21
44
  spec.add_development_dependency "rake"
45
+ spec.add_development_dependency "response_code_matchers"
46
+ spec.add_development_dependency "rspec-json_matcher"
47
+ spec.add_development_dependency "rspec-rails"
48
+ spec.add_development_dependency "simplecov"
49
+ spec.add_development_dependency "thin"
22
50
  end
data/script/clock.rb ADDED
@@ -0,0 +1,5 @@
1
+ require File.expand_path("../../config/environment.rb", __FILE__)
2
+
3
+ Clockwork.every(1.minute, "Job.queue") do
4
+ Job.queue
5
+ end
@@ -0,0 +1,6 @@
1
+ require "factory_girl"
2
+
3
+ FactoryGirl.define do
4
+ factory(:build) do
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ require "factory_girl"
2
+
3
+ FactoryGirl.define do
4
+ factory(:job) do
5
+ sequence(:name) {|n| "name #{n}" }
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe Build do
4
+ let(:build) do
5
+ FactoryGirl.create(:build, job: job)
6
+ end
7
+
8
+ let(:job) do
9
+ FactoryGirl.create(:job, config: { "script" => "true" })
10
+ end
11
+
12
+ describe "#start" do
13
+ context "with success" do
14
+ it "starts its job and sets status with true" do
15
+ build.start
16
+ build.started_at.should be_present
17
+ build.finished_at.should be_present
18
+ build.status.should == true
19
+ end
20
+ end
21
+
22
+ context "with failure" do
23
+ before do
24
+ build.job.config["script"] = "false"
25
+ end
26
+
27
+ it "starts its job and sets status with false" do
28
+ build.start
29
+ build.started_at.should be_present
30
+ build.finished_at.should be_present
31
+ build.status.should == false
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe Job do
4
+ let(:job) do
5
+ FactoryGirl.create(:job)
6
+ end
7
+
8
+ describe "#start" do
9
+ context "without script" do
10
+ it "raises Job::ScriptNotFound" do
11
+ expect { job.start }.to raise_error(Job::ScriptNotFound)
12
+ end
13
+ end
14
+
15
+ context "with script" do
16
+ before do
17
+ job.config["script"] = "true"
18
+ end
19
+
20
+ it "starts its job" do
21
+ Magi::Executer.should_receive(:execute).with("true")
22
+ job.start
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "#scheduled?" do
28
+ context "without schedule" do
29
+ it "returns false" do
30
+ job.should_not be_scheduled
31
+ end
32
+ end
33
+
34
+ context "with matched schedule" do
35
+ before do
36
+ job.config["schedule"] = "* * * * *"
37
+ end
38
+
39
+ it "returns true" do
40
+ job.should be_scheduled
41
+ end
42
+ end
43
+
44
+ context "without matched schedule" do
45
+ before do
46
+ job.config["schedule"] = "0 0 0 0 0"
47
+ end
48
+
49
+ it "returns false" do
50
+ job.should_not be_scheduled
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ require "spec_helper"
2
+
3
+ describe "Builds" do
4
+ let(:params) do
5
+ {}
6
+ end
7
+
8
+ let(:env) do
9
+ { "HTTP_ACCEPT" => "application/json" }
10
+ end
11
+
12
+ let(:job) do
13
+ FactoryGirl.create(:job)
14
+ end
15
+
16
+ let(:build) do
17
+ job.builds.create(id: 1)
18
+ end
19
+
20
+ describe "GET /jobs/:job_id/builds" do
21
+ before do
22
+ build
23
+ end
24
+
25
+ it "returns builds of the job" do
26
+ get "/jobs/#{job.id}/builds", params, env
27
+ response.status.should == 200
28
+ response.body.should be_json([Hash])
29
+ end
30
+ end
31
+
32
+ describe "GET /jobs/:job_id/builds/:id" do
33
+ it "returns the build" do
34
+ get "/jobs/#{job.id}/builds/#{build.id}", params, env
35
+ response.status.should == 200
36
+ response.body.should be_json(Hash)
37
+ end
38
+ end
39
+
40
+ describe "POST /jobs/:job_id/builds" do
41
+ it "creates a new build and queue it" do
42
+ post "/jobs/#{job.id}/builds", params, env
43
+ response.status.should == 201
44
+ response.body.should be_json(Hash)
45
+ BuildWorker.jobs.should have(1).queue
46
+ end
47
+ end
48
+
49
+ describe "PUT /jobs/:job_id/builds/:id" do
50
+ before do
51
+ params[:build] = { status: true }
52
+ end
53
+
54
+ it "updates the build" do
55
+ put "/jobs/#{job.id}/builds/#{build.id}", params, env
56
+ response.status.should == 204
57
+ build.reload.status.should == true
58
+ end
59
+ end
60
+
61
+ describe "DELETE /jobs/:job_id/builds/:id" do
62
+ it "deletes the build" do
63
+ delete "/jobs/#{job.id}/builds/#{build.id}", params, env
64
+ response.status.should == 204
65
+ Build.should have(0).build
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,93 @@
1
+ require "spec_helper"
2
+
3
+ describe "Jobs" do
4
+ let(:params) do
5
+ {}
6
+ end
7
+
8
+ let(:env) do
9
+ { "HTTP_ACCEPT" => "application/json" }
10
+ end
11
+
12
+ let(:job) do
13
+ FactoryGirl.create(:job)
14
+ end
15
+
16
+ describe "GET /jobs" do
17
+ before do
18
+ job
19
+ end
20
+
21
+ it "returns jobs" do
22
+ get "/jobs", params, env
23
+ response.status.should == 200
24
+ response.body.should be_json([Hash])
25
+ end
26
+ end
27
+
28
+ describe "GET /jobs/:id" do
29
+ it "returns the job" do
30
+ get "/jobs/#{job.id}", params, env
31
+ response.status.should == 200
32
+ response.body.should be_json(Hash)
33
+ end
34
+ end
35
+
36
+ describe "POST /jobs" do
37
+ before do
38
+ params[:job] = { name: "name" }
39
+ end
40
+
41
+ context "with invalid params" do
42
+ before do
43
+ params.delete(:job)
44
+ end
45
+
46
+ it "returns 400" do
47
+ post "/jobs", params, env
48
+ response.status.should == 400
49
+ end
50
+ end
51
+
52
+ context "with valid condition" do
53
+ it "creates a new job" do
54
+ post "/jobs", params, env
55
+ response.status.should == 201
56
+ Job.should have(1).job
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "PUT /jobs/:id" do
62
+ before do
63
+ params[:job] = { name: "name" }
64
+ end
65
+
66
+ context "with invalid params" do
67
+ before do
68
+ params.delete(:job)
69
+ end
70
+
71
+ it "returns 400" do
72
+ put "/jobs/#{job.id}", params, env
73
+ response.status.should == 400
74
+ end
75
+ end
76
+
77
+ context "with valid condition" do
78
+ it "updates the job" do
79
+ put "/jobs/#{job.id}", params, env
80
+ response.status.should == 204
81
+ job.reload.name.should == "name"
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "DELETE /jobs/:id" do
87
+ it "deletes the job" do
88
+ delete "/jobs/#{job.id}", params, env
89
+ response.status.should == 204
90
+ Job.should_not be_exist(job.id)
91
+ end
92
+ end
93
+ end