jobs-api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +55 -0
- data/Rakefile +6 -0
- data/app/channels/application_cable/channel.rb +4 -0
- data/app/channels/application_cable/connection.rb +4 -0
- data/app/clients/bitbucket_client.rb +31 -0
- data/app/clients/github_client.rb +100 -0
- data/app/controllers/application_controller.rb +23 -0
- data/app/controllers/events_controller.rb +12 -0
- data/app/controllers/hooks_controller.rb +32 -0
- data/app/controllers/jobs_controller.rb +20 -0
- data/app/controllers/pipelines_controller.rb +11 -0
- data/app/controllers/projects_controller.rb +39 -0
- data/app/controllers/sessions_controller.rb +17 -0
- data/app/controllers/users_controller.rb +15 -0
- data/app/jobs/application_job.rb +2 -0
- data/app/jobs/pipeline_runner_job.rb +106 -0
- data/app/mailers/application_mailer.rb +4 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/basic_pipeline.rb +73 -0
- data/app/models/event.rb +24 -0
- data/app/models/job.rb +27 -0
- data/app/models/pipeline.rb +21 -0
- data/app/models/pipeline_definition.rb +98 -0
- data/app/models/project.rb +45 -0
- data/app/models/team.rb +11 -0
- data/app/models/user.rb +43 -0
- data/app/models/user_team.rb +4 -0
- data/app/serializers/job_serializer.rb +12 -0
- data/app/serializers/pipeline_serializer.rb +3 -0
- data/app/serializers/project_serializer.rb +4 -0
- data/app/serializers/team_serializer.rb +3 -0
- data/app/serializers/user_serializer.rb +3 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/config/application.rb +37 -0
- data/config/boot.rb +3 -0
- data/config/cable.yml +9 -0
- data/config/database.yml +16 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +47 -0
- data/config/environments/production.rb +78 -0
- data/config/environments/test.rb +42 -0
- data/config/initializers/app_setup.rb +46 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/filter_parameter_logging.rb +4 -0
- data/config/initializers/inflections.rb +16 -0
- data/config/initializers/mime_types.rb +4 -0
- data/config/initializers/new_framework_defaults.rb +18 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/en.yml +23 -0
- data/config/puma.rb +47 -0
- data/config/routes.rb +23 -0
- data/config/secrets.yml +22 -0
- data/config/spring.rb +6 -0
- data/db/migrate/20161107155702_create_projects.rb +15 -0
- data/db/migrate/20161108233030_create_pipelines.rb +15 -0
- data/db/migrate/20161108233304_create_jobs.rb +16 -0
- data/db/migrate/20161109203929_create_users.rb +15 -0
- data/db/migrate/20161109203949_create_teams.rb +11 -0
- data/db/migrate/20161109204011_create_user_teams.rb +10 -0
- data/db/migrate/20161109205639_add_references_to_projects.rb +6 -0
- data/db/migrate/20161110154413_create_delayed_jobs.rb +22 -0
- data/db/schema.rb +111 -0
- data/db/seeds.rb +7 -0
- data/lib/jobs_api.rb +13 -0
- metadata +255 -0
@@ -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
|
data/app/models/event.rb
ADDED
@@ -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
|
data/app/models/team.rb
ADDED
data/app/models/user.rb
ADDED
@@ -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,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 @@
|
|
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
|