multi_repo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|