repo-mgr 0.1.1 → 0.3.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 +4 -4
- data/README.md +62 -13
- data/lib/repo_mgr/backends/deb.rb +51 -55
- data/lib/repo_mgr/backends/rpm.rb +118 -2
- data/lib/repo_mgr/cli.rb +105 -15
- data/lib/repo_mgr/config.rb +21 -10
- data/lib/repo_mgr/publishers/git.rb +30 -0
- data/lib/repo_mgr/publishers.rb +18 -0
- metadata +48 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a977e25ed86d9763543cd445087e3d4e94e6ecf830e707ffb8c9dfb9c5d19541
|
4
|
+
data.tar.gz: a7d2ea6036a1d3a4fc3fc2c6f575e7dda9eaa7bc2db1d7bc0b2fe7dcdb459891
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8044c481083ac23582a3d3133dbd225ab874188217766e71d0e82e3a76bf325776ef6241064347ee5fb39df5e63d89acfe7c086f592314062fe1c3a391645ae8
|
7
|
+
data.tar.gz: d58d378485e4a41741360d308652e56b4dc9a3abd4be396840d1131c6daf187fdce3bde7de00eb2242ac0207dfacd912c591783e6fe38eaf91e266f4e77892d0
|
data/README.md
CHANGED
@@ -9,8 +9,14 @@ Features:
|
|
9
9
|
* Create/update deb/rpm repositories.
|
10
10
|
* Add/remove packages to these repositories and automatically sign packages using GPG.
|
11
11
|
* Repository metadata/manifest signing using GPG.
|
12
|
+
* Publish to remote via git.
|
12
13
|
|
13
|
-
To simplify things
|
14
|
+
To simplify things:
|
15
|
+
|
16
|
+
* aptly (which, kind of obviously, manages deb repositories) uses "stable" as distribution and "main" as component.
|
17
|
+
* The git publisher uses the `main` branch for `sync` only.
|
18
|
+
|
19
|
+
The requred aptly version is 1.4.0+ which may be installed from [aptly's deb repository](https://www.aptly.info/download/). The version available in Ubuntu 20.04 (1.3.0) doesn't support gpg2.
|
14
20
|
|
15
21
|
## Install
|
16
22
|
|
@@ -22,20 +28,21 @@ gem install repo-mgr
|
|
22
28
|
rake install
|
23
29
|
```
|
24
30
|
|
25
|
-
As repo-mgr is a frontend for other tools, there
|
31
|
+
As repo-mgr is a frontend for other tools, there are dependencies which must be installed separately. It is not compulsory to install all dependencies, only those needed for a particular use case. The purpose for each tool is explained by `check-depends`.
|
26
32
|
|
27
|
-
To check which dependencies are required and their status:
|
33
|
+
To check which dependencies are required based on use case and their status:
|
28
34
|
|
29
35
|
```bash
|
30
36
|
repo-mgr check-depends
|
31
|
-
|
32
|
-
| Binary | Status |
|
33
|
-
|
34
|
-
| aptly | ✔ |
|
35
|
-
| dpkg-sig | ✔ |
|
36
|
-
| createrepo | ✔ |
|
37
|
-
| rpm | ✔ |
|
38
|
-
|
37
|
+
+------------+--------+-----------------------+
|
38
|
+
| Binary | Status | Purpose |
|
39
|
+
+------------+--------+-----------------------+
|
40
|
+
| aptly | ✔ | Manage apt repository |
|
41
|
+
| dpkg-sig | ✔ | Sign deb packages |
|
42
|
+
| createrepo | ✔ | Manage rpm repository |
|
43
|
+
| rpm | ✔ | Sign rpm packages |
|
44
|
+
| git | ✔ | Use git publisher |
|
45
|
+
+------------+--------+-----------------------+
|
39
46
|
```
|
40
47
|
|
41
48
|
For managing deb repositories:
|
@@ -48,6 +55,12 @@ For managing rpm repositories:
|
|
48
55
|
|
49
56
|
```bash
|
50
57
|
sudo apt install createrepo rpm
|
58
|
+
```
|
59
|
+
|
60
|
+
For using the git publisher:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
sudo apt install git
|
51
64
|
```
|
52
65
|
|
53
66
|
n.b `createrepo` is not normally available for Debian and derrivates (including Ubuntu). This tool
|
@@ -56,6 +69,12 @@ sudo apt install createrepo rpm
|
|
56
69
|
|
57
70
|
You can get our build of createrepo from our [deb repository](https://deb.staker.ltd/).
|
58
71
|
|
72
|
+
Pro tip: if the pkg signing fails because gpg can't open the output device, add this to your shell config:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
export GPG_TTY=$(tty)
|
76
|
+
```
|
77
|
+
|
59
78
|
## How to use
|
60
79
|
|
61
80
|
```bash
|
@@ -65,11 +84,41 @@ repo-mgr help
|
|
65
84
|
# create repo
|
66
85
|
## --path => a local directory where the repository is published - no remote support at the moment
|
67
86
|
## GPGKEYID is expected as log keyid i.e 16 hex chars
|
68
|
-
|
87
|
+
## --publisher - is optional i.e you can still manually publish a local repository
|
88
|
+
repo-mgr upsert-repo --name foo --type deb --path path/to/foo --keyid GPGKEYID --publisher git
|
69
89
|
|
70
90
|
# sign package, add to repository, and update local repo (includes sign repo release manifest)
|
71
91
|
# the local repo is exported to the path indicated in upsert-repo
|
92
|
+
# the git publisher also commits the changes as the path for upsert-repo is expected to be
|
93
|
+
# a git repository
|
72
94
|
repo-mgr add-pkg --repo foo --path path/to/bar_0.0.1_amd64.deb
|
95
|
+
|
96
|
+
# publish the repository to a remote - for git publisher this means doing git push
|
97
|
+
repo-mgr sync --repo foo
|
98
|
+
|
99
|
+
# download repo from remote e.g when changing development machines
|
100
|
+
# repo-mgr upsert-repo (...) # needed only once if the machine is brand new
|
101
|
+
## --url must point to the root of the remote repository
|
102
|
+
## i.e where the pool and dists directories can be found for deb repos
|
103
|
+
## --keyring is required for deb repositories; it is a file from
|
104
|
+
## /usr/share/keyrings - the base path is automatically prepended
|
105
|
+
## WARNING: the packages are not signed when imported via this method!
|
106
|
+
repo-mgr dl-repo --repo foo --type deb --url https://apt.example.com --keyring foo-keyring.gpg
|
107
|
+
|
108
|
+
## --arch must be specified as each rpm repo arch is self-contained
|
109
|
+
## the remote repository gpg key must be imported into the user's keyring and trusted
|
110
|
+
## prior to starting the import process from remote rpm repo
|
111
|
+
repo-mgr dl-repo --repo foo --type rpm --url https://rpm.example.com --arch x86_64
|
112
|
+
```
|
113
|
+
|
114
|
+
## Migrating from v0.1
|
115
|
+
|
116
|
+
The package list is stored into a structure that's prone to lose the list upon re-running `upsert-repo` for `v0.1.x` of this gem. For this reason, the package list data structure has been redesigned within repo-mgr's config file.
|
117
|
+
|
118
|
+
So, to migrate from this earlier version, you must run, for every repo:
|
119
|
+
|
120
|
+
```bash
|
121
|
+
repo-mgr rebuild-pkg-list --repo foo
|
73
122
|
```
|
74
123
|
|
75
|
-
|
124
|
+
This rebuilds the data structure in the new config location.
|
@@ -14,14 +14,7 @@ module RepoMgr
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def add_repo(name)
|
17
|
-
|
18
|
-
|
19
|
-
cmd = "aptly -config=#{@aptly_config_file} repo create #{name}"
|
20
|
-
out, status = Open3.capture2e cmd
|
21
|
-
|
22
|
-
unless status.exitstatus.zero?
|
23
|
-
Tools.error "aptly repo create failed with:\n#{out}"
|
24
|
-
end
|
17
|
+
aptly "repo create #{name}" unless aptly_repos.include? name
|
25
18
|
|
26
19
|
repo_config = @config.cfg[:repos][name]
|
27
20
|
|
@@ -34,6 +27,24 @@ module RepoMgr
|
|
34
27
|
save_aptly_config
|
35
28
|
end
|
36
29
|
|
30
|
+
def dl_repo(options)
|
31
|
+
name = options[:repo]
|
32
|
+
url = options[:url]
|
33
|
+
keyring = options[:keyring]
|
34
|
+
|
35
|
+
if keyring.nil?
|
36
|
+
Tools.error 'you must specify a keyring file for deb repo'
|
37
|
+
end
|
38
|
+
|
39
|
+
keyring = "/usr/share/keyrings/#{keyring}"
|
40
|
+
|
41
|
+
aptly "-keyring=#{keyring} mirror create #{name} #{url} stable main"
|
42
|
+
aptly "-keyring=#{keyring} mirror update #{name}", :output
|
43
|
+
aptly "repo import #{name} #{name} Name"
|
44
|
+
aptly "mirror drop #{name}"
|
45
|
+
aptly("repo search #{name}", :return).split.map { |e| "#{e}.deb" }
|
46
|
+
end
|
47
|
+
|
37
48
|
def add_pkg(repo, pkg)
|
38
49
|
sign_pkg repo, pkg
|
39
50
|
repo_add repo, pkg
|
@@ -45,20 +56,20 @@ module RepoMgr
|
|
45
56
|
repo_publish repo
|
46
57
|
end
|
47
58
|
|
48
|
-
def check_sig(pkg, allow_fail
|
59
|
+
def check_sig(pkg, allow_fail: false)
|
49
60
|
out, status = Open3.capture2e "dpkg-sig --verify #{pkg}"
|
50
61
|
|
51
62
|
return out if status.exitstatus.zero? || allow_fail
|
52
63
|
|
53
64
|
Tools.error "unable to check package signature for #{pkg} - "\
|
54
|
-
|
65
|
+
"dpkg-sig returned:\n#{out}"
|
55
66
|
end
|
56
67
|
|
57
68
|
def sign_pkg(repo, pkg)
|
58
|
-
signature = check_sig pkg, true
|
69
|
+
signature = check_sig pkg, allow_fail: true
|
59
70
|
|
60
71
|
unless signature[-6, 5] == 'NOSIG'
|
61
|
-
return puts "-- dpkg-sig returned:\n#{signature.first}"
|
72
|
+
return puts "-- dpkg-sig returned:\n#{signature.split.first}"
|
62
73
|
end
|
63
74
|
|
64
75
|
if @config.cfg[:repos][repo].nil?
|
@@ -73,6 +84,16 @@ module RepoMgr
|
|
73
84
|
Tools.error "unable to sign #{pkg} - dpkg-sig returned:\n#{out}"
|
74
85
|
end
|
75
86
|
|
87
|
+
def rebuild_pkg_list(repo)
|
88
|
+
aptly("-with-packages repo search #{repo}", :return).split.map do |e|
|
89
|
+
"#{e}.deb"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def export(repo)
|
94
|
+
repo_publish repo
|
95
|
+
end
|
96
|
+
|
76
97
|
private
|
77
98
|
|
78
99
|
def init_aptly_config
|
@@ -102,8 +123,6 @@ module RepoMgr
|
|
102
123
|
dependencyVerboseResolve: false,
|
103
124
|
gpgDisableSign: false,
|
104
125
|
gpgDisableVerify: false,
|
105
|
-
# despite the binary being gpg, this must spell gpg2, otherwise aptly
|
106
|
-
# defaults to gpg1 with less than impressive results
|
107
126
|
gpgProvider: 'gpg2',
|
108
127
|
downloadSourcePackages: false,
|
109
128
|
skipLegacyPool: true,
|
@@ -117,36 +136,30 @@ module RepoMgr
|
|
117
136
|
end
|
118
137
|
# rubocop:enable Metrics/MethodLength
|
119
138
|
|
120
|
-
def
|
121
|
-
cmd = "aptly -
|
139
|
+
def aptly(cmd, output = nil)
|
140
|
+
cmd = "aptly -config=#{@aptly_config_file} #{cmd}"
|
122
141
|
out, status = Open3.capture2e cmd
|
123
142
|
|
124
|
-
unless status.exitstatus.zero?
|
125
|
-
|
143
|
+
Tools.error "#{cmd} failed with:\n#{out}" unless status.exitstatus.zero?
|
144
|
+
|
145
|
+
case output
|
146
|
+
when :output
|
147
|
+
puts out
|
148
|
+
when :return
|
149
|
+
out
|
126
150
|
end
|
151
|
+
end
|
127
152
|
|
128
|
-
|
153
|
+
def aptly_repos
|
154
|
+
aptly('-raw repo list', :return).split
|
129
155
|
end
|
130
156
|
|
131
157
|
def aptly_published_repos
|
132
|
-
|
133
|
-
out, status = Open3.capture2e cmd
|
134
|
-
|
135
|
-
unless status.exitstatus.zero?
|
136
|
-
Tools.error "aptly publish list failed with:\n#{out}"
|
137
|
-
end
|
138
|
-
|
139
|
-
out.split("\n")
|
158
|
+
aptly('-raw publish list', :return).split("\n")
|
140
159
|
end
|
141
160
|
|
142
161
|
def aptly_publish_drop(repo)
|
143
|
-
|
144
|
-
"filesystem:#{repo}:"
|
145
|
-
out, status = Open3.capture2e cmd
|
146
|
-
|
147
|
-
return if status.exitstatus.zero?
|
148
|
-
|
149
|
-
Tools.error "aptly publish drop failed with:\n#{out}"
|
162
|
+
aptly "publish drop stable filesystem:#{repo}:"
|
150
163
|
end
|
151
164
|
|
152
165
|
def save_aptly_config
|
@@ -154,23 +167,12 @@ module RepoMgr
|
|
154
167
|
end
|
155
168
|
|
156
169
|
def repo_add(repo, pkg)
|
157
|
-
|
158
|
-
out, status = Open3.capture2e cmd
|
159
|
-
|
160
|
-
return if status.exitstatus.zero?
|
161
|
-
|
162
|
-
Tools.error "aptly repo add failed with:\n#{out}"
|
170
|
+
aptly "repo add #{repo} #{pkg}"
|
163
171
|
end
|
164
172
|
|
165
173
|
def repo_rm(repo, pkg)
|
166
174
|
package = File.basename pkg, File.extname(pkg)
|
167
|
-
|
168
|
-
"#{repo} #{package}"
|
169
|
-
out, status = Open3.capture2e cmd
|
170
|
-
|
171
|
-
return if status.exitstatus.zero?
|
172
|
-
|
173
|
-
Tools.error "aptly repo remove failed with:\n#{out}"
|
175
|
+
aptly "repo remove #{repo} #{package}"
|
174
176
|
end
|
175
177
|
|
176
178
|
def repo_publish(repo)
|
@@ -179,14 +181,8 @@ module RepoMgr
|
|
179
181
|
end
|
180
182
|
|
181
183
|
keyid = @config.cfg[:repos][repo][:keyid]
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
out, status = Open3.capture2e cmd
|
186
|
-
|
187
|
-
return if status.exitstatus.zero?
|
188
|
-
|
189
|
-
Tools.error "aptly publish repo failed with:\n#{out}"
|
184
|
+
aptly "-distribution=stable -gpg-key=#{keyid} publish repo #{repo} "\
|
185
|
+
"filesystem:#{repo}:"
|
190
186
|
end
|
191
187
|
end
|
192
188
|
end
|
@@ -1,7 +1,12 @@
|
|
1
1
|
# frozen_string_literal: false
|
2
2
|
|
3
|
+
require 'zlib'
|
3
4
|
require 'open3'
|
4
5
|
require 'gpgme'
|
6
|
+
require 'digest'
|
7
|
+
require 'faraday'
|
8
|
+
require 'nokogiri'
|
9
|
+
require 'stringio'
|
5
10
|
require 'fileutils'
|
6
11
|
|
7
12
|
module RepoMgr
|
@@ -27,6 +32,30 @@ module RepoMgr
|
|
27
32
|
sync_repo repo
|
28
33
|
end
|
29
34
|
|
35
|
+
def dl_repo(options)
|
36
|
+
name = options[:repo]
|
37
|
+
url = options[:url]
|
38
|
+
arch = options[:arch]
|
39
|
+
|
40
|
+
Tools.error 'you must specify an arch name for rpm repo' if arch.nil?
|
41
|
+
|
42
|
+
tmpdir = "/tmp/#{name}/#{arch}"
|
43
|
+
dest_dir = "#{@config.cfg_dir}/rpms/#{name}/#{arch}"
|
44
|
+
make_dirs tmpdir, dest_dir
|
45
|
+
|
46
|
+
repomd = dl_repomd url, arch, tmpdir
|
47
|
+
pkgs = dl_primary url, arch, tmpdir, repomd
|
48
|
+
|
49
|
+
pkgs.each do |hash, file|
|
50
|
+
dl_pkg hash, url, arch, file, tmpdir
|
51
|
+
copy_pkg "#{tmpdir}/#{file}", dest_dir
|
52
|
+
end
|
53
|
+
|
54
|
+
sync_repo name
|
55
|
+
|
56
|
+
pkgs.values
|
57
|
+
end
|
58
|
+
|
30
59
|
def remove_pkg(repo, pkg)
|
31
60
|
name = File.basename pkg
|
32
61
|
arch = extract_arch pkg
|
@@ -43,7 +72,7 @@ module RepoMgr
|
|
43
72
|
return out if status.exitstatus.zero?
|
44
73
|
|
45
74
|
Tools.error "unable to check package signature for #{pkg} - "\
|
46
|
-
|
75
|
+
"rpm -K returned:\n#{out}"
|
47
76
|
end
|
48
77
|
|
49
78
|
def sign_pkg(repo, pkg)
|
@@ -69,6 +98,16 @@ module RepoMgr
|
|
69
98
|
Tools.error "unable to sign #{pkg} - rpm --addsign returned:\n#{out}"
|
70
99
|
end
|
71
100
|
|
101
|
+
def rebuild_pkg_list(repo)
|
102
|
+
Dir["#{@config.cfg_dir}/rpms/#{repo}/**/*.rpm"].map do |e|
|
103
|
+
File.basename e
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def export(repo)
|
108
|
+
sync_repo repo
|
109
|
+
end
|
110
|
+
|
72
111
|
private
|
73
112
|
|
74
113
|
def extract_arch(pkg)
|
@@ -104,7 +143,7 @@ module RepoMgr
|
|
104
143
|
return if status.exitstatus.zero?
|
105
144
|
|
106
145
|
Tools.error "unable to create repo for #{arch} - createrepo "\
|
107
|
-
|
146
|
+
"returned:\n#{out}"
|
108
147
|
end
|
109
148
|
|
110
149
|
def sign_repo(repo)
|
@@ -121,6 +160,83 @@ module RepoMgr
|
|
121
160
|
|
122
161
|
File.write 'repomd.xml.asc', signature
|
123
162
|
end
|
163
|
+
|
164
|
+
def faraday_dl(url, tmpdir, file = File.basename(url))
|
165
|
+
puts "-- Download #{file}"
|
166
|
+
File.write "#{tmpdir}/#{file}", Faraday.get(url).body
|
167
|
+
end
|
168
|
+
|
169
|
+
def dl_repomd(url, arch, tmpdir)
|
170
|
+
faraday_dl "#{url}/#{arch}/repodata/repomd.xml", tmpdir
|
171
|
+
faraday_dl "#{url}/#{arch}/repodata/repomd.xml.asc", tmpdir
|
172
|
+
|
173
|
+
valid = false
|
174
|
+
crypto = GPGME::Crypto.new armor: true
|
175
|
+
sig = GPGME::Data.new File.read "#{tmpdir}/repomd.xml.asc"
|
176
|
+
data = File.read "#{tmpdir}/repomd.xml"
|
177
|
+
crypto.verify(sig, signed_text: data) do |sign|
|
178
|
+
valid = sign.valid?
|
179
|
+
end
|
180
|
+
|
181
|
+
unless valid == true
|
182
|
+
Tools.error "unable to check signature for #{tmpdir}/repomd.xml"
|
183
|
+
end
|
184
|
+
|
185
|
+
Nokogiri::XML data
|
186
|
+
end
|
187
|
+
|
188
|
+
def extract_pkgs(primary)
|
189
|
+
pkgs = {}
|
190
|
+
|
191
|
+
primary.css('package').each do |pkg|
|
192
|
+
pkg_hash = pkg.at('checksum[type=sha256]').text
|
193
|
+
pkgs[pkg_hash] = pkg.at('location')['href']
|
194
|
+
end
|
195
|
+
|
196
|
+
pkgs
|
197
|
+
end
|
198
|
+
|
199
|
+
def dl_primary(url, arch, tmpdir, repomd)
|
200
|
+
hash = repomd.at('data[type=primary]').at('checksum').text
|
201
|
+
primary = "#{url}/#{arch}/repodata/#{hash}-primary.xml.gz"
|
202
|
+
|
203
|
+
faraday_dl primary, tmpdir, 'primary.xml.gz'
|
204
|
+
|
205
|
+
primary_xml_gz = "#{tmpdir}/primary.xml.gz"
|
206
|
+
fl_hash = Digest::SHA256.hexdigest(File.read(primary_xml_gz))
|
207
|
+
|
208
|
+
unless fl_hash == hash
|
209
|
+
Tools.error "failed hash check for #{tmpdir}/primary.xml.gz"
|
210
|
+
end
|
211
|
+
|
212
|
+
primary_xml_gz = File.read("#{tmpdir}/primary.xml.gz")
|
213
|
+
primary = Zlib::GzipReader.new(StringIO.new(primary_xml_gz)).read
|
214
|
+
File.write("#{tmpdir}/primary.xml", primary)
|
215
|
+
|
216
|
+
extract_pkgs Nokogiri::XML(primary)
|
217
|
+
end
|
218
|
+
|
219
|
+
def dl_pkg(hash, url, arch, file, tmpdir)
|
220
|
+
unless File.exist? "#{tmpdir}/#{file}"
|
221
|
+
faraday_dl "#{url}/#{arch}/#{file}", tmpdir
|
222
|
+
end
|
223
|
+
|
224
|
+
fl_hash = Digest::SHA256.hexdigest(File.read("#{tmpdir}/#{file}"))
|
225
|
+
|
226
|
+
return if fl_hash == hash
|
227
|
+
|
228
|
+
Tools.error "failed hash check for #{tmpdir}/#{file}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def copy_pkg(file, destdir)
|
232
|
+
puts "-- Copy to repo-mgr #{File.basename(file)}"
|
233
|
+
FileUtils.cp file, destdir
|
234
|
+
end
|
235
|
+
|
236
|
+
def make_dirs(tmpdir, dest_dir)
|
237
|
+
FileUtils.mkdir_p tmpdir
|
238
|
+
FileUtils.mkdir_p dest_dir
|
239
|
+
end
|
124
240
|
end
|
125
241
|
end
|
126
242
|
end
|
data/lib/repo_mgr/cli.rb
CHANGED
@@ -8,6 +8,7 @@ require 'terminal-table'
|
|
8
8
|
require_relative 'tools'
|
9
9
|
require_relative 'config'
|
10
10
|
require_relative 'backends'
|
11
|
+
require_relative 'publishers'
|
11
12
|
|
12
13
|
module RepoMgr
|
13
14
|
# implements CLI interface
|
@@ -20,20 +21,27 @@ module RepoMgr
|
|
20
21
|
%w[deb rpm]
|
21
22
|
end
|
22
23
|
|
24
|
+
def self.publishers
|
25
|
+
%w[git]
|
26
|
+
end
|
27
|
+
|
23
28
|
desc 'check-depends', 'Check dependencies'
|
24
29
|
|
25
30
|
def check_depends
|
26
31
|
rows = []
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
deps = {
|
33
|
+
'aptly' => 'Manage apt repository',
|
34
|
+
'dpkg-sig' => 'Sign deb packages',
|
35
|
+
'createrepo' => 'Manage rpm repository',
|
36
|
+
'rpm' => 'Sign rpm packages',
|
37
|
+
'git' => 'Use git publisher'
|
38
|
+
}
|
39
|
+
|
40
|
+
deps.each do |bin_dep, purpose|
|
41
|
+
rows << which_depends(bin_dep, purpose)
|
34
42
|
end
|
35
43
|
|
36
|
-
puts Terminal::Table.new headings: %w[Binary Status], rows: rows
|
44
|
+
puts Terminal::Table.new headings: %w[Binary Status Purpose], rows: rows
|
37
45
|
end
|
38
46
|
|
39
47
|
desc 'upsert-repo', 'Create/Update new repository'
|
@@ -45,13 +53,14 @@ module RepoMgr
|
|
45
53
|
desc: 'Directory path where to build the repository'
|
46
54
|
option :keyid, type: :string, required: true, aliases: %w[-k],
|
47
55
|
desc: 'GPG key id used to sign the repository metadata'
|
56
|
+
option :publisher, type: :string, aliases: %w[-r], enum: publishers,
|
57
|
+
desc: 'Publisher used to sync repo data to remote target'
|
48
58
|
|
49
59
|
def upsert_repo
|
50
60
|
FileUtils.mkdir_p options[:path]
|
51
61
|
|
52
62
|
config = Config.new
|
53
|
-
config.upsert_repo options
|
54
|
-
options[:keyid]
|
63
|
+
config.upsert_repo options
|
55
64
|
|
56
65
|
backend = Backends.load options[:type], config
|
57
66
|
backend.add_repo options[:name]
|
@@ -66,12 +75,36 @@ module RepoMgr
|
|
66
75
|
config = Config.new
|
67
76
|
|
68
77
|
config.cfg[:repos].each do |name, repo|
|
69
|
-
rows << [name, repo[:type], repo[:path], repo[:keyid]]
|
78
|
+
rows << [name, repo[:type], repo[:path], repo[:keyid], repo[:publisher]]
|
70
79
|
end
|
71
80
|
|
72
81
|
return puts '-- No repos have been created' if rows.count.zero?
|
73
82
|
|
74
|
-
puts Terminal::Table.new
|
83
|
+
puts Terminal::Table.new(
|
84
|
+
headings: %w[Name Type Path KeyID Publisher], rows: rows
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'dl-repo', 'Download repository from remote endpoint'
|
89
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
90
|
+
desc: 'The repository to download packages into'
|
91
|
+
option :type, type: :string, required: true, aliases: %w[-t],
|
92
|
+
enum: types, desc: 'Repository type'
|
93
|
+
option :url, type: :string, required: true, aliases: %w[-u],
|
94
|
+
desc: 'The URL for the remote repository'
|
95
|
+
option :keyring, type: :string, aliases: %w[-k],
|
96
|
+
desc: 'Name of keyring file if required to auth repository'
|
97
|
+
option :arch, type: :string, aliases: %w[-a],
|
98
|
+
desc: 'Pkg arch if multiple arch are supported by repo'
|
99
|
+
|
100
|
+
def dl_repo
|
101
|
+
backend, config = load_backend options[:type]
|
102
|
+
|
103
|
+
pkgs = backend.dl_repo options
|
104
|
+
|
105
|
+
pkgs.each do |pkg|
|
106
|
+
config.add_pkg options[:repo], pkg
|
107
|
+
end
|
75
108
|
end
|
76
109
|
|
77
110
|
desc 'add-pkg', 'Add package to repository'
|
@@ -85,8 +118,14 @@ module RepoMgr
|
|
85
118
|
backend.add_pkg options[:repo], options[:path]
|
86
119
|
config.add_pkg options[:repo], options[:path]
|
87
120
|
|
121
|
+
pub_type = config.cfg[:repos][options[:repo]][:publisher]
|
122
|
+
if pub_type
|
123
|
+
publisher = Publishers.load pub_type, config
|
124
|
+
publisher.save options[:repo], options[:path]
|
125
|
+
end
|
126
|
+
|
88
127
|
puts "-- Added #{File.basename(options[:path])} to "\
|
89
|
-
|
128
|
+
"#{options[:repo]} repository"
|
90
129
|
end
|
91
130
|
|
92
131
|
desc 'list-pkgs', 'List repository packages'
|
@@ -94,7 +133,7 @@ module RepoMgr
|
|
94
133
|
desc: 'The repository to list the packages from'
|
95
134
|
|
96
135
|
def list_pkgs
|
97
|
-
packages = Config.new.cfg[:
|
136
|
+
packages = Config.new.cfg[:packages][options[:repo]]
|
98
137
|
|
99
138
|
if packages.nil?
|
100
139
|
Tools.error "#{options[:repo]} repo does not have any packages"
|
@@ -118,7 +157,7 @@ module RepoMgr
|
|
118
157
|
config.remove_pkg options[:repo], options[:path]
|
119
158
|
|
120
159
|
puts "-- Removed #{File.basename(options[:path])} from "\
|
121
|
-
|
160
|
+
"#{options[:repo]} repository"
|
122
161
|
end
|
123
162
|
|
124
163
|
desc 'check-sig', 'Check package signature'
|
@@ -130,10 +169,61 @@ module RepoMgr
|
|
130
169
|
puts backend.check_sig options[:path]
|
131
170
|
end
|
132
171
|
|
172
|
+
desc 'rebuild-pkg-list', 'Rebuild package list from local pkg cache'
|
173
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
174
|
+
desc: 'The repository to rebuild pkg list for'
|
175
|
+
def rebuild_pkg_list
|
176
|
+
config = Config.new
|
177
|
+
backend = Backends.load config.cfg[:repos][options[:repo]][:type], config
|
178
|
+
pkgs = backend.rebuild_pkg_list options[:repo]
|
179
|
+
|
180
|
+
pkgs.each do |pkg|
|
181
|
+
config.add_pkg options[:repo], pkg
|
182
|
+
end
|
183
|
+
|
184
|
+
puts "-- Rebuilt #{options[:repo]} repo pkg list"
|
185
|
+
end
|
186
|
+
|
187
|
+
desc 'sync', 'Sync local repo to remote target using repo publisher'
|
188
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
189
|
+
desc: 'The repository to sync to remote target via publisher'
|
190
|
+
def sync
|
191
|
+
config = Config.new
|
192
|
+
pub_type = config.cfg[:repos][options[:repo]][:publisher]
|
193
|
+
|
194
|
+
unless pub_type
|
195
|
+
Tools.error "#{options[:repo]} repo does not have a publisher"
|
196
|
+
end
|
197
|
+
|
198
|
+
publisher = Publishers.load pub_type, config
|
199
|
+
publisher.sync options[:repo]
|
200
|
+
|
201
|
+
puts "-- Synchronised #{options[:repo]} using #{pub_type} publisher"
|
202
|
+
end
|
203
|
+
|
204
|
+
desc 'export', 'Export packages and metadata as local repository'
|
205
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
206
|
+
desc: 'The repository to export'
|
207
|
+
def export
|
208
|
+
config = Config.new
|
209
|
+
backend = Backends.load config.cfg[:repos][options[:repo]][:type], config
|
210
|
+
|
211
|
+
backend.export options[:repo]
|
212
|
+
|
213
|
+
puts "-- Exported #{options[:repo]} repo"
|
214
|
+
end
|
215
|
+
|
133
216
|
private
|
134
217
|
|
218
|
+
def which_depends(bin_dep, purpose)
|
219
|
+
return [bin_dep, '✔'.green, purpose] if Tools.which bin_dep
|
220
|
+
|
221
|
+
[bin_dep, '✘'.red, purpose]
|
222
|
+
end
|
223
|
+
|
135
224
|
def load_backend(path)
|
136
225
|
type = File.extname(path).strip.downcase[1..-1]
|
226
|
+
type = path if type.nil?
|
137
227
|
|
138
228
|
unless CLI.types.include? type
|
139
229
|
Tools.error "unsupported package type #{type}"
|
data/lib/repo_mgr/config.rb
CHANGED
@@ -15,7 +15,9 @@ module RepoMgr
|
|
15
15
|
FileUtils.mkdir_p @cfg_dir
|
16
16
|
|
17
17
|
@cfg_file = "#{@cfg_dir}/repo-mgr.yml"
|
18
|
-
|
18
|
+
unless File.exist? @cfg_file
|
19
|
+
File.write @cfg_file, { repos: {}, packages: {} }.to_yaml
|
20
|
+
end
|
19
21
|
|
20
22
|
@cfg = YAML.load_file @cfg_file
|
21
23
|
end
|
@@ -24,17 +26,24 @@ module RepoMgr
|
|
24
26
|
File.write @cfg_file, @cfg.to_yaml
|
25
27
|
end
|
26
28
|
|
27
|
-
def upsert_repo(
|
29
|
+
def upsert_repo(options)
|
30
|
+
name = options[:name]
|
31
|
+
type = options[:type]
|
32
|
+
|
28
33
|
if @cfg[:repos][name] && @cfg[:repos][name][:type] != type
|
29
34
|
Tools.error "unable to change type for #{name} repository"
|
30
35
|
end
|
31
36
|
|
32
37
|
@cfg[:repos][name] = {
|
33
38
|
type: type,
|
34
|
-
path: path,
|
35
|
-
keyid: keyid
|
39
|
+
path: options[:path],
|
40
|
+
keyid: options[:keyid]
|
36
41
|
}
|
37
42
|
|
43
|
+
if options[:publisher]
|
44
|
+
@cfg[:repos][name][:publisher] = options[:publisher]
|
45
|
+
end
|
46
|
+
|
38
47
|
save
|
39
48
|
end
|
40
49
|
|
@@ -43,14 +52,15 @@ module RepoMgr
|
|
43
52
|
Tools.error "unable to add packages to #{repo} - repo does not exist"
|
44
53
|
end
|
45
54
|
|
46
|
-
@cfg[:
|
55
|
+
@cfg[:packages] ||= {}
|
56
|
+
@cfg[:packages][repo] ||= []
|
47
57
|
pkg = File.basename path
|
48
58
|
|
49
|
-
if @cfg[:
|
59
|
+
if @cfg[:packages][repo].include?(pkg)
|
50
60
|
Tools.error "you already have #{pkg} in your #{repo} repo"
|
51
61
|
end
|
52
62
|
|
53
|
-
@cfg[:
|
63
|
+
@cfg[:packages][repo] << pkg
|
54
64
|
|
55
65
|
save
|
56
66
|
end
|
@@ -58,12 +68,13 @@ module RepoMgr
|
|
58
68
|
def remove_pkg(repo, path)
|
59
69
|
if @cfg[:repos][repo].nil?
|
60
70
|
Tools.error "unable to remove packages from #{repo} "\
|
61
|
-
|
71
|
+
'- repo does not exist'
|
62
72
|
end
|
63
73
|
|
64
|
-
@cfg[:
|
74
|
+
@cfg[:packages] ||= {}
|
75
|
+
@cfg[:packages][repo] ||= []
|
65
76
|
pkg = File.basename path
|
66
|
-
@cfg[:
|
77
|
+
@cfg[:packages][repo].delete pkg
|
67
78
|
|
68
79
|
save
|
69
80
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
|
5
|
+
module RepoMgr
|
6
|
+
module Publisher
|
7
|
+
# git publisher
|
8
|
+
class Git
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
# method invoked when the local deb/rpm repository is built
|
14
|
+
# for git, this requires a commit into the target git repository
|
15
|
+
# which is the target for deb/rpm repository export
|
16
|
+
def save(repo, pkg)
|
17
|
+
git = ::Git.open @config.cfg[:repos][repo][:path]
|
18
|
+
git.add(all: true)
|
19
|
+
git.commit "Add #{File.basename(pkg)}."
|
20
|
+
end
|
21
|
+
|
22
|
+
# method invoked when the local deb/rpm repository is published
|
23
|
+
# for git, this is pushing to a remote
|
24
|
+
def sync(repo)
|
25
|
+
git = ::Git.open @config.cfg[:repos][repo][:path]
|
26
|
+
git.push(git.remote('origin'), 'main')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require_relative 'publishers/git'
|
4
|
+
|
5
|
+
module RepoMgr
|
6
|
+
# factory loader for RepoMgr::Publisher::Foo objects
|
7
|
+
class Publishers
|
8
|
+
def self.load(publisher, config)
|
9
|
+
@obj ||= {}
|
10
|
+
|
11
|
+
@obj[publisher] ||= Object.const_get(
|
12
|
+
"RepoMgr::Publisher::#{publisher.capitalize}"
|
13
|
+
).new(config)
|
14
|
+
|
15
|
+
@obj[publisher]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: repo-mgr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ștefan Rusu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colored
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.9.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.9.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: git
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.8.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.8.1
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: gpgme
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,20 @@ dependencies:
|
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.13.3
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.13.3
|
41
83
|
- !ruby/object:Gem::Dependency
|
42
84
|
name: terminal-table
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +109,7 @@ dependencies:
|
|
67
109
|
- !ruby/object:Gem::Version
|
68
110
|
version: '1.1'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
112
|
+
name: juwelier
|
71
113
|
requirement: !ruby/object:Gem::Requirement
|
72
114
|
requirements:
|
73
115
|
- - ">="
|
@@ -140,6 +182,8 @@ files:
|
|
140
182
|
- lib/repo_mgr/backends/rpm.rb
|
141
183
|
- lib/repo_mgr/cli.rb
|
142
184
|
- lib/repo_mgr/config.rb
|
185
|
+
- lib/repo_mgr/publishers.rb
|
186
|
+
- lib/repo_mgr/publishers/git.rb
|
143
187
|
- lib/repo_mgr/tools.rb
|
144
188
|
homepage: https://github.com/mr-staker/repo-mgr
|
145
189
|
licenses:
|
@@ -160,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
204
|
- !ruby/object:Gem::Version
|
161
205
|
version: '0'
|
162
206
|
requirements: []
|
163
|
-
rubygems_version: 3.
|
207
|
+
rubygems_version: 3.2.32
|
164
208
|
signing_key:
|
165
209
|
specification_version: 4
|
166
210
|
summary: deb and rpm repository manager
|