magi 0.0.1 → 0.0.2

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