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,129 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module MultiRepo::Helpers
|
4
|
+
class PullRequestBlasterOuter
|
5
|
+
attr_reader :repo, :base, :head, :script, :dry_run, :message, :title
|
6
|
+
|
7
|
+
def initialize(repo, base:, head:, script:, dry_run:, message:, title: nil, **)
|
8
|
+
@repo = repo
|
9
|
+
@base = base
|
10
|
+
@head = head
|
11
|
+
@script = begin
|
12
|
+
s = Pathname.new(script)
|
13
|
+
s = Pathname.new(Dir.pwd).join(script) if s.relative?
|
14
|
+
raise "File not found #{s}" unless s.exist?
|
15
|
+
s.to_s
|
16
|
+
end
|
17
|
+
@dry_run = dry_run
|
18
|
+
@message = message
|
19
|
+
@title = (title || message)[0, 72]
|
20
|
+
end
|
21
|
+
|
22
|
+
def blast
|
23
|
+
puts "+++ blasting #{repo.name}..."
|
24
|
+
|
25
|
+
repo.git.fetch
|
26
|
+
|
27
|
+
unless repo.git.remote_branch?("origin", base)
|
28
|
+
puts "!!! Skipping #{repo.name}: 'origin/#{base}' not found"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
repo.git.hard_checkout(head, "origin/#{base}")
|
33
|
+
run_script
|
34
|
+
|
35
|
+
result = false
|
36
|
+
if !commit_changes
|
37
|
+
puts "!!! Failed to commit changes. Perhaps the script is wrong or #{repo.name} is already updated."
|
38
|
+
elsif dry_run
|
39
|
+
result = "Committed but is dry run"
|
40
|
+
else
|
41
|
+
puts "Do you want to open a pull request on #{repo.name} with the above changes? (Y/N)"
|
42
|
+
answer = $stdin.gets.chomp
|
43
|
+
if answer.upcase.start_with?("Y")
|
44
|
+
fork_repo unless forked?
|
45
|
+
push_branch
|
46
|
+
result = open_pull_request
|
47
|
+
end
|
48
|
+
end
|
49
|
+
puts "--- blasting #{repo.name} complete"
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def github
|
56
|
+
@github ||= MultiRepo::Service::Github.new(dry_run: dry_run)
|
57
|
+
end
|
58
|
+
|
59
|
+
def forked?
|
60
|
+
github.client.repos(github.client.login).any? { |m| m.name == repo.name }
|
61
|
+
end
|
62
|
+
|
63
|
+
def fork_repo
|
64
|
+
github.client.fork(repo.name)
|
65
|
+
until forked?
|
66
|
+
print "."
|
67
|
+
sleep 3
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_script
|
72
|
+
repo.chdir do
|
73
|
+
parts = []
|
74
|
+
parts << "GITHUB_REPO=#{repo.name}"
|
75
|
+
parts << "DRY_RUN=true" if dry_run
|
76
|
+
parts << script
|
77
|
+
cmd = parts.join(" ")
|
78
|
+
|
79
|
+
unless system(cmd)
|
80
|
+
puts "!!! Script execution failed."
|
81
|
+
exit $?.exitstatus
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def commit_changes
|
87
|
+
repo.chdir do
|
88
|
+
begin
|
89
|
+
repo.git.client.add("-v", ".")
|
90
|
+
repo.git.client.commit("-m", message)
|
91
|
+
repo.git.client.show
|
92
|
+
if dry_run
|
93
|
+
puts "!!! --dry-run enabled: If the above commit in #{repo.path} looks good, run again without dry run to fork the repo, push the branch and open a pull request."
|
94
|
+
end
|
95
|
+
true
|
96
|
+
rescue MiniGit::GitError => e
|
97
|
+
e.status.exitstatus == 0
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def origin_remote
|
103
|
+
"pr_blaster_outer"
|
104
|
+
end
|
105
|
+
|
106
|
+
def origin_url
|
107
|
+
"git@github.com:#{github.client.login}/#{repo.name}.git"
|
108
|
+
end
|
109
|
+
|
110
|
+
def pr_head
|
111
|
+
"#{github.client.login}:#{head}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def push_branch
|
115
|
+
repo.chdir do
|
116
|
+
repo.git.client.remote("add", origin_remote, origin_url) unless repo.git.remote?(origin_remote)
|
117
|
+
repo.git.client.push("-f", origin_remote, "#{head}:#{head}")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def open_pull_request
|
122
|
+
pr = github.client.create_pull_request(repo.name, base, pr_head, title, title)
|
123
|
+
pr.html_url
|
124
|
+
rescue => err
|
125
|
+
raise unless err.message.include?("A pull request already exists")
|
126
|
+
puts "!!! Skipping. #{err.message}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "active_support/core_ext/object/deep_dup"
|
2
|
+
|
3
|
+
module MultiRepo::Helpers
|
4
|
+
class ReadmeBadges
|
5
|
+
attr_reader :repo, :dry_run
|
6
|
+
attr_accessor :badges
|
7
|
+
|
8
|
+
def initialize(repo, dry_run: false, **)
|
9
|
+
@repo = repo
|
10
|
+
@dry_run = dry_run
|
11
|
+
|
12
|
+
if repo.dry_run != dry_run
|
13
|
+
raise ArgumentError, "expected repo.dry_run (#{repo.dry_run}) to match dry_run (#{dry_run})"
|
14
|
+
end
|
15
|
+
|
16
|
+
reload
|
17
|
+
end
|
18
|
+
|
19
|
+
def save!
|
20
|
+
lines = content.lines
|
21
|
+
|
22
|
+
apply_badges!(lines)
|
23
|
+
save_contents!(lines.join)
|
24
|
+
|
25
|
+
if dry_run
|
26
|
+
reload(lines)
|
27
|
+
else
|
28
|
+
reload
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def content
|
35
|
+
return "" unless @file
|
36
|
+
File.read(repo.path.join(@file))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def reload(lines = nil)
|
42
|
+
@file = repo.detect_readme_file if lines.nil?
|
43
|
+
|
44
|
+
reload_badges(lines)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reload_badges(lines)
|
48
|
+
lines ||= content.lines
|
49
|
+
@badges = extract_badges(lines)
|
50
|
+
@original_badges = @badges.deep_dup
|
51
|
+
@original_badge_indexes = @badges.map { |b| b["index"] }
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_badges(lines)
|
55
|
+
lines.each.with_index.select do |l, _i|
|
56
|
+
l.to_s.start_with?("[![")
|
57
|
+
end.map do |l, i|
|
58
|
+
match = l.match(/\A\[!\[(?<description>[^\]]+)\]\((?<image>[^\)]+)\)\]\((?<url>[^\)]+)\)/)
|
59
|
+
match.named_captures.merge("index" => i)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_badge_string(badge)
|
64
|
+
"[![#{badge["description"]}](#{badge["image"]})](#{badge["url"]})\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def apply_badges!(lines)
|
68
|
+
return if badges == @original_badges
|
69
|
+
|
70
|
+
lines.reject!.with_index { |_l, i| @original_badge_indexes.include?(i) }
|
71
|
+
|
72
|
+
start_index = @original_badge_indexes[0] || 2
|
73
|
+
@badges.reverse_each do |b|
|
74
|
+
lines.insert(start_index, build_badge_string(b))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def save_contents!(contents)
|
79
|
+
repo.rm_file("README")
|
80
|
+
repo.rm_file("README.txt")
|
81
|
+
repo.write_file("README.md", contents)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MultiRepo::Helpers
|
2
|
+
class RenameLabels
|
3
|
+
attr_reader :repo_name, :rename_hash, :github
|
4
|
+
|
5
|
+
def initialize(repo_name, rename_hash, dry_run: false, **)
|
6
|
+
@repo_name = repo_name
|
7
|
+
@rename_hash = rename_hash
|
8
|
+
@github = MultiRepo::Service::Github.new(dry_run: dry_run)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
rename_hash.each do |old_name, new_name|
|
13
|
+
github_label = existing_labels.detect { |l| l.name == old_name }
|
14
|
+
|
15
|
+
if github_label
|
16
|
+
puts "Renaming label #{old_name.inspect} to #{new_name.inspect}"
|
17
|
+
github.update_label(repo_name, old_name, name: new_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private def existing_labels
|
23
|
+
@existing_labels ||= github.labels(repo_name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MultiRepo::Helpers
|
2
|
+
class UpdateBranchProtection
|
3
|
+
attr_reader :repo_name, :branch, :dry_run, :github
|
4
|
+
|
5
|
+
def initialize(repo_name, branch:, dry_run: false, **)
|
6
|
+
@repo_name = repo_name
|
7
|
+
@branch = branch
|
8
|
+
@github = MultiRepo::Service::Github.new(dry_run: dry_run)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
puts "Protecting #{branch} branch"
|
13
|
+
|
14
|
+
settings = {
|
15
|
+
:enforce_admins => nil,
|
16
|
+
:required_status_checks => nil,
|
17
|
+
:required_pull_request_reviews => nil,
|
18
|
+
:restrictions => nil
|
19
|
+
}
|
20
|
+
|
21
|
+
github.protect_branch(repo_name, branch, settings)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MultiRepo::Helpers
|
2
|
+
class UpdateLabels
|
3
|
+
attr_reader :repo_name, :expected_labels, :github
|
4
|
+
|
5
|
+
def initialize(repo_name, dry_run: false, **)
|
6
|
+
@repo_name = repo_name
|
7
|
+
@expected_labels = MultiRepo::Labels[repo_name]
|
8
|
+
@github = MultiRepo::Service::Github.new(dry_run: dry_run)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
if expected_labels.nil?
|
13
|
+
puts "!! No labels defined for #{repo_name}"
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
expected_labels.each do |label, color|
|
18
|
+
github_label = existing_labels.detect { |l| l.name == label }
|
19
|
+
|
20
|
+
if !github_label
|
21
|
+
puts "Creating #{label.inspect} with #{color.inspect}"
|
22
|
+
github.create_label(repo_name, label, color)
|
23
|
+
elsif github_label.color.downcase != color.downcase
|
24
|
+
puts "Updating #{label.inspect} to #{color.inspect}"
|
25
|
+
github.update_label(repo_name, label, color: color)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private def existing_labels
|
31
|
+
@existing_labels ||= github.client.labels(repo_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module MultiRepo::Helpers
|
4
|
+
class UpdateMilestone
|
5
|
+
attr_reader :repo_name, :title, :due_on, :close, :github
|
6
|
+
|
7
|
+
def initialize(repo_name, title:, due_on:, close:, dry_run: false, **)
|
8
|
+
raise ArgumentError, "due_on must be specified" if due_on.nil? && !close
|
9
|
+
|
10
|
+
@repo_name = repo_name
|
11
|
+
@title = title
|
12
|
+
@due_on = MultiRepo::Service::Github.parse_milestone_date(due_on) if due_on
|
13
|
+
@close = close
|
14
|
+
@github = MultiRepo::Service::Github.new(dry_run: dry_run)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
existing = github.find_milestone_by_title(repo_name, title)
|
19
|
+
if close
|
20
|
+
if existing
|
21
|
+
puts "Closing milestone #{title.inspect} (#{existing.number})"
|
22
|
+
github.close_milestone(repo.name, title, existing.number)
|
23
|
+
end
|
24
|
+
elsif existing
|
25
|
+
puts "Updating milestone #{title.inspect} (#{existing.number}) with due date #{due_on_str.inspect}"
|
26
|
+
github.update_milestone(repo.name, existing.number, due_on)
|
27
|
+
else
|
28
|
+
puts "Creating milestone #{title.inspect} with due date #{due_on_str.inspect}"
|
29
|
+
github.create_milestone(repo.name, title, due_on)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MultiRepo::Helpers
|
2
|
+
class UpdateRepoSettings
|
3
|
+
attr_reader :repo_name, :github
|
4
|
+
|
5
|
+
def initialize(repo_name, dry_run: false, **)
|
6
|
+
@repo_name = repo_name
|
7
|
+
@github = MultiRepo::Service::Github.new(dry_run: dry_run)
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
settings = {
|
12
|
+
:has_wiki => false,
|
13
|
+
:has_projects => false,
|
14
|
+
:allow_merge_commit => true,
|
15
|
+
:allow_rebase_merge => false,
|
16
|
+
:allow_squash_merge => false,
|
17
|
+
}
|
18
|
+
|
19
|
+
puts "Editing #{repo_name}"
|
20
|
+
github.edit_repository(repo_name, settings)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MultiRepo
|
2
|
+
class Labels
|
3
|
+
def self.config_file
|
4
|
+
MultiRepo.config_dir.join("labels.yml")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.config
|
8
|
+
@config ||= YAML.unsafe_load_file(config_file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.[](repo)
|
12
|
+
all[repo]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
@all ||= begin
|
17
|
+
require "more_core_extensions/core_ext/hash/nested"
|
18
|
+
|
19
|
+
Array(config["orgs"]).each do |org, options|
|
20
|
+
MultiRepo::Service::Github.org_repo_names(org).each do |repo_name|
|
21
|
+
next if config.key_path?("repos", repo_name)
|
22
|
+
next if options["except"].include?(repo_name)
|
23
|
+
|
24
|
+
config.store_path("repos", repo_name, options["labels"])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
config["repos"].sort.to_h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module MultiRepo
|
4
|
+
class Repo
|
5
|
+
attr_reader :name, :config, :path
|
6
|
+
attr_accessor :dry_run
|
7
|
+
|
8
|
+
def initialize(name, config: nil, dry_run: false)
|
9
|
+
@name = name
|
10
|
+
@dry_run = dry_run
|
11
|
+
@config = OpenStruct.new(config || {})
|
12
|
+
@path = MultiRepo.repos_dir.join(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias to_s inspect
|
16
|
+
|
17
|
+
def git
|
18
|
+
@git ||= MultiRepo::Service::Git.new(path: path, clone_source: config.clone_source || "git@github.com:#{name}.git", dry_run: dry_run)
|
19
|
+
end
|
20
|
+
|
21
|
+
def chdir
|
22
|
+
git # Ensures the clone exists
|
23
|
+
Dir.chdir(path) { yield }
|
24
|
+
end
|
25
|
+
|
26
|
+
def short_name
|
27
|
+
name.split("/").last
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_file(file, content, **kwargs)
|
31
|
+
if dry_run
|
32
|
+
puts "** dry-run: Writing #{path.join(file).expand_path}".light_black
|
33
|
+
else
|
34
|
+
chdir { File.write(file, content) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rm_file(file)
|
39
|
+
return unless path.join(file).exist?
|
40
|
+
|
41
|
+
if dry_run
|
42
|
+
puts "** dry-run: Removing #{path.join(file).expand_path}".light_black
|
43
|
+
else
|
44
|
+
chdir { FileUtils.rm_f(file) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def detect_readme_file
|
49
|
+
chdir do
|
50
|
+
%w[README.md README README.txt].detect do |f|
|
51
|
+
File.exist?(f)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module MultiRepo
|
4
|
+
class RepoSet
|
5
|
+
def self.config_files
|
6
|
+
Dir.glob(MultiRepo.config_dir.join("repos*.yml")).sort
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.config
|
10
|
+
@config ||= config_files.each_with_object({}) do |f, h|
|
11
|
+
h.merge!(YAML.unsafe_load_file(f))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.[](set_name)
|
16
|
+
all[set_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.all
|
20
|
+
@all ||= config.transform_values do |repo_set|
|
21
|
+
repo_set.map do |repo, config|
|
22
|
+
Repo.new(repo, config: config)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module MultiRepo::Service
|
2
|
+
class Artifactory
|
3
|
+
def self.api_token
|
4
|
+
@api_token ||= ENV.fetch("ARTIFACTORY_API_TOKEN")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.api_token=(token)
|
8
|
+
@api_token = token
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.api_endpoint
|
12
|
+
@api_endpoint ||= ENV.fetch("ARTIFACTORY_API_ENDPOINT")
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.api_endpoint=(endpoint)
|
16
|
+
@api_endpoint = endpoint
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.auth_header
|
20
|
+
{"X-JFrog-Art-Api" => api_token}
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :dry_run, :cache
|
24
|
+
|
25
|
+
def initialize(dry_run: false, cache: true)
|
26
|
+
require "rest-client"
|
27
|
+
require "json"
|
28
|
+
|
29
|
+
@dry_run = dry_run
|
30
|
+
@cache = cache
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_cache
|
34
|
+
FileUtils.rm_f(Dir.glob("/tmp/artifactory-*"))
|
35
|
+
end
|
36
|
+
|
37
|
+
# https://www.jfrog.com/confluence/display/JFROG/RPM+Repositories
|
38
|
+
def get(path, **kwargs)
|
39
|
+
path = path.to_s
|
40
|
+
request(:get, path, **kwargs)
|
41
|
+
end
|
42
|
+
|
43
|
+
def list(folder, cache: @cache, **kwargs)
|
44
|
+
folder = folder.to_s
|
45
|
+
cache_file = "/tmp/artifactory-#{folder.tr("/", "_")}-#{Date.today}.txt"
|
46
|
+
if cache && File.exist?(cache_file)
|
47
|
+
File.readlines(cache_file, :chomp => true)
|
48
|
+
else
|
49
|
+
data = raw_list(folder, cache: cache, **kwargs)
|
50
|
+
uri = data["uri"]
|
51
|
+
|
52
|
+
data["files"].map { |d| File.join(uri, d["uri"]) }.tap do |d|
|
53
|
+
File.write(cache_file, d.join("\n")) if cache
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def raw_list(folder, cache: @cache, **kwargs)
|
59
|
+
folder = folder.to_s
|
60
|
+
cache_file = "/tmp/artifactory-#{folder.tr("/", "_")}-raw-#{Date.today}.json"
|
61
|
+
if cache && File.exist?(cache_file)
|
62
|
+
JSON.parse(File.read(cache_file))
|
63
|
+
else
|
64
|
+
get("/api/storage/#{folder}?list&deep=1", **kwargs).tap do |d|
|
65
|
+
File.write(cache_file, JSON.pretty_generate(d)) if cache
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete(file, **kwargs)
|
71
|
+
file = file.to_s
|
72
|
+
request(:delete, strip_api_prefix(file), **kwargs)
|
73
|
+
rescue RestClient::NotFound => err
|
74
|
+
# Ignore deletes on a 404 because it's already deleted
|
75
|
+
raise unless err.http_code == 404
|
76
|
+
end
|
77
|
+
|
78
|
+
def move(file, to, **kwargs)
|
79
|
+
file = file.to_s
|
80
|
+
to = to.to_s
|
81
|
+
request(:post, File.join("/api/move", "#{strip_api_prefix(file)}?to=/#{strip_api_prefix(to)}"), **kwargs)
|
82
|
+
end
|
83
|
+
|
84
|
+
def copy(file, to, **kwargs)
|
85
|
+
file = file.to_s
|
86
|
+
to = to.to_s
|
87
|
+
request(:post, File.join("/api/copy", "#{strip_api_prefix(file)}?to=/#{strip_api_prefix(to)}"), **kwargs)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def request(verb, path, body: nil, headers: nil, verbose: true)
|
93
|
+
headers ||= self.class.auth_header.merge(
|
94
|
+
"Accept" => "application/json",
|
95
|
+
"Content-Type" => "application/json"
|
96
|
+
)
|
97
|
+
path = File.join(self.class.api_endpoint, path)
|
98
|
+
|
99
|
+
puts "+ #{verb.to_s.upcase} #{path}".light_black if verbose
|
100
|
+
if dry_run && %i[delete put post patch].include?(verb)
|
101
|
+
puts "+ dry_run: #{verb.to_s.upcase} #{path}".light_black if verbose
|
102
|
+
{}
|
103
|
+
else
|
104
|
+
response =
|
105
|
+
if %i[put post patch].include?(verb)
|
106
|
+
RestClient.send(verb, path, body, headers)
|
107
|
+
else
|
108
|
+
RestClient.send(verb, path, headers)
|
109
|
+
end
|
110
|
+
response.empty? ? {} : JSON.parse(response)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def api_file_prefix
|
115
|
+
File.join(self.class.api_endpoint, "api/storage")
|
116
|
+
end
|
117
|
+
|
118
|
+
def strip_api_prefix(path)
|
119
|
+
path.start_with?(api_file_prefix) ? path.sub(api_file_prefix, "") : path
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|