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
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,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative 'config/application'
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -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,12 @@
1
+ class EventsController < ApplicationController
2
+
3
+ def create
4
+ event = Event.new(
5
+ project_id: params[:project_id],
6
+ name: params[:name],
7
+ )
8
+ event.execute
9
+ render json: event
10
+ end
11
+
12
+ 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,11 @@
1
+ class PipelinesController < ApplicationController
2
+
3
+ def index
4
+ render json: current_entity.pipelines
5
+ end
6
+
7
+ def show
8
+ render json: current_entity.pipelines.find(params[:id])
9
+ end
10
+
11
+ 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,15 @@
1
+ class UsersController < ApplicationController
2
+
3
+ def current
4
+ render json: current_user
5
+ end
6
+
7
+ def index
8
+ render json: current_entity.users
9
+ end
10
+
11
+ def teams
12
+ render json: current_user.teams
13
+ end
14
+
15
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ 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