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