akabei 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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +55 -0
  8. data/Rakefile +14 -0
  9. data/akabei.gemspec +29 -0
  10. data/bin/akabei +9 -0
  11. data/lib/akabei.rb +5 -0
  12. data/lib/akabei/abs.rb +60 -0
  13. data/lib/akabei/archive_utils.rb +75 -0
  14. data/lib/akabei/attr_path.rb +20 -0
  15. data/lib/akabei/builder.rb +108 -0
  16. data/lib/akabei/chroot_tree.rb +81 -0
  17. data/lib/akabei/cli.rb +172 -0
  18. data/lib/akabei/error.rb +4 -0
  19. data/lib/akabei/package.rb +174 -0
  20. data/lib/akabei/package_entry.rb +110 -0
  21. data/lib/akabei/package_info.rb +68 -0
  22. data/lib/akabei/repository.rb +156 -0
  23. data/lib/akabei/signer.rb +75 -0
  24. data/lib/akabei/thor_handler.rb +14 -0
  25. data/lib/akabei/version.rb +3 -0
  26. data/spec/akabei/abs_spec.rb +95 -0
  27. data/spec/akabei/archive_utils_spec.rb +44 -0
  28. data/spec/akabei/builder_spec.rb +129 -0
  29. data/spec/akabei/chroot_tree_spec.rb +108 -0
  30. data/spec/akabei/cli_spec.rb +5 -0
  31. data/spec/akabei/package_entry_spec.rb +70 -0
  32. data/spec/akabei/package_info_spec.rb +17 -0
  33. data/spec/akabei/package_spec.rb +54 -0
  34. data/spec/akabei/repository_spec.rb +157 -0
  35. data/spec/akabei/signer_spec.rb +5 -0
  36. data/spec/data/input/abs.tar.gz +0 -0
  37. data/spec/data/input/htop-vi.tar.gz +0 -0
  38. data/spec/data/input/makepkg.x86_64.conf +140 -0
  39. data/spec/data/input/nkf-2.1.3-1-x86_64.pkg.tar.xz +0 -0
  40. data/spec/data/input/nkf.PKGINFO +22 -0
  41. data/spec/data/input/nkf.tar.gz +0 -0
  42. data/spec/data/input/pacman.x86_64.conf +99 -0
  43. data/spec/data/input/ruby.PKGINFO +38 -0
  44. data/spec/data/input/test.db +0 -0
  45. data/spec/data/input/test.files +0 -0
  46. data/spec/integration/build_spec.rb +105 -0
  47. data/spec/spec_helper.rb +110 -0
  48. metadata +225 -0
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+ require 'akabei/builder'
3
+ require 'akabei/signer'
4
+
5
+ describe Akabei::Builder do
6
+ let(:builder) { described_class.new }
7
+ let(:pkgdest) { test_dest('packages').tap(&:mkpath) }
8
+ let(:package_dir) { test_dest('nkf').tap(&:mkpath) }
9
+ let(:pkgname) { 'nkf-2.1.3-1' }
10
+ let(:arch) { 'x86_64' }
11
+ let(:src_fname) { 'nkf-2.1.3.tar.gz' }
12
+ let(:pkg_fname) { "#{pkgname}-#{arch}.pkg.tar.xz" }
13
+ let(:log_build_fname) { "#{pkgname}-#{arch}-build.log" }
14
+ let(:log_package_fname) { "#{pkgname}-#{arch}-package.log" }
15
+ let(:srcpkg_fname) { "#{pkgname}.src.tar.gz" }
16
+
17
+ before do
18
+ builder.pkgdest = pkgdest
19
+ end
20
+
21
+ describe '#build_package' do
22
+ let(:chroot) { double('ChrootTree') }
23
+
24
+ before do
25
+ expect(chroot).to receive(:makechrootpkg).once.with(package_dir.to_s, anything) { |dir, env|
26
+ expect(env[:SRCDEST]).to be_directory
27
+ expect(env[:PKGDEST]).to be_directory
28
+ expect(env[:LOGDEST]).to be_directory
29
+
30
+ env[:PKGDEST].join(pkg_fname).open('w') {}
31
+ env[:SRCDEST].join(src_fname).open('w') {}
32
+ env[:LOGDEST].join(log_build_fname).open('w') {}
33
+ env[:LOGDEST].join(log_package_fname).open('w') {}
34
+ }
35
+ end
36
+
37
+ it 'executes makechrootpkg' do
38
+ packages = builder.build_package(package_dir, chroot)
39
+ expect(packages.size).to eq(1)
40
+ package = packages.first
41
+ expect(package.path).to eq(pkgdest.join(pkg_fname))
42
+ expect(package.path).to be_readable
43
+ end
44
+
45
+ context 'with signer' do
46
+ let(:signer) { double('Signer') }
47
+
48
+ before do
49
+ builder.signer = signer
50
+ end
51
+
52
+ it 'creates a detached signature' do
53
+ expect(signer).to receive(:detach_sign).once { |path|
54
+ File.open("#{path}.sig", 'w') {}
55
+ }
56
+
57
+ packages = builder.build_package(package_dir, chroot)
58
+ expect(packages.size).to eq(1)
59
+ package = packages.first
60
+ expect(pkgdest.join("#{pkg_fname}.sig")).to be_readable
61
+ end
62
+
63
+ it "doesn't leave packages if signing failed" do
64
+ expect(signer).to receive(:detach_sign).once.and_raise(Akabei::Signer::InvalidSignature.new(double('path'), double('from')))
65
+
66
+ expect { builder.build_package(package_dir, chroot) }.to raise_error(Akabei::Signer::InvalidSignature)
67
+ expect(pkgdest.join(pkg_fname)).to_not be_readable
68
+ end
69
+ end
70
+
71
+ context 'with srcdest' do
72
+ let(:dest) { test_dest('sources').tap(&:mkpath) }
73
+
74
+ before do
75
+ builder.srcdest = dest
76
+ end
77
+
78
+ it 'stores sources' do
79
+ builder.build_package(package_dir, chroot)
80
+ expect(dest.join(src_fname)).to be_readable
81
+ end
82
+ end
83
+
84
+ context 'with logdest' do
85
+ let(:dest) { test_dest('logs').tap(&:mkpath) }
86
+
87
+ before do
88
+ builder.logdest = dest
89
+ end
90
+
91
+ it 'stores logs' do
92
+ builder.build_package(package_dir, chroot)
93
+ expect(dest.join(log_build_fname)).to be_readable
94
+ expect(dest.join(log_package_fname)).to be_readable
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#with_source_package' do
100
+ before do
101
+ expect(builder).to receive(:system).once.with(any_args) { |env, makepkg, source, opts|
102
+ srcdest = Pathname.new(env['SRCDEST'])
103
+ srcpkgdest = Pathname.new(env['SRCPKGDEST'])
104
+ builddir = Pathname.new(env['BUILDDIR'])
105
+ expect(srcdest).to be_directory
106
+ expect(srcpkgdest).to be_directory
107
+ expect(builddir).to be_directory
108
+
109
+ expect(makepkg).to eq('makepkg')
110
+ expect(source).to eq('--source')
111
+ expect(opts[:chdir]).to eq(package_dir)
112
+
113
+ # Simulate `makepkg --source`
114
+ srcdest.join(src_fname).open('w') {}
115
+ srcpkgdest.join(srcpkg_fname).open('w') { |f| f.write('AKABEI SPEC') }
116
+ builddir.join('nkf').mkpath
117
+ Pathname.new(opts[:chdir]).join(srcpkg_fname).make_symlink(srcpkgdest.join(srcpkg_fname))
118
+ true
119
+ }
120
+ end
121
+
122
+ it 'creates only source package' do
123
+ builder.with_source_package(package_dir) do |srcpkg|
124
+ expect(srcpkg).to be_readable
125
+ expect(srcpkg.read).to eq('AKABEI SPEC')
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+ require 'akabei/chroot_tree'
3
+
4
+ describe Akabei::ChrootTree do
5
+ let(:arch) { 'x86_64' }
6
+ let(:chroot_root) { Pathname.new('/tmp') }
7
+ let(:chroot) { described_class.new(chroot_root, arch) }
8
+
9
+ describe '#with_chroot' do
10
+ let(:action) { double('some action') }
11
+
12
+ before do
13
+ allow(chroot).to receive(:execute) { nil }
14
+ expect(action).to receive(:call).once
15
+ end
16
+
17
+ context 'without root' do
18
+ let(:chroot_root) { nil }
19
+
20
+ it 'creates a temporary chroot' do
21
+ expect(chroot).to receive(:mkarchroot).once
22
+ expect(Dir).to receive(:mktmpdir).once.and_call_original
23
+
24
+ chroot.with_chroot { action.call }
25
+ end
26
+ end
27
+
28
+ context 'with root' do
29
+ it 'uses given root' do
30
+ expect(Dir).to_not receive(:mktmpdir)
31
+ expect(chroot).to receive(:execute).once.with(any_args) { |*args|
32
+ expect(args[0]).to eq('mkarchroot')
33
+ expect(args[1]).to eq(chroot_root.join('root').to_s)
34
+ }
35
+
36
+ chroot.with_chroot { action.call }
37
+ end
38
+ end
39
+
40
+ it 'respects makepkg_config' do
41
+ path = '/path/to/makepkg.conf'
42
+ chroot.makepkg_config = path
43
+ expect(chroot).to receive(:execute).once.with(any_args) { |*args|
44
+ expect(args[0]).to eq('mkarchroot')
45
+ expect(args.each_cons(2)).to include(['-M', path])
46
+ }
47
+
48
+ chroot.with_chroot { action.call }
49
+ end
50
+
51
+ it 'respects pacman_config' do
52
+ path = '/path/to/pacman.conf'
53
+ chroot.pacman_config = path
54
+ expect(chroot).to receive(:execute).once.with(any_args) { |*args|
55
+ expect(args[0]).to eq('mkarchroot')
56
+ expect(args.each_cons(2)).to include(['-C', path])
57
+ }
58
+
59
+ chroot.with_chroot { action.call }
60
+ end
61
+ end
62
+
63
+ describe '#execute' do
64
+ let(:command) { %w[rm -rf /] }
65
+ let(:opts) { { chdir: '/' } }
66
+
67
+ it 'calls sudo and setarch' do
68
+ expect(chroot).to receive(:system).once.with(any_args) { |*args|
69
+ expect(args).to eq(%W[sudo setarch #{arch}] + command + [opts])
70
+ }
71
+
72
+ stdout = capture_stdout do
73
+ chroot.execute(*command + [opts])
74
+ end
75
+ expect(stdout).to include('rm -rf /')
76
+ end
77
+
78
+ context 'with :env keyword' do
79
+ let(:key) { :FOO }
80
+ let(:value) { 'BAR' }
81
+
82
+ it 'calls sudo, setarch and env' do
83
+ expect(chroot).to receive(:system).once.with(any_args) { |*args|
84
+ expected_opts = opts.dup
85
+ expected_opts.delete(:env)
86
+ expect(args).to eq(%W[sudo setarch #{arch} env #{key}=#{value}] + command + [expected_opts])
87
+ }.and_return(true)
88
+
89
+ opts.merge!(env: { key => value })
90
+ stdout = capture_stdout do
91
+ chroot.execute(*command, opts)
92
+ end
93
+ expect(stdout).to include('rm -rf /')
94
+ expect(opts).to have_key(:env)
95
+ end
96
+
97
+ context 'with command failure' do
98
+ before do
99
+ allow(chroot).to receive(:system).and_return(false)
100
+ end
101
+
102
+ it 'raises an error' do
103
+ expect { capture_stdout { chroot.execute(command + [opts]) } }.to raise_error(Akabei::ChrootTree::CommandFailed)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'akabei/cli'
3
+
4
+ describe Akabei::CLI do
5
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+ require 'akabei/package_entry'
3
+
4
+ describe Akabei::PackageEntry do
5
+ let(:entry) { described_class.new }
6
+
7
+ describe '#write_desc' do
8
+ it 'writes desc attributes' do
9
+ entry.add('name', 'akabei')
10
+ entry.add('desc', 'Custom repository manager for ArchLinux pacman')
11
+ entry.add('depends', 'ruby')
12
+
13
+ io = StringIO.new
14
+ entry.write_desc(io)
15
+ expect(io.string).to include('akabei')
16
+ expect(io.string).to include('pacman')
17
+ expect(io.string).to_not include('ruby')
18
+ end
19
+
20
+ it 'rejects multiple descs' do
21
+ entry.add('desc', 'Custom repository manager for ArchLinux pacman')
22
+ expect { entry.add('desc', 'Awful repository manager') }.to raise_error(Akabei::Error)
23
+ expect(entry.desc).not_to include('Awful')
24
+ end
25
+
26
+ it 'rejects unknown attribute' do
27
+ expect { entry.add('akabei', 'akabei') }.to raise_error(Akabei::Error)
28
+ end
29
+ end
30
+
31
+ describe '#write_depends' do
32
+ it 'writes depends attributes' do
33
+ entry.add('name', 'akabei')
34
+ entry.add('depends', 'ruby')
35
+ entry.add('makedepends', 'gcc')
36
+ entry.add('makedepends', 'rubygems')
37
+
38
+ io = StringIO.new
39
+ entry.write_depends(io)
40
+ expect(io.string).not_to include('akabei')
41
+ expect(io.string).to include('ruby')
42
+ expect(io.string).to include('gcc')
43
+ expect(io.string).to include('rubygems')
44
+ end
45
+ end
46
+
47
+ describe '#write_files' do
48
+ it 'writes files attribute' do
49
+ entry.add('name', 'gcc')
50
+ entry.add('files', '/usr/bin/gcov')
51
+ entry.add('files', '/usr/bin/g++')
52
+
53
+ io = StringIO.new
54
+ entry.write_files(io)
55
+ expect(io.string).to_not include('gcc')
56
+ expect(io.string).to include('gcov')
57
+ expect(io.string).to include('g++')
58
+ end
59
+
60
+ it 'warns if files is empty' do
61
+ entry.add('name', 'gcc')
62
+ entry.add('depends', 'glibc')
63
+
64
+ io = StringIO.new
65
+ stderr = capture_stderr { entry.write_files(io) }
66
+ expect(io.string).to be_empty
67
+ expect(stderr).to include('empty')
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'akabei/package'
3
+
4
+ describe Akabei::PackageInfo do
5
+ describe '.parse' do
6
+ it 'parses .PKGINFO' do
7
+ pkginfo = described_class.parse(test_input('ruby.PKGINFO').read)
8
+ expect(pkginfo.pkgver).to eq('2.1.0-2')
9
+ expect(pkginfo.pkgbase).to eq('ruby')
10
+ expect(pkginfo.provides).to match_array(%w[rake rubygems])
11
+ expect(pkginfo.group).to eq([])
12
+
13
+ pkginfo = described_class.parse(test_input('nkf.PKGINFO').read)
14
+ expect(pkginfo.pkgbase).to be_nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'akabei/package'
3
+
4
+ describe Akabei::Package do
5
+ let(:package) { described_class.new(pkg_path) }
6
+ let(:pkg_path) { test_input('nkf-2.1.3-1-x86_64.pkg.tar.xz') }
7
+
8
+ describe '#pkginfo' do
9
+ it 'returns PackageInfo' do
10
+ pkginfo = package.pkginfo
11
+ expect(package.db_name).to eq('nkf-2.1.3-1')
12
+ end
13
+
14
+ context "when pkg_path doesn't exist" do
15
+ let(:pkg_path) { test_dest('does_not_exist.pkg.tar.xz') }
16
+
17
+ it 'raises an error' do
18
+ expect { package.pkginfo }.to raise_error(Archive::Error)
19
+ end
20
+ end
21
+
22
+ context 'with invalid archive' do
23
+ let(:pkg_path) { test_input('nkf.tar.gz') }
24
+
25
+ it 'raises an error' do
26
+ expect { package.pkginfo }.to raise_error(Akabei::Error)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#to_entry' do
32
+ it 'returns PackageEntry' do
33
+ entry = package.to_entry
34
+ expect(entry.sha256sum).to eq('8543fc47ce33a24bc6c0670b045f4b0381dff1472df895879e9a2cf86835a57b')
35
+ expect(entry.pgpsig).to be_nil
36
+ end
37
+
38
+ context 'with detached signature file' do
39
+ let(:pkg_path) { test_dest('nkf.pkg.tar.xz') }
40
+ let(:signature) { 'SOME SIGNATURE' }
41
+
42
+ before do
43
+ FileUtils.cp(test_input('nkf-2.1.3-1-x86_64.pkg.tar.xz'), pkg_path)
44
+ File.open("#{pkg_path}.sig", 'w') { |f| f.write(signature) }
45
+ end
46
+
47
+ it 'returns PackageEntry with pgpsig' do
48
+ entry = package.to_entry
49
+ expect(entry.sha256sum).to eq('8543fc47ce33a24bc6c0670b045f4b0381dff1472df895879e9a2cf86835a57b')
50
+ expect(entry.pgpsig).to eq(Base64.strict_encode64(signature))
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+ require 'akabei/repository'
3
+ require 'akabei/signer'
4
+
5
+ describe Akabei::Repository do
6
+ let(:repo) { described_class.new }
7
+ let(:db_path) { test_input('test.db') }
8
+
9
+ describe '#load' do
10
+ context 'without database' do
11
+ let(:db_path) { test_dest('repo.db') }
12
+
13
+ it 'does nothing' do
14
+ repo.load(db_path)
15
+ expect(repo.count).to eq(0)
16
+ end
17
+ end
18
+
19
+ context 'with valid database' do
20
+ it 'loads package entries' do
21
+ repo.load(db_path)
22
+ entry = repo['htop-vi']
23
+ expect(entry).to_not be_nil
24
+ expect(entry.files).to be_empty
25
+ end
26
+ end
27
+
28
+ context 'with invalid database' do
29
+ let(:db_path) { test_input('abs.tar.gz') }
30
+
31
+ it 'raises an error' do
32
+ expect { repo.load(db_path) }.to raise_error(Akabei::Error)
33
+ end
34
+ end
35
+
36
+ context 'with signer' do
37
+ let(:signer) { double('Signer') }
38
+ let(:db_path) { test_dest('test.db') }
39
+ let(:sig_path) { test_dest("test.db.sig") }
40
+
41
+ before do
42
+ repo.signer = signer
43
+ end
44
+
45
+ it 'skips verification if signature is absent' do
46
+ expect { repo.load(db_path) }.to_not raise_error
47
+ end
48
+
49
+ context 'with signature' do
50
+ before do
51
+ FileUtils.cp(test_input('test.db'), db_path)
52
+ sig_path.open('w') {}
53
+ end
54
+
55
+ it 'loads package entries if the signature is valid' do
56
+ expect(signer).to receive(:verify!).with(db_path)
57
+ repo.load(db_path)
58
+ expect(repo['htop-vi']).not_to be_nil
59
+ end
60
+
61
+ it 'raises an error if the signature is invalid' do
62
+ expect(signer).to receive(:verify!).with(db_path).and_raise(Akabei::Signer::InvalidSignature.new(double('path'), double('from')))
63
+ expect { repo.load(db_path) }.to raise_error(Akabei::Signer::InvalidSignature)
64
+ expect(repo.count).to eq(0)
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'with files' do
70
+ let(:db_path) { test_input('test.files') }
71
+
72
+ it 'loads package entries with files' do
73
+ repo.load(db_path)
74
+ entry = repo['htop-vi']
75
+ expect(entry.files).to_not be_empty
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '#add' do
81
+ it 'adds an entry' do
82
+ package = double('package')
83
+ entry = double('entry')
84
+ allow(package).to receive(:db_name).and_return('nkf-2.1.3-1')
85
+ expect(package).to receive(:to_entry).and_return(entry)
86
+ allow(entry).to receive(:name).and_return('nkf')
87
+
88
+ repo.add(package)
89
+ expect(repo['nkf']).to eql(entry)
90
+ end
91
+ end
92
+
93
+ describe '#remove' do
94
+ before do
95
+ repo.load(db_path)
96
+ end
97
+
98
+ context 'with entry present' do
99
+ it 'removes the package entry' do
100
+ expect { repo.remove('htop-vi') }.to change { repo.count }.by(-1)
101
+ end
102
+ end
103
+
104
+ context 'with package absent' do
105
+ it 'does nothing' do
106
+ expect { repo.remove('nkf') }.not_to change { repo.count }
107
+ end
108
+ end
109
+ end
110
+
111
+ describe '#save' do
112
+ let(:dest_path) { test_dest('test.db') }
113
+
114
+ before do
115
+ repo.load(db_path)
116
+ end
117
+
118
+ it 'stores repository database' do
119
+ repo.save(dest_path)
120
+ expect(dest_path).to be_readable
121
+ new_repo = described_class.load(dest_path)
122
+ expect(repo).to eq(new_repo)
123
+ end
124
+
125
+ context 'with signer' do
126
+ let(:signer) { double('Signer') }
127
+
128
+ before do
129
+ repo.signer = signer
130
+ end
131
+
132
+ it 'stores repository database and sign it' do
133
+ expect(signer).to receive(:detach_sign).once.with(dest_path) { |path|
134
+ File.open("#{path}.sig", 'w') {}
135
+ }
136
+
137
+ repo.save(dest_path)
138
+ expect(Pathname.new("#{dest_path}.sig")).to be_readable
139
+ end
140
+ end
141
+
142
+ context 'with include_files' do
143
+ let(:db_path) { test_input('test.files') }
144
+
145
+ before do
146
+ repo.include_files = true
147
+ end
148
+
149
+ it 'stores repository database' do
150
+ repo.save(dest_path)
151
+ new_repo = described_class.load(dest_path)
152
+ new_repo.include_files = true
153
+ expect(repo).to eq(new_repo)
154
+ end
155
+ end
156
+ end
157
+ end