cyclid 0.2.1 → 0.2.2

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