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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: faf9a8ab0e8465b9b83f9f5bb3244afd9727a86f
|
4
|
+
data.tar.gz: 1a88b1bf4cc7990fb42b33e22b36d2b13b742f47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff226b858ccdb44baf0c63fe6966eb14e13acb6075c95c2ff200c443e99b47171a4c769fa264c18e6a7fb4aa00ab87b56d816be706276d354c6656d73e3cfa55
|
7
|
+
data.tar.gz: 8e6350809e1d974eb47e6f25508518d2722dcf114ab72550bebbf596fed24636cba7a5dd9f8471ac32ab717fe44adebce5f4ee0059f04c4c27fa5e8804ad74a6
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
Endpoints
|
27
|
+
|
28
|
+
Jobs:
|
29
|
+
|
30
|
+
GET `/current_user` => current user
|
31
|
+
GET `/users` => list all users
|
32
|
+
GET `/teams` => list all teams
|
33
|
+
GET `/teams/:id/users` => all users that can access a team
|
34
|
+
|
35
|
+
POST `/jobs` => create an untracked job
|
36
|
+
GET `/jobs/pop` => pop a job from the queue
|
37
|
+
PUT `/jobs/:id/status` => update a job's status
|
38
|
+
|
39
|
+
POST `/events` => enqueue an event for processing
|
40
|
+
|
41
|
+
GET `projects` => retrieve a list of all projects. by team, by user or all
|
42
|
+
GET `projects/:id` => get a single project
|
43
|
+
DEL `projects/:id` => delete a project
|
44
|
+
PUT `projects/:id` => update a project
|
45
|
+
POST `projects` => create a freestyle project
|
46
|
+
GET `projects/:id/branches` => retrieve a list of branches with a build status for a project, show all branches option
|
47
|
+
GET `projects/:id/jobs` => retrieve a list of the latest jobs for a project
|
48
|
+
|
49
|
+
GET `/pipelines` => list all pipeline definitions by registry with latest status.
|
50
|
+
GET `/pipelines/:name/instances` => list all pipeline instances for this definition
|
51
|
+
GET `/pipelines/:name/instances/:id` => get a specific pipeline, more information, status and jobs
|
52
|
+
|
53
|
+
GET `/libraries` => list all installed libraries known
|
54
|
+
POST `/libraries` => install a library
|
55
|
+
DEL `/libraries/:name` => uninstall a library
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class BitbucketClient
|
2
|
+
|
3
|
+
def initialize(token)
|
4
|
+
@token = token
|
5
|
+
end
|
6
|
+
|
7
|
+
def teams
|
8
|
+
req(:get, '2.0/user/orgs').map do |org|
|
9
|
+
{id: org['id'], name: org['name']}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def req(method, url, params={})
|
14
|
+
res = conn.send(method, url, params)
|
15
|
+
JSON.parse(res.body)
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def conn
|
23
|
+
@github ||= Faraday.new(url: 'https://api.bitbucket.org/') do |faraday|
|
24
|
+
faraday.request :url_encoded # form-encode POST params
|
25
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
26
|
+
faraday.response :logger # log requests to STDOUT
|
27
|
+
faraday.headers['Authorization'] = "Bearer #{@token}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class GithubClient
|
2
|
+
|
3
|
+
def initialize(username, token)
|
4
|
+
@token = token
|
5
|
+
@username = username
|
6
|
+
end
|
7
|
+
|
8
|
+
def teams
|
9
|
+
req(:get, 'user/orgs').map do |org|
|
10
|
+
{id: org['id'], name: org['login']}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def repo(repo)
|
15
|
+
res = req(:get, "/repos/#{@username}/#{repo}")
|
16
|
+
|
17
|
+
if res['source']
|
18
|
+
{
|
19
|
+
id: res['source']['id'],
|
20
|
+
name: res['source']['name'],
|
21
|
+
owner: res['source']['owner']['login'],
|
22
|
+
owner_id: res['source']['owner']['id'],
|
23
|
+
}
|
24
|
+
else
|
25
|
+
{
|
26
|
+
id: res['id'],
|
27
|
+
name: res['name'],
|
28
|
+
owner: res['owner']['login'],
|
29
|
+
owner_id: res['owner']['id'],
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def latest_commit(repo)
|
35
|
+
res = req(:get, "/repos/#{@username}/#{repo}/git/refs")
|
36
|
+
return nil if res.length == 0
|
37
|
+
|
38
|
+
res.each do |ref|
|
39
|
+
if ref['ref'].split('/')[1] == 'heads'
|
40
|
+
return {
|
41
|
+
sha: ref['object']['sha'],
|
42
|
+
branch: ref['ref'].split('refs/heads/')[-1],
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def branches(repo)
|
51
|
+
req_cached(:get, "/repos/#{@username}/#{repo}/branches").map do |branch|
|
52
|
+
{name: branch['name'], commit: branch['commit']['sha']}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def file(repo, path)
|
57
|
+
res = req(:get, "/repos/#{@username}/#{repo}/contents/#{path}")
|
58
|
+
Base64.decode64(res['content']) if res['content']
|
59
|
+
end
|
60
|
+
|
61
|
+
def register_webhook(repo)
|
62
|
+
post_json("/repos/#{@username}/#{repo}/hooks", {
|
63
|
+
name: 'web',
|
64
|
+
active: true,
|
65
|
+
events: %w(push pull_request),
|
66
|
+
config: {
|
67
|
+
url: "#{Config.api_root_url}/hooks/github",
|
68
|
+
content_type: 'json'
|
69
|
+
}
|
70
|
+
})
|
71
|
+
end
|
72
|
+
|
73
|
+
def req(method, url, params={})
|
74
|
+
puts "#{method} https://api.github.com/#{url}"
|
75
|
+
res = conn.send(method, url, params)
|
76
|
+
JSON.parse(res.body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def post_json(url, payload)
|
80
|
+
res = conn.post do |req|
|
81
|
+
req.url url
|
82
|
+
req.headers['Content-Type'] = 'application/json'
|
83
|
+
req.body = JSON.generate(payload)
|
84
|
+
end
|
85
|
+
JSON.parse(res.body)
|
86
|
+
end
|
87
|
+
|
88
|
+
def req_cached(method, url, params={})
|
89
|
+
Rails.cache.fetch("github-api:#{method}.#{url}", expires_in: 1.day) { req(method, url, params) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def conn
|
93
|
+
@github ||= Faraday.new(url: 'https://api.github.com/') do |faraday|
|
94
|
+
faraday.request :url_encoded # form-encode POST params
|
95
|
+
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
|
96
|
+
faraday.headers['Authorization'] = "token #{@token}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class ApplicationController < ActionController::API
|
2
|
+
serialization_scope :view_context
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def current_user
|
7
|
+
# @current_user ||= User.find_by(id: session[:user_id])
|
8
|
+
@current_user ||= User.first
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_entity
|
12
|
+
@current_entity ||= begin
|
13
|
+
if params[:team_id]
|
14
|
+
current_user.teams.find(params[:team_id])
|
15
|
+
elsif params[:user_id]
|
16
|
+
current_user.users.find(params[:user_id])
|
17
|
+
else
|
18
|
+
current_user
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class HooksController < ApplicationController
|
2
|
+
|
3
|
+
def github
|
4
|
+
|
5
|
+
event = {}
|
6
|
+
if params[:ref]
|
7
|
+
|
8
|
+
event[:name] = 'git:push'
|
9
|
+
if params[:ref].split('/')[1] == 'heads'
|
10
|
+
event[:branch] = params[:ref].split('refs/heads/')[-1]
|
11
|
+
end
|
12
|
+
|
13
|
+
event[:commit] = params[:after]
|
14
|
+
event[:project_id] = Project.find_by(repo_id: params[:repository][:id], repo_provider: 'github').try(:id)
|
15
|
+
|
16
|
+
elsif params[:pull_request]
|
17
|
+
|
18
|
+
event[:name] = 'git:pull_request'
|
19
|
+
event[:commit] = params[:pull_request][:head][:sha]
|
20
|
+
event[:branch] = params[:pull_request][:head][:ref]
|
21
|
+
event[:project_id] = Project.find_by(repo_id: params[:pull_request][:head][:repo][:id], repo_provider: 'github').try(:id)
|
22
|
+
event[:pull_request] = true
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
evt = Event.new(event)
|
27
|
+
evt.execute
|
28
|
+
|
29
|
+
render plain: 'OK'
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class JobsController < ApplicationController
|
2
|
+
|
3
|
+
def pop
|
4
|
+
render json: Job.pop
|
5
|
+
end
|
6
|
+
|
7
|
+
def index
|
8
|
+
render json: current_entity.jobs
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
render json: current_entity.jobs.find(params[:id]), show_all: true
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_status
|
16
|
+
Job.find_by!(key: params[:id]).update!(status: params[:status])
|
17
|
+
render plain: 'OK'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class ProjectsController < ApplicationController
|
2
|
+
|
3
|
+
def index
|
4
|
+
render json: current_user.projects
|
5
|
+
end
|
6
|
+
|
7
|
+
def create
|
8
|
+
render json: Project.add_project(current_user, params[:name])
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
render json: current_user.projects.find(params[:id])
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
project = current_user.projects.find(params[:id])
|
17
|
+
project.update!(safe_params)
|
18
|
+
render json: project
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy
|
22
|
+
render json: current_user.projects.find(params[:id]).destroy!
|
23
|
+
end
|
24
|
+
|
25
|
+
def jobs
|
26
|
+
render json: current_user.projects.find(params[:project_id]).jobs
|
27
|
+
end
|
28
|
+
|
29
|
+
def branches
|
30
|
+
render json: current_user.projects.find(params[:project_id]).branches, root: :jobs
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def safe_params
|
36
|
+
params.require(:project).permit!
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class SessionsController < ApplicationController
|
2
|
+
|
3
|
+
def create
|
4
|
+
user = User.from_omniauth(request.env['omniauth.auth'])
|
5
|
+
session[:user_id] = user.id
|
6
|
+
redirect_to Config.ui_root_url
|
7
|
+
end
|
8
|
+
|
9
|
+
def destroy
|
10
|
+
session[:user_id] = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def failure
|
14
|
+
redirect_to Config.ui_root_url, failed: true
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class PipelineRunnerJob < ApplicationJob
|
2
|
+
attr_accessor :event, :pipeline, :definition
|
3
|
+
|
4
|
+
queue_as :default
|
5
|
+
|
6
|
+
# receives a pipeline as it's argument
|
7
|
+
def perform(pipeline_object, event=nil, definition_name=nil)
|
8
|
+
|
9
|
+
if event
|
10
|
+
event = Event.new(event)
|
11
|
+
# this is the starting point for the pipeline if the event is present
|
12
|
+
raise Exception.new('Wrong code path') if pipeline_object.present? || definition_name.nil?
|
13
|
+
|
14
|
+
unless definition_name.constantize.trigger_when?(event)
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
pipeline_object = Pipeline.create!(
|
19
|
+
project: event.project,
|
20
|
+
status: 'STARTING',
|
21
|
+
event: event.data,
|
22
|
+
definition: definition_name,
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
self.pipeline = pipeline_object
|
27
|
+
self.definition = pipeline.definition_class.new(pipeline, event)
|
28
|
+
self.event = event || pipeline.event
|
29
|
+
|
30
|
+
if pipeline.status?(:failed) || pipeline.status?(:complete)
|
31
|
+
# pipeline has already been marked as failing or complete
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
if last_stage_failed?
|
36
|
+
# fail the pipeline if it's failing
|
37
|
+
update_status :failed
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
if last_stage_pending?
|
42
|
+
# nothing to do here, still waiting on some methods to come through
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
# if we've gotten here all the checks are passing and we can move onto the next stage
|
47
|
+
|
48
|
+
unless next_stage.present?
|
49
|
+
# no next stage, pipeline is complete
|
50
|
+
complete_pipeline
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
# run the next stage
|
55
|
+
pipeline.update!(stage: next_stage, status: 'PENDING')
|
56
|
+
execute(next_stage)
|
57
|
+
|
58
|
+
unless last_stage_pending?
|
59
|
+
# no jobs were created during the last stage, therefore everything is done!
|
60
|
+
complete_pipeline
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute(this_stage)
|
65
|
+
begin
|
66
|
+
definition.send(this_stage)
|
67
|
+
rescue Exception => e
|
68
|
+
pipeline.update!(status: 'ERROR', error: e.message)
|
69
|
+
raise e
|
70
|
+
ensure
|
71
|
+
pipeline.save
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def complete_pipeline
|
76
|
+
definition.class.ensure_stages.each do |ensured_stage|
|
77
|
+
# will fail the job if an error is raised
|
78
|
+
execute(ensured_stage)
|
79
|
+
end
|
80
|
+
update_status :complete
|
81
|
+
end
|
82
|
+
|
83
|
+
def next_stage
|
84
|
+
@next_stage ||= begin
|
85
|
+
if pipeline.stage
|
86
|
+
cur_idx = definition.class.stages.find_index { |st| st == pipeline.stage.downcase.to_sym }
|
87
|
+
definition.class.stages[cur_idx + 1]
|
88
|
+
else
|
89
|
+
definition.class.stages[0]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def update_status(status)
|
95
|
+
pipeline.update!(status: status.to_s.upcase)
|
96
|
+
end
|
97
|
+
|
98
|
+
def last_stage_failed?
|
99
|
+
Job.where('pipeline_id = ? AND pipeline_stage = ? AND (status = "FAILED" OR status = "CANCELLED")', pipeline.id, pipeline.stage).exists?
|
100
|
+
end
|
101
|
+
|
102
|
+
def last_stage_pending?
|
103
|
+
Job.where('pipeline_id = ? AND pipeline_stage = ? AND (status = "QUEUED" OR status = "PENDING")', pipeline.id, pipeline.stage).exists?
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|