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