cyclid 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eff5a90aeccc36b649701ec2ec970f4dc1c11e9d
4
- data.tar.gz: 103987b807c2b04930a68d96787d64cb3864b359
3
+ metadata.gz: 33d614a9893bc411011e194512153b90d56082a0
4
+ data.tar.gz: 0d607fd8d0892da8031255600cbd6703b42ffc5c
5
5
  SHA512:
6
- metadata.gz: 802e3bce3fdd10e988997c282f635dda5cb81715bf6e0c6755bfc32ef1ef5491a424caa8d0cd1b03fee9befe18a5cd825e5ad053725b726070322e5d03dc19f4
7
- data.tar.gz: 2562e60cf0591decaba4bb01ac515072695b3834cfd3c18c6589794655ea9cf0451d575bbbc006ae3f160d5a9e2fc6504ad2fa2b7c209fe528d9e54943518fda
6
+ metadata.gz: 90e8b58bb414d9116671741ce28d67106ac2075bda3a129a9a8775a555e30921d0455fa0c6ab10f50387952cdfb27e48c33ce9ea2f172c3384f90c954d0e47e2
7
+ data.tar.gz: d9b11717018b8f45f690fe2dd553603cfbaa59967cf65cb7e7ea1c9384f0f528e762440a1397c3eacf44c59184bc693ec17be594b683244a87d0a3b78c343af4
@@ -80,7 +80,7 @@ module Cyclid
80
80
  register Organizations::Jobs
81
81
  end
82
82
 
83
- namespace '/configs/:type' do
83
+ namespace '/configs' do
84
84
  register Organizations::Configs
85
85
  end
86
86
 
@@ -25,6 +25,14 @@ module Cyclid
25
25
  # rubocop:disable Metrics/LineLength
26
26
  # @!group Organizations
27
27
 
28
+ # @!method get_organizations_organization_configs
29
+ # @overload GET /organizations/:organization/configs
30
+ # @macro rest
31
+ # @param [String] organization Name of the organization.
32
+ # Get the list of plugins which support per-organization configurations.
33
+ # @return The list of plugins.
34
+ # @return [404] The organization or plugin does not exist.
35
+
28
36
  # @!method get_organizations_organization_configs_type_plugin
29
37
  # @overload GET /organizations/:organization/configs/:type/:plugin
30
38
  # @macro rest
@@ -63,8 +71,21 @@ module Cyclid
63
71
  include Errors::HTTPErrors
64
72
  include Constants::JobStatus
65
73
 
74
+ # Return a list of plugins which have configs
75
+ app.get do
76
+ authorized_for!(params[:name], Operations::READ)
77
+
78
+ configs = []
79
+ Cyclid.plugins.all.each do |plugin|
80
+ configs << { type: plugin.human_name, name: plugin.name } \
81
+ if plugin.config?
82
+ end
83
+
84
+ return configs.to_json
85
+ end
86
+
66
87
  # Get the current configuration for the given plugin.
67
- app.get '/:plugin' do
88
+ app.get '/:type/:plugin' do
68
89
  authorized_for!(params[:name], Operations::READ)
69
90
 
70
91
  org = Organization.find_by(name: params[:name])
@@ -96,7 +117,7 @@ module Cyclid
96
117
  end
97
118
 
98
119
  # Update the plugin configuration
99
- app.put '/:plugin' do
120
+ app.put '/:type/:plugin' do
100
121
  authorized_for!(params[:name], Operations::ADMIN)
101
122
 
102
123
  payload = parse_request_body
@@ -89,7 +89,7 @@ module Cyclid
89
89
 
90
90
  begin
91
91
  @builder.release(@transport, @build_host) if @build_host
92
- @transport.close if @transport
92
+ @transport&.close
93
93
  rescue ::Net::SSH::Disconnect # rubocop:disable Lint/HandleExceptions
94
94
  # Ignored
95
95
  end
@@ -87,7 +87,7 @@ module Cyclid
87
87
  end
88
88
 
89
89
  # Write to web socket
90
- @websocket.write data if @websocket
90
+ @websocket&.write data
91
91
  end
92
92
 
93
93
  # Non-destructively read any new data from the buffer
@@ -40,6 +40,11 @@ module Cyclid
40
40
  Cyclid.plugins.register(self)
41
41
  end
42
42
 
43
+ # Does this plugin support configuration data?
44
+ def config?
45
+ false
46
+ end
47
+
43
48
  # Get the configuration for the given org
44
49
  def get_config(org)
45
50
  # If the organization was passed by name, convert it into an Organization object
@@ -123,3 +128,8 @@ module Cyclid
123
128
  end
124
129
 
125
130
  require_rel 'plugins/*.rb'
131
+
132
+ # Load all plugins from Gems
133
+ Gem.find_files('cyclid/plugins/**/*.rb').each do |path|
134
+ require path
135
+ end
@@ -157,6 +157,11 @@ module Cyclid
157
157
 
158
158
  # Static methods for handling plugin config data
159
159
  class << self
160
+ # This plugin has configuration data
161
+ def config?
162
+ true
163
+ end
164
+
160
165
  # Update the plugin configuration
161
166
  def update_config(current, new)
162
167
  current.merge! new
@@ -194,7 +199,7 @@ module Cyclid
194
199
  description: 'SMTP server username',
195
200
  default: nil }
196
201
  schema << { name: 'password',
197
- type: 'string',
202
+ type: 'password',
198
203
  description: 'SMTP server password',
199
204
  default: nil }
200
205
 
@@ -99,6 +99,11 @@ module Cyclid
99
99
 
100
100
  # Static methods for handling plugin config data
101
101
  class << self
102
+ # This plugin has configuration data
103
+ def config?
104
+ true
105
+ end
106
+
102
107
  # Update the plugin configuration
103
108
  def update_config(current, new)
104
109
  current.merge! new
@@ -26,64 +26,43 @@ module Cyclid
26
26
  class Controller < Module
27
27
  attr_reader :plugin_methods
28
28
 
29
- def initialize(methods = nil)
29
+ def initialize(methods = nil, custom_routes = [])
30
30
  @plugin_methods = methods
31
+
32
+ # Provide default routes for the four basic HTTP verbs; for most
33
+ # plugins they only need to implement the matching method for the
34
+ # verb(s) they'll respond too.
35
+ default_routes = [{ verb: :get, path: nil, func: 'get' },
36
+ { verb: :post, path: nil, func: 'post' },
37
+ { verb: :put, path: nil, func: 'put' },
38
+ { verb: :delete, path: nil, func: 'delete' }]
39
+
40
+ # ...but more complex plugins can add additional routes with their
41
+ # own paths & methods, if required.
42
+ @routes = default_routes.concat custom_routes
31
43
  end
32
44
 
33
45
  # Sinatra callback
34
46
  def registered(app)
35
47
  include Errors::HTTPErrors
36
48
 
37
- app.get do
38
- Cyclid.logger.debug 'ApiExtension::Controller::get'
39
-
40
- org = Organization.find_by(name: params[:name])
41
- halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
42
- if org.nil?
43
-
44
- config = controller_plugin.get_config(org)
45
-
46
- get(http_headers(request.env), config['config'])
47
- end
48
-
49
- app.post do
50
- Cyclid.logger.debug 'ApiExtension::Controller::post'
49
+ @routes.each do |route|
50
+ verb = route[:verb]
51
+ path = route[:path]
52
+ func = route[:func]
51
53
 
52
- payload = parse_request_body
54
+ app.send(verb, path) do
55
+ Cyclid.logger.debug "ApiExtension::Controller::#{verb} #{path}"
53
56
 
54
- org = Organization.find_by(name: params[:name])
55
- halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
56
- if org.nil?
57
+ org = Organization.find_by(name: params[:name])
58
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
59
+ if org.nil?
57
60
 
58
- config = controller_plugin.get_config(org)
61
+ config = controller_plugin.get_config(org)
59
62
 
60
- post(payload, http_headers(request.env), config['config'])
61
- end
62
-
63
- app.put do
64
- Cyclid.logger.debug 'ApiExtension::Controller::put'
65
-
66
- payload = parse_request_body
67
-
68
- org = Organization.find_by(name: params[:name])
69
- halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
70
- if org.nil?
71
-
72
- config = controller_plugin.get_config(org)
73
-
74
- put(payload, http_headers(request.env), config['config'])
75
- end
76
-
77
- app.delete do
78
- Cyclid.logger.debug 'ApiExtension::Controller::delete'
79
-
80
- org = Organization.find_by(name: params[:name])
81
- halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
82
- if org.nil?
83
-
84
- config = controller_plugin.get_config(org)
85
-
86
- delete(http_headers(request.env), config['config'])
63
+ meth = method(func)
64
+ meth.call(http_headers(request.env), config['config'])
65
+ end
87
66
  end
88
67
 
89
68
  app.helpers do
@@ -107,13 +86,13 @@ module Cyclid
107
86
  end
108
87
 
109
88
  # POST callback
110
- def post(_data, _headers, _config)
89
+ def post(_headers, _config)
111
90
  authorize('post')
112
91
  return_failure(405, 'not implemented')
113
92
  end
114
93
 
115
94
  # PUT callback
116
- def put(_data, _headers, _config)
95
+ def put(_headers, _config)
117
96
  authorize('put')
118
97
  return_failure(405, 'not implemented')
119
98
  end
@@ -173,6 +152,20 @@ module Cyclid
173
152
 
174
153
  return http_headers
175
154
  end
155
+
156
+ # Return the current organization name
157
+ def organization_name
158
+ params[:name]
159
+ end
160
+
161
+ # Find & return the Organization model
162
+ def retrieve_organization(name = nil)
163
+ name ||= organization_name
164
+ org = Organization.find_by(name: name)
165
+ halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
166
+ if org.nil?
167
+ return org
168
+ end
176
169
  end
177
170
  end
178
171
 
@@ -14,7 +14,6 @@
14
14
  # limitations under the License.
15
15
 
16
16
  require_rel 'github/methods'
17
- require_rel 'github/status'
18
17
  require_rel 'github/callback'
19
18
 
20
19
  # Top level module for the core Cyclid code.
@@ -25,12 +24,20 @@ module Cyclid
25
24
  module Plugins
26
25
  # API extension for Github hooks
27
26
  class Github < Api
28
- # Return an instance of the Github API controller
29
- def self.controller
30
- return ApiExtension::Controller.new(ApiExtension::GithubMethods)
31
- end
32
-
33
27
  class << self
28
+ # Return an instance of the Github API controller
29
+ def controller
30
+ routes = [{ verb: :get, path: '/oauth/request', func: 'oauth_request' },
31
+ { verb: :get, path: '/oauth/callback', func: 'oauth_callback' }]
32
+
33
+ return ApiExtension::Controller.new(ApiExtension::GithubMethods, routes)
34
+ end
35
+
36
+ # This plugin has configuration data
37
+ def config?
38
+ true
39
+ end
40
+
34
41
  # Merge the given config into the current config & validate
35
42
  def update_config(config, new)
36
43
  Cyclid.logger.debug "config=#{config} new=#{new}"
@@ -70,11 +77,14 @@ module Cyclid
70
77
  config['repository_tokens'] = merged
71
78
  end
72
79
 
73
- if new.key? 'hmac_secret'
74
- Cyclid.logger.debug 'updating HMAC secret'
75
- config['hmac_secret'] = new['hmac_secret']
80
+ if new.key? 'oauth_token'
81
+ Cyclid.logger.debug 'updating OAuth token'
82
+ config['oauth_token'] = new['oauth_token']
76
83
  end
77
84
 
85
+ # Remove any old keys
86
+ config.delete 'hmac_secret' if config.key? 'hmac_secret'
87
+
78
88
  return config
79
89
  end
80
90
 
@@ -82,7 +92,7 @@ module Cyclid
82
92
  def default_config
83
93
  config = {}
84
94
  config['repository_tokens'] = []
85
- config['hmac_secret'] = nil
95
+ config['oauth_token'] = nil
86
96
 
87
97
  return config
88
98
  end
@@ -92,13 +102,16 @@ module Cyclid
92
102
  schema = []
93
103
  schema << { name: 'repository_tokens',
94
104
  type: 'hash-list',
95
- description: 'Repository OAuth tokens',
105
+ description: 'Individual repository personal OAuth tokens',
96
106
  default: [] }
97
- schema << { name: 'hmac_secret',
98
- type: 'string',
99
- description: 'Github HMAC signing secret',
107
+ schema << { name: 'oauth_token',
108
+ type: 'password',
109
+ description: 'Organization Github OAuth token',
100
110
  default: nil }
101
-
111
+ schema << { name: 'oauth_request',
112
+ type: 'link-relative',
113
+ description: 'Authorize with Github',
114
+ default: '/oauth/request' }
102
115
  return schema
103
116
  end
104
117
  end
@@ -0,0 +1,62 @@
1
+ Github API plugin
2
+ ---
3
+
4
+ This is the Github API plugin for Cyclid. It adds API end points which support Github OAuth authentication and webhook events:
5
+
6
+ | Verb | Path | Notes |
7
+ | --- | --- | --- |
8
+ | **GET** | / |Github webhook callback. |
9
+ | **GET** | /oauth/request | Start the OAuth authorization process. |
10
+ | **GET** | /oauth/callback | The OUth authorization calback. |
11
+
12
+ # OAuth
13
+
14
+ Initiate the Github OAuth "Web" flow with a GET to the /oauth/request endpoint. This will cause a redirect to Github where the user can authorise the OAuth request.
15
+
16
+ When the request has been authorised the user will be redirected back to the /oauth/callback endpoint, which in turn will then redirect to the Cyclid UI.
17
+
18
+ # Server Configuration
19
+
20
+ The server-side configuration for this plugin is:
21
+
22
+ ```
23
+ github:
24
+ client_id: <Github OAuth client ID>
25
+ client_secret: <Github OAuth client secret>
26
+ api_url: <This API>
27
+ ui_url: <Cyclid UI instance attached to this API>
28
+ ```
29
+ The `client_id` and `client_secret` can be obtained from Github when you [register Cyclid as an application](https://github.com/settings/applications/new) and should be provided here to enable OAuth authorization flow.
30
+
31
+ The `api_url` should be the publically resolvable name of the Cyclid API server, and the `ui_url` should be the publically resolvable name of a Cyclid UI server which is configured to use the API server. These URLs are both used to generate appropriate redirects during the OAuth authorization process.
32
+
33
+ Note that the `api_url` provided must match the "Authorization callback URL" setting you provide to Github when you register Cyclid as an application; this includes both the scheme (http: or https:) and any port number (Cyclid by default uses port 8361). So for example:
34
+
35
+ ```
36
+ api_url: https://cyclid.example.com:8361
37
+ ```
38
+
39
+ # Plugin configuration
40
+
41
+ The per-organization plugin configuration is:
42
+
43
+ ```
44
+ Individual repository personal OAuth tokens
45
+ None
46
+ Organization Github OAuth token: abcdef123456789
47
+ ```
48
+
49
+ The individual repository OAuth tokens can be used to provide a privately generated OAuth token for a single repository. OAuth tokens are matched by the repository URL.
50
+
51
+ The organization OAuth token is the token normally generated via. the normal Github OAuth authorization process. This is the token which will be used by default.
52
+
53
+ # Webhook events
54
+
55
+ The plugin currently supports the following webhook events:
56
+
57
+ * ping
58
+ * status
59
+ * pull_request
60
+ * push
61
+
62
+ The `ping` and `status` events return immediatly with a 200 response. The `pull_request` and `push` events may cause a new job to be submitted, if a Cyclid job file can be found in the repository which generated the event.
@@ -13,6 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ require 'octokit'
17
+
16
18
  # Top level module for the core Cyclid code.
17
19
  module Cyclid
18
20
  # Module for the Cyclid API
@@ -24,9 +26,16 @@ module Cyclid
24
26
  # Notifier callback for Github. Updates the external Github Pull
25
27
  # Request status as the job progresses.
26
28
  class GithubCallback < Plugins::Notifier::Callback
27
- def initialize(statuses, auth_token)
28
- @statuses = statuses
29
+ def initialize(auth_token, repo, sha, linkback_url)
29
30
  @auth_token = auth_token
31
+ @repo = repo
32
+ @sha = sha
33
+ @linkback_url = linkback_url
34
+ end
35
+
36
+ # Return or create an Octokit client
37
+ def client
38
+ @client ||= Octokit::Client.new(access_token: @auth_token)
30
39
  end
31
40
 
32
41
  # Job status has changed
@@ -45,7 +54,10 @@ module Cyclid
45
54
  return false
46
55
  end
47
56
 
48
- GithubStatus.set_status(@statuses, @auth_token, state, message)
57
+ target_url = "#{@linkback_url}/job/#{job_id}"
58
+ client.create_status(@repo, @sha, state, context: 'Cyclid',
59
+ target_url: target_url,
60
+ description: message)
49
61
  end
50
62
 
51
63
  # Job has completed
@@ -57,7 +69,10 @@ module Cyclid
57
69
  state = 'failure'
58
70
  message = "Job ##{job_id} failed."
59
71
  end
60
- GithubStatus.set_status(@statuses, @auth_token, state, message)
72
+ target_url = "#{@linkback_url}/job/#{job_id}"
73
+ client.create_status(@repo, @sha, state, context: 'Cyclid',
74
+ target_url: target_url,
75
+ description: message)
61
76
  end
62
77
  end
63
78
  end