multi_repo 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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +16 -0
- data/.github/workflows/ci.yaml +32 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_cc.yml +4 -0
- data/.rubocop_local.yml +0 -0
- data/.whitesource +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +90 -0
- data/Rakefile +6 -0
- data/bin/console +8 -0
- data/exe/multi_repo +29 -0
- data/lib/multi_repo/cli.rb +92 -0
- data/lib/multi_repo/helpers/git_mirror.rb +198 -0
- data/lib/multi_repo/helpers/license.rb +106 -0
- data/lib/multi_repo/helpers/pull_request_blaster_outer.rb +129 -0
- data/lib/multi_repo/helpers/readme_badges.rb +84 -0
- data/lib/multi_repo/helpers/rename_labels.rb +26 -0
- data/lib/multi_repo/helpers/update_branch_protection.rb +24 -0
- data/lib/multi_repo/helpers/update_labels.rb +34 -0
- data/lib/multi_repo/helpers/update_milestone.rb +33 -0
- data/lib/multi_repo/helpers/update_repo_settings.rb +23 -0
- data/lib/multi_repo/labels.rb +31 -0
- data/lib/multi_repo/repo.rb +56 -0
- data/lib/multi_repo/repo_set.rb +27 -0
- data/lib/multi_repo/service/artifactory.rb +122 -0
- data/lib/multi_repo/service/code_climate.rb +119 -0
- data/lib/multi_repo/service/docker.rb +178 -0
- data/lib/multi_repo/service/git/minigit_capturing_patch.rb +12 -0
- data/lib/multi_repo/service/git.rb +90 -0
- data/lib/multi_repo/service/github.rb +238 -0
- data/lib/multi_repo/service/rubygems_stub.rb +103 -0
- data/lib/multi_repo/service/travis.rb +68 -0
- data/lib/multi_repo/version.rb +3 -0
- data/lib/multi_repo.rb +44 -0
- data/multi_repo.gemspec +44 -0
- data/repos/.gitkeep +0 -0
- data/scripts/delete_labels +23 -0
- data/scripts/destroy_branch +23 -0
- data/scripts/destroy_remote +26 -0
- data/scripts/destroy_tag +31 -0
- data/scripts/each_repo +23 -0
- data/scripts/fetch_repos +18 -0
- data/scripts/git_mirror +9 -0
- data/scripts/github_rate_limit +10 -0
- data/scripts/hacktoberfest +138 -0
- data/scripts/make_alumni +50 -0
- data/scripts/new_rubygems_stub +17 -0
- data/scripts/pull_request_blaster_outer +24 -0
- data/scripts/pull_request_labeler +59 -0
- data/scripts/pull_request_merger +63 -0
- data/scripts/reenable_repo_workflows +33 -0
- data/scripts/rename_labels +22 -0
- data/scripts/restart_travis_builds +31 -0
- data/scripts/show_commit_history +86 -0
- data/scripts/show_org_members +19 -0
- data/scripts/show_org_repos +13 -0
- data/scripts/show_org_stats +82 -0
- data/scripts/show_project_cards +35 -0
- data/scripts/show_repo_set +13 -0
- data/scripts/show_tag +33 -0
- data/scripts/show_travis_status +63 -0
- data/scripts/update_branch_protection +22 -0
- data/scripts/update_labels +16 -0
- data/scripts/update_milestone +21 -0
- data/scripts/update_repo_settings +15 -0
- metadata +366 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
module MultiRepo::Service
|
2
|
+
class CodeClimate
|
3
|
+
def self.api_token
|
4
|
+
@api_token ||= ENV["CODECLIMATE_API_TOKEN"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.api_token=(token)
|
8
|
+
@api_token = token
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.badge_name
|
12
|
+
"Code Climate"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.badge_details(repo)
|
16
|
+
{
|
17
|
+
"description" => badge_name,
|
18
|
+
"image" => "https://codeclimate.com/github/#{repo.name}.svg",
|
19
|
+
"url" => "https://codeclimate.com/github/#{repo.name}"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.coverage_badge_name
|
24
|
+
"Test Coverage"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.coverage_badge_details(repo)
|
28
|
+
{
|
29
|
+
"description" => coverage_badge_name,
|
30
|
+
"image" => "https://codeclimate.com/github/#{repo.name}/badges/coverage.svg",
|
31
|
+
"url" => "https://codeclimate.com/github/#{repo.name}/coverage"
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :repo, :dry_run
|
36
|
+
|
37
|
+
def initialize(repo, dry_run: false, **_)
|
38
|
+
@repo = repo
|
39
|
+
@dry_run = dry_run
|
40
|
+
end
|
41
|
+
|
42
|
+
def save!
|
43
|
+
write_codeclimate_yaml
|
44
|
+
write_rubocop_yamls
|
45
|
+
end
|
46
|
+
|
47
|
+
def enable
|
48
|
+
ensure_enabled
|
49
|
+
end
|
50
|
+
|
51
|
+
def badge_details
|
52
|
+
self.class.badge_details(repo)
|
53
|
+
end
|
54
|
+
|
55
|
+
def coverage_badge_details
|
56
|
+
self.class.coverage_badge_details(repo)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_reporter_id
|
60
|
+
ensure_enabled
|
61
|
+
@response.dig("data", 0, "attributes", "test_reporter_id")
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_repo_secret
|
65
|
+
Github.new(dry_run: dry_run).create_or_update_repository_secret(repo.name, "CC_TEST_REPORTER_ID", test_reporter_id)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def ensure_enabled
|
71
|
+
return if @enabled
|
72
|
+
|
73
|
+
require 'rest-client'
|
74
|
+
require 'json'
|
75
|
+
|
76
|
+
@response =
|
77
|
+
if dry_run
|
78
|
+
puts "** dry-run: RestClient.get(\"https://api.codeclimate.com/v1/repos?github_slug=#{repo.name}\", #{headers})".light_black
|
79
|
+
{"data" => [{"attributes" => {"badge_token" => "0123456789abdef01234", "test_reporter_id" => "0123456789abcedef0123456789abcedef0123456789abcedef0123456789abc"}}]}
|
80
|
+
else
|
81
|
+
JSON.parse(RestClient.get("https://api.codeclimate.com/v1/repos?github_slug=#{repo.name}", headers))
|
82
|
+
end
|
83
|
+
|
84
|
+
if @response["data"].empty?
|
85
|
+
payload = {"data" => {"type" => "repos", "attributes" => {"url" => "https://github.com/#{repo.name}"}}}.to_json
|
86
|
+
@response = JSON.parse(RestClient.post("https://api.codeclimate.com/v1/github/repos", payload, headers))
|
87
|
+
@response["data"] = [@response["data"]]
|
88
|
+
end
|
89
|
+
|
90
|
+
@enabled = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def headers
|
94
|
+
token = self.class.api_token
|
95
|
+
raise "Missing CodeClimate API Token" if token.nil?
|
96
|
+
|
97
|
+
{
|
98
|
+
:accept => "application/vnd.api+json",
|
99
|
+
:content_type => "application/vnd.api+json",
|
100
|
+
:authorization => "Token token=#{token}"
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_codeclimate_yaml
|
105
|
+
write_generator_file(".codeclimate.yml")
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_rubocop_yamls
|
109
|
+
%w[.rubocop.yml .rubocop_cc.yml .rubocop_local.yml].each do |file|
|
110
|
+
write_generator_file(file)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def write_generator_file(file)
|
115
|
+
content = RestClient.get("https://raw.githubusercontent.com/ManageIQ/manageiq/master/lib/generators/manageiq/plugin/templates/#{file}").body
|
116
|
+
repo.write_file(file, content, dry_run: dry_run)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module MultiRepo::Service
|
2
|
+
class Docker
|
3
|
+
def self.registry
|
4
|
+
@registry ||= ENV.fetch("DOCKER_REGISTRY")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.registry=(endpoint)
|
8
|
+
@registry = endpoint
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.clear_cache
|
12
|
+
FileUtils.rm_f(Dir.glob("/tmp/docker-*"))
|
13
|
+
end
|
14
|
+
|
15
|
+
SMALL_IMAGE = "hello-world:latest".freeze
|
16
|
+
|
17
|
+
def self.ensure_small_image
|
18
|
+
return @has_small_image if defined?(@has_small_image)
|
19
|
+
|
20
|
+
return false unless system?("docker pull #{SMALL_IMAGE} &>/dev/null")
|
21
|
+
|
22
|
+
@has_small_image = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.tag_small_image(fq_path)
|
26
|
+
return false unless ensure_small_image
|
27
|
+
|
28
|
+
system?("docker tag #{SMALL_IMAGE} #{fq_path}") &&
|
29
|
+
system?("docker push #{fq_path}") &&
|
30
|
+
system?("docker rmi #{fq_path}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.system?(command, dry_run: false, verbose: true)
|
34
|
+
if dry_run
|
35
|
+
puts "+ dry_run: #{command}".light_black
|
36
|
+
true
|
37
|
+
else
|
38
|
+
puts "+ #{command}".light_black
|
39
|
+
system(command)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.system!(command, **kwargs)
|
44
|
+
exit($?.exitstatus) unless system?(command, **kwargs)
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_accessor :registry, :cache, :dry_run
|
48
|
+
|
49
|
+
def initialize(registry: self.class.registry, cache: true, dry_run: false)
|
50
|
+
require "rest-client"
|
51
|
+
require "fileutils"
|
52
|
+
require "json"
|
53
|
+
|
54
|
+
@registry = registry
|
55
|
+
|
56
|
+
@cache = cache
|
57
|
+
@dry_run = dry_run
|
58
|
+
|
59
|
+
self.class.clear_cache unless cache
|
60
|
+
end
|
61
|
+
|
62
|
+
def tags(image, **kwargs)
|
63
|
+
path = File.join("v2", image, "tags/list")
|
64
|
+
cache_file = "/tmp/docker-tags-#{image.tr("/", "-")}-raw-#{Date.today}.json"
|
65
|
+
request(:get, path, **kwargs).tap do |data|
|
66
|
+
File.write(cache_file, JSON.pretty_generate(data))
|
67
|
+
end["tags"]
|
68
|
+
end
|
69
|
+
|
70
|
+
def retag(image, new_image)
|
71
|
+
system?("skopeo copy --multi-arch all docker://#{image} docker://#{new_image}", dry_run: dry_run)
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_registry_tag(image, tag, **kwargs)
|
75
|
+
path = File.join("v2", image, "manifests", tag)
|
76
|
+
request(:delete, path, **kwargs)
|
77
|
+
true
|
78
|
+
rescue RestClient::NotFound => err
|
79
|
+
# Ignore deletes on 404s because they are either already deleted or the tag is orphaned.
|
80
|
+
raise unless err.http_code == 404
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def force_delete_registry_tag(image, tag, **kwargs)
|
85
|
+
return true if delete_registry_tag(image, tag, **kwargs)
|
86
|
+
|
87
|
+
# The tag is likely orphaned, so recreate the tag with a new image, then immediately delete it
|
88
|
+
fq_path = File.join(registry, "#{image}:#{tag}")
|
89
|
+
self.class.tag_small_image(fq_path) &&
|
90
|
+
delete_registry_tag(image, tag, **kwargs)
|
91
|
+
end
|
92
|
+
|
93
|
+
def run(image, command, platform: nil)
|
94
|
+
system_capture!("docker run --rm -it #{"--platform=#{platform} " if platform} #{image} #{command}")
|
95
|
+
end
|
96
|
+
|
97
|
+
def fetch_image_by_sha(source_image, tag: nil, platform: nil)
|
98
|
+
source_image_name, _source_image_sha = source_image.split("@")
|
99
|
+
|
100
|
+
system!("docker pull #{"--platform=#{platform} " if platform}#{source_image}")
|
101
|
+
system!("docker tag #{source_image} #{source_image_name}:#{tag}") if tag
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def remove_images(*images)
|
107
|
+
command = "docker rmi #{images.join(" ")}"
|
108
|
+
|
109
|
+
# Don't use system_capture! as this is expected to fail if the image does not exist.
|
110
|
+
if dry_run
|
111
|
+
puts "+ dry_run: #{command}".light_black
|
112
|
+
else
|
113
|
+
puts "+ #{command}".light_black
|
114
|
+
`#{command} 2>/dev/null`
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def manifest_inspect(image)
|
119
|
+
command = "docker manifest inspect #{image}"
|
120
|
+
|
121
|
+
cache_file = "/tmp/docker-manifest-#{image.split("@").last}.txt"
|
122
|
+
if cache && File.exist?(cache_file)
|
123
|
+
puts "+ cached: #{command}".light_black
|
124
|
+
data = File.read(cache_file)
|
125
|
+
else
|
126
|
+
data = system_capture(command)
|
127
|
+
File.write(cache_file, data)
|
128
|
+
end
|
129
|
+
|
130
|
+
data.blank? ? {} : JSON.parse(data)
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def request(verb, path, body: nil, headers: {}, verbose: true)
|
136
|
+
path = File.join(registry, path)
|
137
|
+
|
138
|
+
if dry_run && %i[delete put post patch].include?(verb)
|
139
|
+
puts "+ dry_run: #{verb.to_s.upcase} #{path}".light_black if verbose
|
140
|
+
{}
|
141
|
+
else
|
142
|
+
puts "+ #{verb.to_s.upcase} #{path}".light_black if verbose
|
143
|
+
response =
|
144
|
+
if %i[put post patch].include?(verb)
|
145
|
+
RestClient.send(verb, path, body, headers)
|
146
|
+
else
|
147
|
+
RestClient::Request.execute(:method => verb, :url => path, :headers => headers, :read_timeout => 300) do |response, request, result|
|
148
|
+
if verb == :delete && response.code == 301 # Moved Permanently
|
149
|
+
response.follow_redirection
|
150
|
+
else
|
151
|
+
response.return!
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
response.empty? ? {} : JSON.parse(response)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def system?(command, **kwargs)
|
160
|
+
self.class.system?(command, **kwargs)
|
161
|
+
end
|
162
|
+
|
163
|
+
def system!(command, **kwargs)
|
164
|
+
self.class.system!(command, **kwargs)
|
165
|
+
end
|
166
|
+
|
167
|
+
def system_capture(command)
|
168
|
+
puts "+ #{command}".light_black
|
169
|
+
`#{command}`.chomp
|
170
|
+
end
|
171
|
+
|
172
|
+
def system_capture!(command)
|
173
|
+
system_capture(command).tap do
|
174
|
+
exit($?.exitstatus) if $?.exitstatus != 0
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "active_support/core_ext/object/blank"
|
2
|
+
|
3
|
+
module MultiRepo::Service
|
4
|
+
class Git
|
5
|
+
def self.client(path:, clone_source:)
|
6
|
+
require "minigit"
|
7
|
+
require_relative "git/minigit_capturing_patch"
|
8
|
+
|
9
|
+
retried = false
|
10
|
+
MiniGit.debug = true if ENV["GIT_DEBUG"]
|
11
|
+
MiniGit.new(path)
|
12
|
+
rescue ArgumentError => err
|
13
|
+
raise if retried
|
14
|
+
raise unless err.message.include?("does not seem to exist")
|
15
|
+
|
16
|
+
clone(clone_source: clone_source, path: path)
|
17
|
+
retried = true
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.clone(clone_source:, path:)
|
22
|
+
require "minigit"
|
23
|
+
require "shellwords"
|
24
|
+
|
25
|
+
args = ["clone", clone_source, path]
|
26
|
+
command = Shellwords.join(["git", *args])
|
27
|
+
command << " &>/dev/null" unless ENV["GIT_DEBUG"]
|
28
|
+
puts "+ #{command}" if ENV["GIT_DEBUG"] # Matches the output of MiniGit
|
29
|
+
|
30
|
+
raise MiniGit::GitError.new(args, $?) unless system(command)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :dry_run, :client
|
34
|
+
|
35
|
+
def initialize(path:, clone_source:, dry_run: false)
|
36
|
+
require "minigit"
|
37
|
+
|
38
|
+
@dry_run = dry_run
|
39
|
+
@client = self.class.client(path: path, clone_source: clone_source)
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch(output: false)
|
43
|
+
client = output ? self.client : self.client.capturing
|
44
|
+
|
45
|
+
client.fetch(:all => true, :tags => true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def hard_checkout(branch, source = "origin/#{branch}", output: false)
|
49
|
+
client = output ? self.client : self.client.capturing
|
50
|
+
|
51
|
+
client.reset(:hard => true)
|
52
|
+
client.clean("-xdf")
|
53
|
+
client.checkout("-B", branch, source)
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy_tag(tag, output: false)
|
57
|
+
client = output ? self.client : self.client.capturing
|
58
|
+
|
59
|
+
if dry_run
|
60
|
+
puts "** dry-run: git tag --delete #{tag}".light_black
|
61
|
+
else
|
62
|
+
client.tag({:delete => true}, tag)
|
63
|
+
end
|
64
|
+
rescue MiniGit::GitError
|
65
|
+
# Ignore missing tags because we want them destroyed anyway
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def branch?(branch)
|
70
|
+
client.capturing.rev_parse("--verify", branch)
|
71
|
+
rescue MiniGit::GitError
|
72
|
+
false
|
73
|
+
else
|
74
|
+
true
|
75
|
+
end
|
76
|
+
alias_method :tag?, :branch?
|
77
|
+
|
78
|
+
def remote?(remote)
|
79
|
+
client.capturing.remote("show", remote)
|
80
|
+
rescue MiniGit::GitError => e
|
81
|
+
false
|
82
|
+
else
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def remote_branch?(remote, branch)
|
87
|
+
client.capturing.ls_remote(remote, branch).present?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module MultiRepo::Service
|
4
|
+
class Github
|
5
|
+
def self.api_token
|
6
|
+
@api_token ||= ENV["GITHUB_API_TOKEN"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.api_token=(token)
|
10
|
+
@api_token = token
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.api_endpoint
|
14
|
+
@api_endpoint ||= ENV["GITHUB_API_ENDPOINT"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.api_endpoint=(endpoint)
|
18
|
+
@api_endpoint = endpoint
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.client
|
22
|
+
@client ||= begin
|
23
|
+
raise "Missing GitHub API Token" if api_token.nil?
|
24
|
+
|
25
|
+
params = {
|
26
|
+
:api_endpoint => api_endpoint,
|
27
|
+
:access_token => api_token,
|
28
|
+
:auto_paginate => true
|
29
|
+
}.compact
|
30
|
+
|
31
|
+
require 'octokit'
|
32
|
+
Octokit::Client.new(params)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.org_repo_names(org, include_forks: false, include_archived: false)
|
37
|
+
repos = client.list_repositories(org, :type => "sources")
|
38
|
+
repos.reject!(&:fork?) unless include_forks
|
39
|
+
repos.reject!(&:archived?) unless include_archived
|
40
|
+
repos.map(&:full_name).sort
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.valid_milestone_date?(date)
|
44
|
+
!!parse_milestone_date(date)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.parse_milestone_date(date)
|
48
|
+
require "active_support/core_ext/time"
|
49
|
+
ActiveSupport::TimeZone.new('Pacific Time (US & Canada)').parse(date) # LOL GitHub, TimeZones are hard
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find_milestone_by_title(repo_name, title)
|
53
|
+
client.list_milestones(repo_name, :state => :all).detect { |m| m.title.casecmp?(title) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.org_member_names(org)
|
57
|
+
client.org_members(org).map(&:login).sort_by(&:downcase)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.find_team_by_name(org, team)
|
61
|
+
client.org_teams(org).detect { |t| t.slug == team }
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.team_members(org, team)
|
65
|
+
team_id = find_team_by_name(org, team)&.id
|
66
|
+
team_id ? client.team_members(team_id) : []
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.team_member_names(org, team)
|
70
|
+
team_members(org, team).map(&:login).sort_by(&:downcase)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.team_ids_by_name(org)
|
74
|
+
@team_ids ||= {}
|
75
|
+
@team_ids[org] ||= client.org_teams(org).map { |t| [t.slug, t.id] }.sort.to_h
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.team_names(org)
|
79
|
+
team_ids(org).keys
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.disabled_workflows(repo_name)
|
83
|
+
client.workflows(repo_name)[:workflows].select { |w| w.state == "disabled_inactivity" }
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_reader :dry_run
|
87
|
+
|
88
|
+
def initialize(dry_run: false)
|
89
|
+
require "octokit"
|
90
|
+
|
91
|
+
@dry_run = dry_run
|
92
|
+
end
|
93
|
+
|
94
|
+
delegate :client,
|
95
|
+
:org_repo_names,
|
96
|
+
:find_milestone_by_title,
|
97
|
+
:org_member_names,
|
98
|
+
:find_team_by_name,
|
99
|
+
:team_members,
|
100
|
+
:team_member_names,
|
101
|
+
:team_ids_by_name,
|
102
|
+
:team_names,
|
103
|
+
:disabled_workflows,
|
104
|
+
:to => :class
|
105
|
+
|
106
|
+
def edit_repository(repo_name, settings)
|
107
|
+
if dry_run
|
108
|
+
puts "** dry-run: github.edit_repository(#{repo_name.inspect}, #{settings.inspect[1..-2]})".light_black
|
109
|
+
else
|
110
|
+
client.edit_repository(repo_name, settings)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def create_label(repo_name, label, color)
|
115
|
+
if dry_run
|
116
|
+
puts "** dry-run: github.add_label(#{repo_name.inspect}, #{label.inspect}, #{color.inspect})".light_black
|
117
|
+
else
|
118
|
+
client.add_label(repo_name, label, color)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def update_label(repo_name, label, color: nil, name: nil)
|
123
|
+
settings = {:color => color, :name => name}.compact
|
124
|
+
raise ArgumentError, "one of color or name must be passed" if settings.empty?
|
125
|
+
|
126
|
+
if dry_run
|
127
|
+
puts "** dry-run: github.update_label(#{repo_name.inspect}, #{label.inspect}, #{settings.inspect[1..-2]})".light_black
|
128
|
+
else
|
129
|
+
client.update_label(repo_name, label, settings)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def delete_label!(repo_name, label)
|
134
|
+
if dry_run
|
135
|
+
puts "** dry-run: github.delete_label!(#{repo_name.inspect}, #{label.inspect})".light_black
|
136
|
+
else
|
137
|
+
client.delete_label!(repo_name, label)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def create_milestone(repo_name, title, due_on)
|
142
|
+
if dry_run
|
143
|
+
puts "** dry-run: github.create_milestone(#{repo_name.inspect}, #{title.inspect}, :due_on => #{due_on.strftime("%Y-%m-%d").inspect})".light_black
|
144
|
+
else
|
145
|
+
client.create_milestone(repo_name, title, :due_on => due_on)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def update_milestone(repo_name, milestone_number, due_on)
|
150
|
+
if dry_run
|
151
|
+
puts "** dry-run: github.update_milestone(#{repo_name.inspect}, #{milestone_number}, :due_on => #{due_on.strftime("%Y-%m-%d").inspect})".light_black
|
152
|
+
else
|
153
|
+
client.update_milestone(repo_name, milestone_number, :due_on => due_on)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def close_milestone(repo_name, milestone_number)
|
158
|
+
if dry_run
|
159
|
+
puts "** dry-run: github.update_milestone(#{repo_name.inspect}, #{milestone_number}, :state => 'closed')".light_black
|
160
|
+
else
|
161
|
+
client.update_milestone(repo_name, milestone_number, :state => "closed")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def protect_branch(repo_name, branch, settings)
|
166
|
+
if dry_run
|
167
|
+
puts "** dry-run: github.protect_branch(#{repo_name.inspect}, #{branch.inspect}, #{settings.inspect[1..-2]})".light_black
|
168
|
+
else
|
169
|
+
client.protect_branch(repo_name, branch, settings)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def add_team_membership(org, team, user)
|
174
|
+
team_id = team_ids_by_name(org)[team]
|
175
|
+
|
176
|
+
if dry_run
|
177
|
+
puts "** dry-run: github.add_team_membership(#{team_id.inspect}, #{user.inspect})".light_black
|
178
|
+
else
|
179
|
+
client.add_team_membership(team_id, user)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def remove_team_membership(org, team, user)
|
184
|
+
team_id = team_ids_by_name(org)[team]
|
185
|
+
|
186
|
+
if dry_run
|
187
|
+
puts "** dry-run: github.remove_team_membership(#{team_id.inspect}, #{user.inspect})".light_black
|
188
|
+
else
|
189
|
+
client.remove_team_membership(team_id, user)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def remove_collaborator(repo_name, user)
|
194
|
+
if dry_run
|
195
|
+
puts "** dry-run: github.remove_collaborator(#{repo_name.inspect}, #{user.inspect})".light_black
|
196
|
+
else
|
197
|
+
client.remove_collaborator(repo_name, user)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def enable_workflow(repo_name, workflow_number)
|
202
|
+
command = "repos/#{repo_name}/actions/workflows/#{workflow_number}/enable"
|
203
|
+
|
204
|
+
if dry_run
|
205
|
+
puts "** dry-run: github.put(#{command.inspect})".light_black
|
206
|
+
else
|
207
|
+
client.put(command)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def create_or_update_repository_secret(repo_name, key, value)
|
212
|
+
payload = encode_secret(repo_name, value)
|
213
|
+
|
214
|
+
if dry_run
|
215
|
+
puts "** dry-run: github.create_or_update_secret(#{repo_name.inspect}, #{key.inspect}, #{payload.inspect})".light_black
|
216
|
+
else
|
217
|
+
client.create_or_update_secret(repo_name, key, payload)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
private def encode_secret(repo_name, value)
|
222
|
+
require "rbnacl"
|
223
|
+
require "base64"
|
224
|
+
|
225
|
+
repo_public_key = client.get_public_key(repo_name)
|
226
|
+
decoded_repo_public_key = Base64.decode64(repo_public_key.key)
|
227
|
+
public_key = RbNaCl::PublicKey.new(decoded_repo_public_key)
|
228
|
+
box = RbNaCl::Boxes::Sealed.from_public_key(public_key)
|
229
|
+
encrypted_value = box.encrypt(value)
|
230
|
+
encoded_encrypted_value = Base64.strict_encode64(encrypted_value)
|
231
|
+
|
232
|
+
{
|
233
|
+
"encrypted_value" => encoded_encrypted_value,
|
234
|
+
"key_id" => repo_public_key.key_id
|
235
|
+
}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|