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