repo-mgr 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be8fb6163c79ae7f9956b0237f390c4d7764c23ef6f4ffa24c1531a3726187e0
4
- data.tar.gz: 60346b9d260718f3a03745b6c90b1fdb80c34ac62458cd999d67a2cadc7b840d
3
+ metadata.gz: a977e25ed86d9763543cd445087e3d4e94e6ecf830e707ffb8c9dfb9c5d19541
4
+ data.tar.gz: a7d2ea6036a1d3a4fc3fc2c6f575e7dda9eaa7bc2db1d7bc0b2fe7dcdb459891
5
5
  SHA512:
6
- metadata.gz: 8af9d6f12205d03e88d28ed848331708a9c6313ea377fd9c9144a2a2f88070de904784cc63659a42b9f3d110775caeeea636d90953927ed645039f135873eaf9
7
- data.tar.gz: 4688c21a24223d81a0b7fb1c9694a59e26cc7b125c4466aecf9a3bbc470c74619d6a2aa61f7469c9acc993c9f48ac4267fece461bc9738018bea819f5019214a
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, aptly (which, kind of obviously, manages deb repositories) uses "stable" as distribution and "main" as component.
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's dependencies which must be installed separately.
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
- repo-mgr upsert-repo --name foo --type deb --path path/to/foo --keyid GPGKEYID
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
- You then need to sync the repo to whatever desired target. For the time being, this isn't implemented as the main use case for this tool is publishing into a git repository which is served as GitHub page.
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
- return if aptly_repos.include? name
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 = false)
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
- "dpkg-sig returned:\n#{out}"
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 aptly_repos
121
- cmd = "aptly -raw -config=#{@aptly_config_file} repo list"
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
- Tools.error "aptly repo list failed with:\n#{out}"
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
- out.split("\n")
153
+ def aptly_repos
154
+ aptly('-raw repo list', :return).split
129
155
  end
130
156
 
131
157
  def aptly_published_repos
132
- cmd = "aptly -raw -config=#{@aptly_config_file} publish list"
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
- cmd = "aptly -config=#{@aptly_config_file} publish drop stable "\
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
- cmd = "aptly -config=#{@aptly_config_file} repo add #{repo} #{pkg}"
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
- cmd = "aptly -config=#{@aptly_config_file} repo remove "\
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
- cmd = "aptly -config=#{@aptly_config_file} -distribution=stable "\
183
- "-gpg-key=#{keyid} publish repo #{repo} filesystem:#{repo}:"
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
- "rpm -K returned:\n#{out}"
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
- "returned:\n#{out}"
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
- %w[aptly dpkg-sig createrepo rpm].each do |bin_dep|
29
- rows << if Tools.which bin_dep
30
- [bin_dep, ''.green]
31
- else
32
- [bin_dep, ''.red]
33
- end
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[:name], options[:type], options[:path],
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 headings: %w[Name Type Path KeyID], rows: rows
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
- "#{options[:repo]} repository"
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[:repos][options[:repo]][:packages]
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
- "#{options[:repo]} repository"
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}"
@@ -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
- File.write @cfg_file, { repos: {} }.to_yaml unless File.exist? @cfg_file
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(name, type, path, keyid)
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[:repos][repo][:packages] ||= []
55
+ @cfg[:packages] ||= {}
56
+ @cfg[:packages][repo] ||= []
47
57
  pkg = File.basename path
48
58
 
49
- if @cfg[:repos][repo][:packages].include?(pkg)
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[:repos][repo][:packages] << pkg
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
- '- repo does not exist'
71
+ '- repo does not exist'
62
72
  end
63
73
 
64
- @cfg[:repos][repo][:packages] ||= []
74
+ @cfg[:packages] ||= {}
75
+ @cfg[:packages][repo] ||= []
65
76
  pkg = File.basename path
66
- @cfg[:repos][repo][:packages].delete pkg
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.1.1
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: 2021-04-17 00:00:00.000000000 Z
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: jeweler
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.1.4
207
+ rubygems_version: 3.2.32
164
208
  signing_key:
165
209
  specification_version: 4
166
210
  summary: deb and rpm repository manager