jobs-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +55 -0
  3. data/Rakefile +6 -0
  4. data/app/channels/application_cable/channel.rb +4 -0
  5. data/app/channels/application_cable/connection.rb +4 -0
  6. data/app/clients/bitbucket_client.rb +31 -0
  7. data/app/clients/github_client.rb +100 -0
  8. data/app/controllers/application_controller.rb +23 -0
  9. data/app/controllers/events_controller.rb +12 -0
  10. data/app/controllers/hooks_controller.rb +32 -0
  11. data/app/controllers/jobs_controller.rb +20 -0
  12. data/app/controllers/pipelines_controller.rb +11 -0
  13. data/app/controllers/projects_controller.rb +39 -0
  14. data/app/controllers/sessions_controller.rb +17 -0
  15. data/app/controllers/users_controller.rb +15 -0
  16. data/app/jobs/application_job.rb +2 -0
  17. data/app/jobs/pipeline_runner_job.rb +106 -0
  18. data/app/mailers/application_mailer.rb +4 -0
  19. data/app/models/application_record.rb +3 -0
  20. data/app/models/basic_pipeline.rb +73 -0
  21. data/app/models/event.rb +24 -0
  22. data/app/models/job.rb +27 -0
  23. data/app/models/pipeline.rb +21 -0
  24. data/app/models/pipeline_definition.rb +98 -0
  25. data/app/models/project.rb +45 -0
  26. data/app/models/team.rb +11 -0
  27. data/app/models/user.rb +43 -0
  28. data/app/models/user_team.rb +4 -0
  29. data/app/serializers/job_serializer.rb +12 -0
  30. data/app/serializers/pipeline_serializer.rb +3 -0
  31. data/app/serializers/project_serializer.rb +4 -0
  32. data/app/serializers/team_serializer.rb +3 -0
  33. data/app/serializers/user_serializer.rb +3 -0
  34. data/app/views/layouts/mailer.html.erb +13 -0
  35. data/app/views/layouts/mailer.text.erb +1 -0
  36. data/config/application.rb +37 -0
  37. data/config/boot.rb +3 -0
  38. data/config/cable.yml +9 -0
  39. data/config/database.yml +16 -0
  40. data/config/environment.rb +5 -0
  41. data/config/environments/development.rb +47 -0
  42. data/config/environments/production.rb +78 -0
  43. data/config/environments/test.rb +42 -0
  44. data/config/initializers/app_setup.rb +46 -0
  45. data/config/initializers/backtrace_silencers.rb +7 -0
  46. data/config/initializers/filter_parameter_logging.rb +4 -0
  47. data/config/initializers/inflections.rb +16 -0
  48. data/config/initializers/mime_types.rb +4 -0
  49. data/config/initializers/new_framework_defaults.rb +18 -0
  50. data/config/initializers/wrap_parameters.rb +14 -0
  51. data/config/locales/en.yml +23 -0
  52. data/config/puma.rb +47 -0
  53. data/config/routes.rb +23 -0
  54. data/config/secrets.yml +22 -0
  55. data/config/spring.rb +6 -0
  56. data/db/migrate/20161107155702_create_projects.rb +15 -0
  57. data/db/migrate/20161108233030_create_pipelines.rb +15 -0
  58. data/db/migrate/20161108233304_create_jobs.rb +16 -0
  59. data/db/migrate/20161109203929_create_users.rb +15 -0
  60. data/db/migrate/20161109203949_create_teams.rb +11 -0
  61. data/db/migrate/20161109204011_create_user_teams.rb +10 -0
  62. data/db/migrate/20161109205639_add_references_to_projects.rb +6 -0
  63. data/db/migrate/20161110154413_create_delayed_jobs.rb +22 -0
  64. data/db/schema.rb +111 -0
  65. data/db/seeds.rb +7 -0
  66. data/lib/jobs_api.rb +13 -0
  67. metadata +255 -0
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: 'from@example.com'
3
+ layout 'mailer'
4
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
@@ -0,0 +1,73 @@
1
+ class BasicPipeline < PipelineDefinition
2
+ stage :first
3
+
4
+ def first
5
+ scm = {}
6
+ if event.data[:commit]
7
+ scm[:commit] = event.data[:commit]
8
+ scm[:branch] = event.data[:branch]
9
+ scm[:pull_request] = event.data[:pull_request]
10
+ else
11
+ latest = project.owner.client.latest_commit(project.repo_name)
12
+ scm[:commit] = latest[:sha]
13
+ scm[:branch] = latest[:branch]
14
+ end
15
+
16
+ create_job(
17
+ project: project,
18
+ scm: scm,
19
+ spec: basic_job_from_file,
20
+ )
21
+ end
22
+
23
+ def basic_job_from_file
24
+ ci_file = project.owner.client.file(project.repo_name, 'ci.yml')
25
+ if ci_file
26
+ spec = {}
27
+ build = HashWithIndifferentAccess.new(YAML.load(ci_file))
28
+
29
+ # services are copied directly
30
+ spec[:services] = build[:services]
31
+
32
+ # turns each key into a section
33
+ spec[:sections] = []
34
+
35
+ spec[:sections] << {
36
+ name: 'setup',
37
+ fail_on_err: true,
38
+ commands: build[:setup],
39
+ on_success: build[:on_success],
40
+ on_failure: build[:on_failure],
41
+ }
42
+
43
+ spec[:sections] << {
44
+ name: 'test',
45
+ fail_on_err: true,
46
+ commands: build[:test],
47
+ on_success: build[:on_success],
48
+ on_failure: build[:on_failure],
49
+ }
50
+
51
+ spec[:sections] << {
52
+ name: 'after',
53
+ fail_on_err: true,
54
+ commands: build[:after],
55
+ }
56
+
57
+ spec
58
+ else
59
+ {
60
+ sections: [
61
+ {
62
+ name: 'test',
63
+ fail_on_err: true,
64
+ commands: ['exit 1'],
65
+ }
66
+ ]
67
+ }
68
+ end
69
+ end
70
+
71
+
72
+
73
+ end
@@ -0,0 +1,24 @@
1
+ class Event
2
+ attr_accessor :name, :project, :data
3
+
4
+ def initialize(data)
5
+ @data = data
6
+ @name = data[:name]
7
+ @project = data[:project_id] ? Project.find(data[:project_id]) : nil
8
+ end
9
+
10
+ def execute
11
+ return nil unless project.enabled_pipelines
12
+
13
+ project.enabled_pipelines.each do |pipeline_definition|
14
+ if pipeline_definition.constantize.trigger_when?(self)
15
+ PipelineRunnerJob.perform_later(nil, self.data, pipeline_definition)
16
+ end
17
+ end
18
+ end
19
+
20
+ def as_json
21
+ data
22
+ end
23
+
24
+ end
data/app/models/job.rb ADDED
@@ -0,0 +1,27 @@
1
+ # Job Spec:
2
+ # {
3
+ # sections: [
4
+ # { name: "name", on_success: [], on_failure: [], commands: [], fail_on_err: true }
5
+ # ],
6
+ # services: [
7
+ # { name: "", docker: { ... } }
8
+ # ],
9
+ # repo: {
10
+ # name: "repo",
11
+ # owner: "repo-owner",
12
+ # commit: "abcdefg",
13
+ # branch: "feature/x",
14
+ # provider: "github.com",
15
+ # }
16
+ # }
17
+ #
18
+ class Job < ApplicationRecord
19
+ belongs_to :pipeline
20
+ has_one :project, through: :pipeline
21
+ after_save { pipeline.run if status_changed? }
22
+
23
+ def self.pop
24
+ Job.where(status: 'PENDING').limit(1).update(worker: worker).first
25
+ end
26
+
27
+ end
@@ -0,0 +1,21 @@
1
+ class Pipeline < ApplicationRecord
2
+ belongs_to :project, optional: true
3
+ has_many :jobs
4
+
5
+ def definition_class
6
+ @definition_class ||= definition.constantize
7
+ end
8
+
9
+ def status?(is)
10
+ status && status.upcase == is.to_s.upcase
11
+ end
12
+
13
+ def run(event=nil)
14
+ PipelineRunnerJob.perform_later(self, event)
15
+ end
16
+
17
+ def event
18
+ @event ||= Event.new(super)
19
+ end
20
+
21
+ end
@@ -0,0 +1,98 @@
1
+ class PipelineDefinition
2
+ attr_reader :pipeline, :event
3
+
4
+ class PipelineConfigError
5
+ end
6
+
7
+ def self.start(event)
8
+ PipelineRunnerJob.perform_later(nil, event.data, self.name)
9
+ end
10
+
11
+ def self.trigger_when?(event)
12
+ true
13
+ end
14
+
15
+ def self.stages
16
+ @stages ||= []
17
+ end
18
+
19
+ def self.stage_options
20
+ @stage_options ||= {}
21
+ end
22
+
23
+ def self.ensure_stages
24
+ @ensure ||= []
25
+ end
26
+
27
+ def self.stage(name, opts={})
28
+ stages << name.to_sym
29
+ stage_options[name.to_sym] = opts
30
+ end
31
+
32
+ def self.ensure_stage(name)
33
+ ensure_stages << name.to_sym
34
+ end
35
+
36
+ def initialize(pipeline, event)
37
+ @pipeline = pipeline
38
+ @event = event
39
+ @job_creation_idx = 0
40
+ end
41
+
42
+ def create_job(key: nil, project: nil, scm: nil, spec: nil)
43
+ project = self.project unless project
44
+
45
+ scm = {} if scm.nil?
46
+ if project
47
+ scm[:owner] = project.repo_owner
48
+ scm[:name] = project.repo_name
49
+ scm[:provider] = project.repo_provider
50
+ end
51
+
52
+ @job_creation_idx += 1
53
+ key = "#{pipeline.definition.underscore}.#{pipeline.id}.#{stage}-#{@job_creation_idx}" unless key
54
+
55
+ Job.create!(
56
+ pipeline: pipeline,
57
+ pipeline_stage: stage,
58
+ status: 'QUEUED',
59
+ spec: spec.merge(repo: scm),
60
+ commit: scm[:commit],
61
+ branch: scm[:branch],
62
+ key: key,
63
+ )
64
+ end
65
+
66
+ def project=(project)
67
+ pipeline.update!(project_id: project.id)
68
+ @project = project
69
+ end
70
+
71
+ def project
72
+ @project ||= pipeline.project
73
+ end
74
+
75
+ def stage
76
+ pipeline.stage
77
+ end
78
+
79
+ def next
80
+ pipeline.run
81
+ end
82
+
83
+ def variables
84
+ @variables ||= OpenStruct.new(pipeline.variables)
85
+ end
86
+
87
+ def save
88
+ pipeline.update!(variables: variables.to_h)
89
+ end
90
+
91
+ def self.as_json
92
+ {
93
+ stages: stages.map { |stage| stage_options.merge(stage: stage) },
94
+ name: name,
95
+ }
96
+ end
97
+
98
+ end
@@ -0,0 +1,45 @@
1
+ class Project < ApplicationRecord
2
+ belongs_to :team, optional: true
3
+ belongs_to :user, optional: true
4
+ has_many :pipelines
5
+ has_many :jobs, through: :pipelines
6
+
7
+ def self.add_project(user, name)
8
+ repo = user.client.repo(name)
9
+
10
+ found = Project.find_by(repo_id: repo[:id])
11
+ return found if found
12
+
13
+ project = {
14
+ name: repo[:name],
15
+ repo_provider: user.provider,
16
+ repo_owner: repo[:owner],
17
+ repo_name: repo[:name],
18
+ repo_id: repo[:id],
19
+ }
20
+
21
+ team = user.teams.find_by(provider_id: repo[:owner_id])
22
+ if team
23
+ project[:team] = team
24
+ else
25
+ project[:user] = user
26
+ end
27
+
28
+ create!(project)
29
+ end
30
+
31
+ def status
32
+ jobs.where(branch: 'master').last.try(:status)
33
+ end
34
+
35
+ def branches
36
+ jobs.group(:branch).limit(20).pluck(:branch).map do |branch|
37
+ jobs.where(branch: branch).first
38
+ end
39
+ end
40
+
41
+ def owner
42
+ user || team
43
+ end
44
+
45
+ end
@@ -0,0 +1,11 @@
1
+ class Team < ApplicationRecord
2
+ has_many :user_teams
3
+ has_many :users, through: :user_teams
4
+ has_many :projects
5
+ has_many :pipelines, through: :projects
6
+
7
+ def client
8
+ users.first.client
9
+ end
10
+
11
+ end
@@ -0,0 +1,43 @@
1
+ class User < ApplicationRecord
2
+ has_many :user_teams
3
+ has_many :teams, through: :user_teams
4
+ has_many :users, through: :teams
5
+ has_many :projects
6
+ has_many :pipelines, through: :projects
7
+ has_many :jobs, through: :pipelines
8
+
9
+ def self.from_omniauth(auth)
10
+ new_user = false
11
+ user = User.find_by(provider: auth.provider, provider_id: auth.uid)
12
+ unless user.present?
13
+ user = User.create!(provider: auth.provider, provider_id: auth.uid)
14
+ new_user = true
15
+ end
16
+
17
+ user.username = auth.info.nickname
18
+ user.email = auth.info.email
19
+ user.name = auth.info.name
20
+ user.token = auth.credentials.token
21
+ user.refresh_token = auth.credentials.refresh_token
22
+ user.save!
23
+
24
+ user.sync if new_user
25
+
26
+ user
27
+ end
28
+
29
+ def sync
30
+ client.teams.each do |team|
31
+ teams.create!(provider: provider, provider_id: team[:id], name: team[:name])
32
+ end
33
+ end
34
+
35
+ def client
36
+ if provider == 'github'
37
+ GithubClient.new(username, token)
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,4 @@
1
+ class UserTeam < ApplicationRecord
2
+ belongs_to :user
3
+ belongs_to :team
4
+ end
@@ -0,0 +1,12 @@
1
+ class JobSerializer < ActiveModel::Serializer
2
+ has_one :pipeline, if: :show_all?
3
+ has_one :project, if: :show_all?
4
+
5
+ attributes :id, :pipeline_id, :project_id, :pipeline_stage, :key, :status, :commit, :branch, :worker, :created_at, :updated_at
6
+ attribute :spec, if: :show_all?
7
+
8
+ def show_all?
9
+ instance_options[:show_all]
10
+ end
11
+
12
+ end
@@ -0,0 +1,3 @@
1
+ class PipelineSerializer < ActiveModel::Serializer
2
+ attributes Pipeline.column_names
3
+ end
@@ -0,0 +1,4 @@
1
+ class ProjectSerializer < ActiveModel::Serializer
2
+ attributes Project.column_names
3
+ attributes :status
4
+ end
@@ -0,0 +1,3 @@
1
+ class TeamSerializer < ActiveModel::Serializer
2
+ attributes Team.column_names
3
+ end
@@ -0,0 +1,3 @@
1
+ class UserSerializer < ActiveModel::Serializer
2
+ attributes :id, :name, :provider, :provider_id, :email, :username, :created_at, :updated_at
3
+ end
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,37 @@
1
+ require_relative 'boot'
2
+
3
+ require "rails"
4
+ # Pick the frameworks you want:
5
+ require "active_model/railtie"
6
+ require "active_job/railtie"
7
+ require "active_record/railtie"
8
+ require "action_controller/railtie"
9
+ require "action_mailer/railtie"
10
+ require "action_view/railtie"
11
+ require "action_cable/engine"
12
+ # require "sprockets/railtie"
13
+ require "rails/test_unit/railtie"
14
+
15
+ require "./lib/jobs_api"
16
+
17
+ # Require the gems listed in Gemfile, including any gems
18
+ # you've limited to :test, :development, or :production.
19
+ Bundler.require(*Rails.groups)
20
+
21
+ module JobsApi
22
+ class Application < Rails::Application
23
+ # Settings in config/environments/* take precedence over those specified here.
24
+ # Application configuration should go into files in config/initializers
25
+ # -- all .rb files in that directory are automatically loaded.
26
+
27
+ # Only loads a smaller set of middleware suitable for API only apps.
28
+ # Middleware like session, flash, cookies can be added back manually.
29
+ # Skip views, helpers and assets when generating a new resource.
30
+ config.api_only = true
31
+
32
+ # middleware required by omniauth
33
+ config.middleware.use Rack::Session::Cookie, secret: ENV['SECRET_KEY_BASE'] || 'secret'
34
+
35
+ config.active_job.queue_adapter = :delayed_job
36
+ end
37
+ end