jobs-api 0.1.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 (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