2
2
3
3
require 'octokit'
4
4
require 'thor'
5
+ require 'yaml'
5
6
6
7
class Samvera < Thor
7
8
9
+ attr_reader :owner , :repo , :label , :project_id
10
+
8
11
desc "audit_issues" , "Audits a repository for all stale issues, labels them, and adds a comment to the issue."
9
12
option :repo , required : true , type : :string
10
13
option :updated , type : :string , default : "2021-01-01"
@@ -13,13 +16,11 @@ class Samvera < Thor
13
16
option :project_id , type : :numeric , default : 28
14
17
def audit_issues
15
18
16
- repo = options [ :repo ]
19
+ @ repo = options [ :repo ]
17
20
created = options [ :created ]
18
21
updated = options [ :updated ]
19
- label = options [ :label ]
20
-
21
- # Authenticate with GitHub
22
- client = Octokit ::Client . new ( access_token : ENV [ 'GH_TOKEN' ] )
22
+ @label = options [ :label ]
23
+ @project_id = options [ :project_id ]
23
24
24
25
# Define the search criteria
25
26
query = "repo:#{ repo } is:issue is:open created:<#{ created } updated:<#{ updated } "
@@ -63,9 +64,11 @@ class Samvera < Thor
63
64
say ( "No milestone to remove from Issue ##{ issue . number } " , :yellow )
64
65
end
65
66
66
- project_url = "https://github.com/orgs/samvera/projects/#{ project_id } "
67
- project_card = client . create_project_card ( project_url , content_id : issue . id , content_type : 'Issue' )
68
- say ( "Added Issue ##{ issue . number } to Project '#{ project_name } '" , :green )
67
+ # This will fail, as projects are only supported with the GraphQL API
68
+ # @see https://docs.github.com/en/rest/projects/projects?apiVersion=2022-11-28
69
+ # project_url = "https://github.com/orgs/samvera/projects/#{project_id}"
70
+ # project_card = client.create_project_card(project_url, content_id: issue.id, content_type: 'Issue')
71
+ # say("Added Issue ##{issue.number} to Project '#{project_url}'", :green)
69
72
rescue Octokit ::Error => e
70
73
say ( "Failed to audit Issue ##{ issue . number } : #{ e . message } " , :red )
71
74
end
@@ -86,8 +89,6 @@ class Samvera < Thor
86
89
user_login = options [ :user ]
87
90
org = options [ :org ]
88
91
89
- client = Octokit ::Client . new ( access_token : ENV [ 'GH_TOKEN' ] )
90
-
91
92
begin
92
93
team = client . team_by_name ( org , team_slug )
93
94
team_id = team [ :id ]
@@ -113,8 +114,6 @@ class Samvera < Thor
113
114
user_login = options [ :user ]
114
115
org = options [ :org ]
115
116
116
- client = Octokit ::Client . new ( access_token : ENV [ 'GH_TOKEN' ] )
117
-
118
117
begin
119
118
team = client . team_by_name ( org , team_slug )
120
119
team_id = team [ :id ]
@@ -129,5 +128,279 @@ class Samvera < Thor
129
128
say ( "The user is not a member of the team." , :yellow )
130
129
end
131
130
end
131
+
132
+ desc "audit_repo_ci" , "Audit the continuous integration (CI) configuration for a Samvera GitHub Repository"
133
+ option :repo , required : true , type : :string
134
+ option :owner , type : :string , default : "samvera"
135
+ option :label , type : :string , default : "maintenance"
136
+ option :project_id , type : :numeric , default : 28
137
+ def audit_repo_ci
138
+
139
+ @owner = options [ :owner ]
140
+ @repo = options [ :repo ]
141
+ @label = options [ :label ]
142
+ @project_id = options [ :project_id ]
143
+
144
+ repo_url = "https://github.com/#{ owner } /#{ repo } .git"
145
+ local_dir = "tmp/#{ repo } "
146
+
147
+ # Clone the repository if it doesn't already exist
148
+ unless Dir . exist? ( local_dir )
149
+ say ( "Cloning repository..." , :green )
150
+ system ( "git clone #{ repo_url } #{ local_dir } " )
151
+ end
152
+
153
+ file_path = File . join ( local_dir , '.circleci' , 'config.yml' )
154
+
155
+ if File . exist? ( file_path )
156
+ say ( "File exists: #{ file_path } " , :green )
157
+
158
+ content = File . read ( file_path )
159
+ config = YAML . load ( content )
160
+
161
+ if config . key? ( "orbs" )
162
+ say ( "Orbs are specified" , :green )
163
+
164
+ orbs = config [ "orbs" ]
165
+
166
+ if orbs . key? ( "samvera" )
167
+ say ( "samvera/circleci-orb is used" , :green )
168
+
169
+ samvera_orb = orbs [ "samvera" ]
170
+ if samvera_orb != samvera_orb_release
171
+ say ( "Unsupported release of samvera/circleci-orb is referenced" , :red )
172
+ # create an issue for updating to the latest release of samvera/circleci-orb
173
+ else
174
+ say ( "Latest supported release of samvera/circleci-orb is referenced" , :green )
175
+ end
176
+ else
177
+ say ( "samvera/circleci-orb is not used" , :red )
178
+ # create an issue for including the latest release of samvera/circleci-orb
179
+ end
180
+ else
181
+ say ( "No orbs are specified" , :red )
182
+ # create an issue for including the latest release of samvera/circleci-orb
183
+ end
184
+
185
+ if config . key? ( "jobs" )
186
+
187
+ jobs = config [ "jobs" ]
188
+ checks_for_master_branch = false
189
+
190
+ jobs . each_pair do |key , job |
191
+
192
+ if job . key? ( "parameters" )
193
+ parameters = job [ "parameters" ]
194
+
195
+ if parameters . key? ( "ruby_version" )
196
+ say ( "Ruby version is parameterized for #{ key } " , :green )
197
+ else
198
+ say ( "Ruby version is not parameterized for #{ key } " , :red )
199
+ # create an issue
200
+ end
201
+
202
+ if parameters . key? ( "bundler_version" )
203
+ say ( "Bundler version is parameterized for #{ key } " , :green )
204
+ else
205
+ say ( "Bundler version is not parameterized for #{ key } " , :red )
206
+ # create an issue
207
+ end
208
+ else
209
+ say ( "Parameters are not specified for job #{ key } " , :red )
210
+ # create an issue
211
+ end
212
+
213
+ if job . key? ( "steps" )
214
+ steps = job [ "steps" ]
215
+
216
+ if steps . empty?
217
+ say ( "Steps are empty for #{ key } " , :red )
218
+ # create an issue
219
+ end
220
+
221
+ steps . each do |step |
222
+ if step . is_a? ( Hash )
223
+ if step . key? ( "run" )
224
+ command = step [ "run" ]
225
+
226
+ if command . key? ( "name" )
227
+ name = command [ "name" ]
228
+
229
+ if name == "Check for a branch named 'master'"
230
+ checks_for_master_branch = true
231
+ say ( "Found a job which checks for the existence of a branch named `master`." , :green )
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ else
239
+ say ( "Steps are not specified for job #{ key } " , :red )
240
+ # create an issue
241
+ end
242
+ end
243
+
244
+ unless checks_for_master_branch
245
+ say ( "No job checks for the existence of a branch named `master`." , :red )
246
+ # create issue
247
+ end
248
+ else
249
+ say ( "No jobs are specified" , :red )
250
+ # create an issue for reviewing the CircleCI configuration
251
+ end
252
+
253
+ if config . key? ( "workflows" )
254
+
255
+ workflows = config [ "workflows" ]
256
+
257
+ workflows . each_pair do |key , workflow |
258
+
259
+ if workflow . key? ( "jobs" )
260
+ jobs = workflow [ "jobs" ]
261
+
262
+ jobs . each do |job |
263
+ job . each_pair do |key , arg |
264
+ if arg . key? ( "ruby_version" )
265
+ ruby_version = arg [ "ruby_version" ]
266
+
267
+ if supported_ruby_versions . include? ( ruby_version )
268
+ say ( "Supported Ruby version #{ ruby_version } is used for CircleCI" , :green )
269
+ else
270
+ validation_error = "Unsupported Ruby version #{ ruby_version } is used for CircleCI"
271
+ handle_error ( validation_error : validation_error )
272
+ end
273
+ end
274
+
275
+ if arg . key? ( "rails_version" )
276
+ rails_version = arg [ "rails_version" ]
277
+
278
+ if supported_rails_versions . include? ( rails_version )
279
+ say ( "Supported Rails version #{ rails_version } is used for CircleCI" , :green )
280
+ else
281
+ validation_error = "Unsupported Rails version #{ rails_version } is used for CircleCI"
282
+ handle_error ( validation_error : validation_error )
283
+ end
284
+ end
285
+ end
286
+ end
287
+ else
288
+ say ( "No workflow jobs are specified" , :red )
289
+ # create issue
290
+ end
291
+ end
292
+ else
293
+ say ( "No workflows are specified" , :red )
294
+ # create issue
295
+ end
296
+ else
297
+ say ( "File does not exist: #{ file_path } " , :red )
298
+ # create issue
299
+ end
300
+ end
301
+
302
+ private
303
+
304
+ def config
305
+ @config ||= begin
306
+ file_path = "./config/cli.yaml"
307
+ yaml_content = File . read ( file_path )
308
+ YAML . load ( yaml_content )
309
+ end
310
+ end
311
+
312
+ def samvera_orb_release
313
+ config [ "samvera_orb_release" ]
314
+ end
315
+
316
+ def supported_ruby_versions
317
+ config [ "supported_ruby_versions" ]
318
+ end
319
+
320
+ def supported_rails_versions
321
+ config [ "supported_rails_versions" ]
322
+ end
323
+
324
+ def errors
325
+ @errors ||= [ ]
326
+ end
327
+
328
+ def access_token
329
+ ENV [ 'GH_TOKEN' ]
330
+ end
331
+
332
+ def client
333
+ @client ||= Octokit ::Client . new ( access_token : access_token )
334
+ end
335
+
336
+ def repository
337
+ repository ||= client . repo ( "#{ owner } /#{ repo } " )
338
+ end
339
+
340
+ def project_url
341
+ @project_url ||= "https://github.com/orgs/samvera/projects/#{ project_id } "
342
+ end
343
+
344
+ # This will fail, as projects are only supported with the GraphQL API
345
+ # @see https://docs.github.com/en/rest/projects/projects?apiVersion=2022-11-28
346
+ def columns
347
+ client . project_columns ( project_id )
348
+ end
349
+
350
+ def column
351
+ columns . first
352
+ end
353
+
354
+ def column_id
355
+ column [ "id" ]
356
+ end
357
+
358
+ def prepare_github_issue ( issue :)
359
+ unless issue . labels . map ( &:name ) . include? ( label )
360
+
361
+ begin
362
+ client . add_labels_to_an_issue ( repository . id , issue . number , [ label ] )
363
+ say ( "Label ``#{ label } \" applied to Issue ##{ issue . number } " , :green )
364
+
365
+ # This will fail, as projects are only supported with the GraphQL API
366
+ # @see https://docs.github.com/en/rest/projects/projects?apiVersion=2022-11-28
367
+ #
368
+ # say("Using #{column_id} for Project '#{project_id}'", :green)
369
+ # project_card = client.create_project_card(column_id, content_id: issue.id, content_type: 'Issue')
370
+ # say("Added Issue ##{issue.number} to Project '#{project_id}'", :green)
371
+ rescue Octokit ::Error => e
372
+ say ( "Failed to audit Issue ##{ issue . number } : #{ e . message } " , :red )
373
+ end
374
+ end
375
+ end
376
+
377
+ def create_github_issue ( issue_title :, issue_body :)
378
+
379
+ issues = client . issues ( repository . id )
380
+ existing_issues = issues . select { |issue | issue . title == issue_title }
381
+
382
+ if !existing_issues . empty?
383
+ existing_issues . each do |issue |
384
+ say ( "Issue exists: #{ issue . html_url } " , :yellow )
385
+ prepare_github_issue ( issue : issue )
386
+ end
387
+ else
388
+ issue = self . client . create_issue ( repository . id , issue_title , issue_body )
389
+ say ( "Issue created: #{ issue . html_url } " , :green )
390
+ prepare_github_issue ( issue : issue )
391
+ end
392
+ rescue Octokit ::Error => e
393
+ say ( "Error creating issue: #{ e . message } " , :red )
394
+ end
395
+
396
+ def handle_error ( validation_error :)
397
+ say ( validation_error , :red )
398
+
399
+ unless errors . include? ( validation_error )
400
+ issue_title = "CircleCI audit error: #{ validation_error } "
401
+ create_github_issue ( issue_title : issue_title , issue_body : validation_error )
402
+ errors << validation_error
403
+ end
404
+ end
132
405
end
133
406
0 commit comments