dpkg-s3 0.3.1 → 0.4.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 +1 -1
- data/bin/dpkg-s3 +19 -4
- data/lib/dpkg/s3.rb +3 -2
- data/lib/dpkg/s3/cli.rb +572 -574
- data/lib/dpkg/s3/lock.rb +65 -53
- data/lib/dpkg/s3/manifest.rb +128 -126
- data/lib/dpkg/s3/package.rb +267 -282
- data/lib/dpkg/s3/release.rb +139 -141
- data/lib/dpkg/s3/templates/package.erb +1 -1
- data/lib/dpkg/s3/utils.rb +112 -114
- metadata +37 -9
data/lib/dpkg/s3/lock.rb
CHANGED
@@ -1,67 +1,79 @@
|
|
1
|
-
#
|
2
|
-
require "tempfile"
|
3
|
-
require "socket"
|
4
|
-
require "etc"
|
5
|
-
require "securerandom"
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
require 'tempfile'
|
4
|
+
require 'socket'
|
5
|
+
require 'etc'
|
6
|
+
require 'securerandom'
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
8
|
+
# Dpkg is the root module for all storage modules including S3
|
9
|
+
module Dpkg
|
10
|
+
# S3 storage module resposible of handling packages on S3 including upload, delete
|
11
|
+
module S3
|
12
|
+
# Lock is resposible of creating lock file on S3 to ensure when multiple instances of uploads will
|
13
|
+
# not conflict with each other
|
14
|
+
class Lock
|
15
|
+
attr_accessor :user, :host
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
attempts += 1
|
25
|
-
throw "Unable to obtain a lock after #{max_attempts}, giving up." if attempts > max_attempts
|
26
|
-
sleep(wait)
|
17
|
+
def initialize
|
18
|
+
@user = nil
|
19
|
+
@host = nil
|
27
20
|
end
|
28
|
-
end
|
29
21
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
lockfile.close
|
22
|
+
class << self
|
23
|
+
def locked?(codename, component = nil, architecture = nil, cache_control = nil)
|
24
|
+
Dpkg::S3::Utils.s3_exists?(lock_path(codename, component, architecture, cache_control))
|
25
|
+
end
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
def wait_for_lock(codename, component = nil, architecture = nil, cache_control = nil)
|
28
|
+
max_attempts = 60
|
29
|
+
wait = 10
|
30
|
+
attempts = 0
|
31
|
+
while locked?(codename, component, architecture, cache_control)
|
32
|
+
attempts += 1
|
33
|
+
throw "Unable to obtain a lock after #{max_attempts}, giving up." if attempts > max_attempts
|
34
|
+
sleep(wait)
|
35
|
+
end
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
def lock(codename, component = nil, architecture = nil, cache_control = nil)
|
39
|
+
lockfile = Tempfile.new('lockfile')
|
40
|
+
lock_content = generate_lock_content
|
41
|
+
lockfile.write lock_content
|
42
|
+
lockfile.close
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
Dpkg::S3::Utils.s3_store(lockfile.path,
|
45
|
+
lock_path(codename, component, architecture, cache_control),
|
46
|
+
'text/plain',
|
47
|
+
cache_control)
|
48
48
|
|
49
|
-
|
50
|
-
lock_content = Dpkg::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control))
|
51
|
-
lock_content = lock_content.split.first.split('@')
|
52
|
-
lock = Dpkg::S3::Lock.new
|
53
|
-
lock.user = lock_content[0]
|
54
|
-
lock.host = lock_content[1] if lock_content.size > 1
|
55
|
-
lock
|
56
|
-
end
|
49
|
+
return if lock_content == Dpkg::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control))
|
57
50
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
throw 'Failed to acquire lock, was overwritten by another deb-s3 process'
|
52
|
+
end
|
53
|
+
|
54
|
+
def unlock(codename, component = nil, architecture = nil, cache_control = nil)
|
55
|
+
Dpkg::S3::Utils.s3_remove(lock_path(codename, component, architecture, cache_control))
|
56
|
+
end
|
57
|
+
|
58
|
+
def current(codename, component = nil, architecture = nil, cache_control = nil)
|
59
|
+
lock_content = Dpkg::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control))
|
60
|
+
lock_content = lock_content.split.first.split('@')
|
61
|
+
lock = Dpkg::S3::Lock.new
|
62
|
+
lock.user = lock_content[0]
|
63
|
+
lock.host = lock_content[1] if lock_content.size > 1
|
64
|
+
lock
|
65
|
+
end
|
62
66
|
|
63
|
-
|
64
|
-
|
67
|
+
private
|
68
|
+
|
69
|
+
def lock_path(codename, component = nil, architecture = nil, _cache_control = nil)
|
70
|
+
"dists/#{codename}/#{component}/binary-#{architecture}/lockfile"
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate_lock_content
|
74
|
+
"#{Etc.getlogin}@#{Socket.gethostname}\n#{SecureRandom.hex}"
|
75
|
+
end
|
76
|
+
end
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
data/lib/dpkg/s3/manifest.rb
CHANGED
@@ -1,144 +1,146 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'zlib'
|
4
5
|
require 'dpkg/s3/utils'
|
5
6
|
require 'dpkg/s3/package'
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@fail_if_exists = false
|
30
|
-
@skip_package_upload = false
|
31
|
-
end
|
32
|
-
|
33
|
-
class << self
|
34
|
-
def retrieve(codename, component, architecture, cache_control, fail_if_exists, skip_package_upload=false)
|
35
|
-
m = if s = Dpkg::S3::Utils.s3_read("dists/#{codename}/#{component}/binary-#{architecture}/Packages")
|
36
|
-
self.parse_packages(s)
|
37
|
-
else
|
38
|
-
self.new
|
8
|
+
# Dpkg is the root module for all storage modules including S3
|
9
|
+
module Dpkg
|
10
|
+
# S3 storage module resposible of handling packages on S3 including upload, delete
|
11
|
+
module S3
|
12
|
+
# Manifest is resposible of creating/retrieving and rebuilding the debian Package manifest with
|
13
|
+
# standard information required when publishing the packages to a S3 debian repository
|
14
|
+
class Manifest
|
15
|
+
include Dpkg::S3::Utils
|
16
|
+
|
17
|
+
attr_accessor :codename, :component, :cache_control, :architecture, :fail_if_exists, :skip_package_upload, :files
|
18
|
+
|
19
|
+
attr_reader :packages, :packages_to_be_upload
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@packages = []
|
23
|
+
@packages_to_be_upload = []
|
24
|
+
@component = nil
|
25
|
+
@architecture = nil
|
26
|
+
@files = {}
|
27
|
+
@cache_control = ''
|
28
|
+
@fail_if_exists = false
|
29
|
+
@skip_package_upload = false
|
39
30
|
end
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
32
|
+
class << self
|
33
|
+
def retrieve(codename, component, architecture, cache_control, fail_if_exists, skip_upload: false)
|
34
|
+
m = if (s = Dpkg::S3::Utils.s3_read("dists/#{codename}/#{component}/binary-#{architecture}/Packages"))
|
35
|
+
parse_packages(s)
|
36
|
+
else
|
37
|
+
new
|
38
|
+
end
|
39
|
+
|
40
|
+
m.codename = codename
|
41
|
+
m.component = component
|
42
|
+
m.architecture = architecture
|
43
|
+
m.cache_control = cache_control
|
44
|
+
m.fail_if_exists = fail_if_exists
|
45
|
+
m.skip_package_upload = skip_upload
|
46
|
+
m
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_packages(str)
|
50
|
+
m = new
|
51
|
+
str.split("\n\n").each do |s|
|
52
|
+
next if s.chomp.empty?
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
m.packages << Dpkg::S3::Package.parse_string(s)
|
54
|
+
m.packages << Dpkg::S3::Package.parse_string(s)
|
55
|
+
end
|
56
|
+
m
|
57
|
+
end
|
55
58
|
end
|
56
|
-
m
|
57
|
-
end
|
58
|
-
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
60
|
+
def add(pkg, preserve_versions, needs_uploading: true)
|
61
|
+
if fail_if_exists
|
62
|
+
packages.each do |p|
|
63
|
+
next unless p.name == pkg.name && \
|
64
|
+
p.full_version == pkg.full_version && \
|
65
|
+
File.basename(p.url_filename(@codename)) == \
|
66
|
+
File.basename(pkg.url_filename(@codename))
|
67
|
+
|
68
|
+
raise AlreadyExistsError,
|
69
|
+
"package #{pkg.name}_#{pkg.full_version} already exists " \
|
70
|
+
"with filename (#{p.url_filename(@codename)})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
if preserve_versions
|
74
|
+
packages.delete_if { |p| p.name == pkg.name && p.full_version == pkg.full_version }
|
75
|
+
else
|
76
|
+
packages.delete_if { |p| p.name == pkg.name }
|
77
|
+
end
|
78
|
+
packages << pkg
|
79
|
+
packages_to_be_upload << pkg if needs_uploading
|
80
|
+
pkg
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# Also include the packages not matching a specified version
|
89
|
-
elsif (!versions.nil? and p.name == pkg and !versions.include?(p.version) and !versions.include?("#{p.version}-#{p.iteration}") and !versions.include?(p.full_version))
|
83
|
+
def delete_package(pkg, versions = nil)
|
84
|
+
new_packages = @packages.select do |p|
|
85
|
+
# Include packages we didn't name
|
86
|
+
# Also include the packages not matching a specified version
|
87
|
+
if p.name != pkg || (!versions.nil? && (p.name == pkg) && !versions.include?(p.version) &&
|
88
|
+
!versions.include?("#{p.version}-#{p.iteration}") && !versions.include?(p.full_version))
|
90
89
|
p
|
90
|
+
end
|
91
91
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
92
|
+
deleted = @packages - new_packages
|
93
|
+
@packages = new_packages
|
94
|
+
deleted
|
95
|
+
end
|
97
96
|
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
def generate
|
98
|
+
@packages.collect { |pkg| pkg.generate(@codename) }.join("\n")
|
99
|
+
end
|
101
100
|
|
102
|
-
|
103
|
-
|
101
|
+
def write_to_s3
|
102
|
+
manifest = generate
|
104
103
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
104
|
+
unless skip_package_upload
|
105
|
+
# store any packages that need to be stored
|
106
|
+
@packages_to_be_upload.each do |pkg|
|
107
|
+
yield pkg.url_filename(@codename) if block_given?
|
108
|
+
s3_store(pkg.filename, pkg.url_filename(@codename), 'application/x-debian-package', cache_control,
|
109
|
+
fail_if_exists: fail_if_exists)
|
110
|
+
end
|
111
|
+
end
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
113
|
+
# generate the Packages file
|
114
|
+
pkgs_temp = Tempfile.new('Packages')
|
115
|
+
pkgs_temp.write manifest
|
116
|
+
pkgs_temp.close
|
117
|
+
f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages"
|
118
|
+
yield f if block_given?
|
119
|
+
s3_store(pkgs_temp.path, f, 'text/plain; charset=utf-8', cache_control)
|
120
|
+
@files["#{@component}/binary-#{@architecture}/Packages"] = hashfile(pkgs_temp.path)
|
121
|
+
pkgs_temp.unlink
|
122
|
+
|
123
|
+
# generate the Packages.gz file
|
124
|
+
gztemp = Tempfile.new('Packages.gz')
|
125
|
+
gztemp.close
|
126
|
+
Zlib::GzipWriter.open(gztemp.path) { |gz| gz.write manifest }
|
127
|
+
f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages.gz"
|
128
|
+
yield f if block_given?
|
129
|
+
s3_store(gztemp.path, f, 'application/x-gzip', cache_control)
|
130
|
+
@files["#{@component}/binary-#{@architecture}/Packages.gz"] = hashfile(gztemp.path)
|
131
|
+
gztemp.unlink
|
132
|
+
|
133
|
+
nil
|
134
|
+
end
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
136
|
+
def hashfile(path)
|
137
|
+
{
|
138
|
+
size: File.size(path),
|
139
|
+
sha1: Digest::SHA1.file(path).hexdigest,
|
140
|
+
sha256: Digest::SHA2.file(path).hexdigest,
|
141
|
+
md5: Digest::MD5.file(path).hexdigest
|
142
|
+
}
|
143
|
+
end
|
144
|
+
end
|
143
145
|
end
|
144
146
|
end
|
data/lib/dpkg/s3/package.rb
CHANGED
@@ -1,312 +1,297 @@
|
|
1
|
-
#
|
2
|
-
require "digest/sha1"
|
3
|
-
require "digest/sha2"
|
4
|
-
require "digest/md5"
|
5
|
-
require "socket"
|
6
|
-
require "tmpdir"
|
7
|
-
require "cgi"
|
1
|
+
# frozen_string_literal: true
|
8
2
|
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
attr_accessor :epoch
|
17
|
-
attr_accessor :iteration
|
18
|
-
attr_accessor :maintainer
|
19
|
-
attr_accessor :vendor
|
20
|
-
attr_accessor :url
|
21
|
-
attr_accessor :category
|
22
|
-
attr_accessor :license
|
23
|
-
attr_accessor :architecture
|
24
|
-
attr_accessor :description
|
25
|
-
|
26
|
-
attr_accessor :dependencies
|
27
|
-
|
28
|
-
# Any other attributes specific to this package.
|
29
|
-
# This is where you'd put rpm, deb, or other specific attributes.
|
30
|
-
attr_accessor :attributes
|
31
|
-
|
32
|
-
# hashes
|
33
|
-
attr_accessor :url_filename
|
34
|
-
attr_accessor :sha1
|
35
|
-
attr_accessor :sha256
|
36
|
-
attr_accessor :md5
|
37
|
-
attr_accessor :size
|
38
|
-
|
39
|
-
attr_accessor :filename
|
40
|
-
|
41
|
-
class << self
|
42
|
-
include Dpkg::S3::Utils
|
43
|
-
|
44
|
-
def parse_file(package)
|
45
|
-
p = self.new
|
46
|
-
p.extract_info(extract_control(package))
|
47
|
-
p.apply_file_info(package)
|
48
|
-
p.filename = package
|
49
|
-
p
|
50
|
-
end
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'digest/sha2'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'English'
|
7
|
+
require 'socket'
|
8
|
+
require 'tmpdir'
|
9
|
+
require 'cgi'
|
51
10
|
|
52
|
-
|
53
|
-
p = self.new
|
54
|
-
p.extract_info(s)
|
55
|
-
p
|
56
|
-
end
|
11
|
+
require 'dpkg/s3/utils'
|
57
12
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
13
|
+
# Dpkg is the root module for all storage modules including S3
|
14
|
+
module Dpkg
|
15
|
+
# S3 storage module resposible of handling packages on S3 including upload, delete
|
16
|
+
module S3
|
17
|
+
# Package class inlcudes methods for extracting the control file of a debian archive and extracting
|
18
|
+
# information required for publishing package
|
19
|
+
class Package
|
20
|
+
include Dpkg::S3::Utils
|
21
|
+
|
22
|
+
attr_accessor :name, :version, :epoch, :iteration, :maintainer, :vendor, :url, :category, :license, :architecture,
|
23
|
+
:description, :dependencies, :sha1, :sha256, :md5, :size, :filename, :url_filename
|
24
|
+
|
25
|
+
# Any other attributes specific to this package.
|
26
|
+
# This is where you'd put rpm, deb, or other specific attributes.
|
27
|
+
attr_accessor :attributes
|
28
|
+
|
29
|
+
class << self
|
30
|
+
include Dpkg::S3::Utils
|
31
|
+
|
32
|
+
def parse_file(package)
|
33
|
+
p = new
|
34
|
+
p.extract_info(extract_control(package))
|
35
|
+
p.apply_file_info(package)
|
36
|
+
p.filename = package
|
37
|
+
p
|
71
38
|
end
|
72
39
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
begin
|
79
|
-
safesystem("ar t #{package} #{control_file} &> /dev/null")
|
80
|
-
rescue SafeSystemError
|
81
|
-
warn "Failed to find control data in .deb with ar, trying tar."
|
82
|
-
extract_control_tarball_cmd = "tar #{compression}xf #{package} --to-stdout #{control_file}"
|
40
|
+
def parse_string(str)
|
41
|
+
p = new
|
42
|
+
p.extract_info(str)
|
43
|
+
p
|
83
44
|
end
|
84
45
|
|
85
|
-
|
86
|
-
|
87
|
-
|
46
|
+
def extract_control(package)
|
47
|
+
if system('which dpkg > /dev/null 2>&1')
|
48
|
+
`dpkg -f #{package}`
|
49
|
+
else
|
50
|
+
# use ar to determine control file name (control.ext)
|
51
|
+
package_files = `ar t #{package}`
|
52
|
+
control_file = package_files.split("\n").select do |file|
|
53
|
+
file.start_with?('control.')
|
54
|
+
end.first
|
55
|
+
compression = if control_file == 'control.tar.gz'
|
56
|
+
'z'
|
57
|
+
else
|
58
|
+
'J'
|
59
|
+
end
|
60
|
+
|
61
|
+
# ar fails to find the control.tar.gz tarball within the .deb
|
62
|
+
# on Mac OS. Try using ar to list the control file, if found,
|
63
|
+
# use ar to extract, otherwise attempt with tar which works on OS X.
|
64
|
+
extract_control_tarball_cmd = "ar p #{package} #{control_file}"
|
65
|
+
|
66
|
+
begin
|
67
|
+
safesystem("ar t #{package} #{control_file} &> /dev/null")
|
68
|
+
rescue SafeSystemError
|
69
|
+
warn 'Failed to find control data in .deb with ar, trying tar.'
|
70
|
+
extract_control_tarball_cmd = "tar #{compression}xf #{package} --to-stdout #{control_file}"
|
71
|
+
end
|
72
|
+
|
73
|
+
Dir.mktmpdir do |path|
|
74
|
+
safesystem("#{extract_control_tarball_cmd} | tar -#{compression}xf - -C #{path}")
|
75
|
+
File.read(File.join(path, 'control'), encoding: 'UTF-8')
|
76
|
+
end
|
77
|
+
end
|
88
78
|
end
|
89
79
|
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def initialize
|
94
|
-
@attributes = {}
|
95
|
-
|
96
|
-
# Reference
|
97
|
-
# http://www.debian.org/doc/manuals/maint-guide/first.en.html
|
98
|
-
# http://wiki.debian.org/DeveloperConfiguration
|
99
|
-
# https://github.com/jordansissel/fpm/issues/37
|
100
|
-
if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
|
101
|
-
# Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
|
102
|
-
@maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
|
103
|
-
else
|
104
|
-
# TODO(sissel): Maybe support using 'git config' for a default as well?
|
105
|
-
# git config --get user.name, etc can be useful.
|
106
|
-
#
|
107
|
-
# Otherwise default to user@currenthost
|
108
|
-
@maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
|
109
|
-
end
|
110
80
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
81
|
+
def initialize
|
82
|
+
@attributes = {}
|
83
|
+
|
84
|
+
# Reference
|
85
|
+
# http://www.debian.org/doc/manuals/maint-guide/first.en.html
|
86
|
+
# http://wiki.debian.org/DeveloperConfiguration
|
87
|
+
# https://github.com/jordansissel/fpm/issues/37
|
88
|
+
@maintainer = if ENV.include?('DEBEMAIL') && ENV.include?('DEBFULLNAME')
|
89
|
+
# Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
|
90
|
+
"#{ENV['DEBFULLNAME']} <#{ENV['DEBEMAIL']}>"
|
91
|
+
else
|
92
|
+
# TODO(sissel): Maybe support using 'git config' for a default as well?
|
93
|
+
# git config --get user.name, etc can be useful.
|
94
|
+
#
|
95
|
+
# Otherwise default to user@currenthost
|
96
|
+
"<#{ENV['USER']}@#{Socket.gethostname}>"
|
97
|
+
end
|
98
|
+
|
99
|
+
@name = nil
|
100
|
+
@architecture = 'native'
|
101
|
+
@description = 'no description given'
|
102
|
+
@version = nil
|
103
|
+
@epoch = nil
|
104
|
+
@iteration = nil
|
105
|
+
@url = nil
|
106
|
+
@category = 'default'
|
107
|
+
@license = 'unknown'
|
108
|
+
@vendor = 'none'
|
109
|
+
@sha1 = nil
|
110
|
+
@sha256 = nil
|
111
|
+
@md5 = nil
|
112
|
+
@size = nil
|
113
|
+
@filename = nil
|
114
|
+
@url_filename = nil
|
115
|
+
|
116
|
+
@dependencies = []
|
117
|
+
end
|
130
118
|
|
131
|
-
|
132
|
-
|
133
|
-
[[epoch, version].compact.join(":"), iteration].compact.join("-")
|
134
|
-
end
|
119
|
+
def full_version
|
120
|
+
return nil if [epoch, version, iteration].all?(&:nil?)
|
135
121
|
|
136
|
-
|
137
|
-
|
138
|
-
@filename
|
139
|
-
end
|
122
|
+
[[epoch, version].compact.join(':'), iteration].compact.join('-')
|
123
|
+
end
|
140
124
|
|
141
|
-
|
142
|
-
|
143
|
-
|
125
|
+
def url_filename(codename) # rubocop:disable Lint/DuplicateMethods
|
126
|
+
@url_filename || "pool/#{codename}/#{name[0]}/#{name[0..1]}/#{File.basename(filename)}"
|
127
|
+
end
|
144
128
|
|
145
|
-
|
146
|
-
|
147
|
-
|
129
|
+
def url_filename_encoded(codename)
|
130
|
+
@url_filename || "pool/#{codename}/#{name[0]}/#{name[0..1]}/#{s3_escape(File.basename(filename))}"
|
131
|
+
end
|
148
132
|
|
149
|
-
|
150
|
-
|
151
|
-
|
133
|
+
def generate(codename = nil)
|
134
|
+
template('package.erb').result(binding)
|
135
|
+
end
|
152
136
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
137
|
+
# from fpm
|
138
|
+
def parse_depends(data)
|
139
|
+
return [] if data.nil? || data.empty?
|
140
|
+
|
141
|
+
# parse dependencies. Debian dependencies come in one of two forms:
|
142
|
+
# * name
|
143
|
+
# * name (op version)
|
144
|
+
# They are all on one line, separated by ", "
|
145
|
+
|
146
|
+
dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
|
147
|
+
data.split(/, */).collect do |dep|
|
148
|
+
m = dep_re.match(dep)
|
149
|
+
if m
|
150
|
+
name, op, version = m.captures
|
151
|
+
# this is the proper form of dependency
|
152
|
+
if op && version && op != '' && version != ''
|
153
|
+
"#{name} (#{op} #{version})".strip
|
154
|
+
else
|
155
|
+
name.strip
|
156
|
+
end
|
157
|
+
else
|
158
|
+
# Assume normal form dependency, "name op version".
|
159
|
+
dep
|
160
|
+
end
|
171
161
|
end
|
172
|
-
else
|
173
|
-
# Assume normal form dependency, "name op version".
|
174
|
-
dep
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end # def parse_depends
|
178
|
-
|
179
|
-
# from fpm
|
180
|
-
def fix_dependency(dep)
|
181
|
-
# Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
|
182
|
-
# Convert anything that looks like 'NAME OP VERSION' to this format.
|
183
|
-
if dep =~ /[\(,\|]/
|
184
|
-
# Don't "fix" ones that could appear well formed already.
|
185
|
-
else
|
186
|
-
# Convert ones that appear to be 'name op version'
|
187
|
-
name, op, version = dep.split(/ +/)
|
188
|
-
if !version.nil?
|
189
|
-
# Convert strings 'foo >= bar' to 'foo (>= bar)'
|
190
|
-
dep = "#{name} (#{debianize_op(op)} #{version})"
|
191
162
|
end
|
192
|
-
end
|
193
|
-
|
194
|
-
name_re = /^[^ \(]+/
|
195
|
-
name = dep[name_re]
|
196
|
-
if name =~ /[A-Z]/
|
197
|
-
dep = dep.gsub(name_re) { |n| n.downcase }
|
198
|
-
end
|
199
163
|
|
200
|
-
|
201
|
-
|
202
|
-
|
164
|
+
# from fpm
|
165
|
+
def fix_dependency(dep)
|
166
|
+
# Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
|
167
|
+
# Convert anything that looks like 'NAME OP VERSION' to this format.
|
168
|
+
if dep =~ /[(,|]/
|
169
|
+
# Don't "fix" ones that could appear well formed already.
|
170
|
+
else
|
171
|
+
# Convert ones that appear to be 'name op version'
|
172
|
+
name, op, version = dep.split(/ +/)
|
173
|
+
unless version.nil?
|
174
|
+
# Convert strings 'foo >= bar' to 'foo (>= bar)'
|
175
|
+
dep = "#{name} (#{debianize_op(op)} #{version})"
|
176
|
+
end
|
177
|
+
end
|
203
178
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
179
|
+
name_re = /^[^ (]+/
|
180
|
+
name = dep[name_re]
|
181
|
+
dep = dep.gsub(name_re, &:downcase) if name =~ /[A-Z]/
|
182
|
+
|
183
|
+
dep = dep.gsub('_', '-') if dep.include?('_')
|
184
|
+
|
185
|
+
# Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
|
186
|
+
if dep =~ /\(~>/
|
187
|
+
name, version = dep.gsub(/[()~>]/, '').split(/ +/)[0..1]
|
188
|
+
nextversion = version.split('.').collect(&:to_i)
|
189
|
+
l = nextversion.length
|
190
|
+
nextversion[l - 2] += 1
|
191
|
+
nextversion[l - 1] = 0
|
192
|
+
nextversion = nextversion.join('.')
|
193
|
+
["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
194
|
+
elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
|
195
|
+
# Append this to conflicts
|
196
|
+
self.conflicts += [dep.gsub(/!=/, '=')]
|
197
|
+
[]
|
198
|
+
elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/)) &&
|
199
|
+
attributes[:deb_ignore_iteration_in_dependencies?]
|
200
|
+
# Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
|
201
|
+
# but only when flag --ignore-iteration-in-dependencies is passed.
|
202
|
+
name, version = m[1..2]
|
203
|
+
nextversion = version.split('.').collect(&:to_i)
|
204
|
+
nextversion[-1] += 1
|
205
|
+
nextversion = nextversion.join('.')
|
206
|
+
["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
207
|
+
else
|
208
|
+
# otherwise the dep is probably fine
|
209
|
+
dep.rstrip
|
210
|
+
end
|
211
|
+
end
|
231
212
|
|
232
|
-
|
233
|
-
|
234
|
-
|
213
|
+
# from fpm
|
214
|
+
def extract_info(control)
|
215
|
+
fields = parse_control(control)
|
216
|
+
|
217
|
+
# Parse 'epoch:version-iteration' in the version string
|
218
|
+
full_version = fields.delete('Version')
|
219
|
+
raise "Unsupported version string '#{full_version}'" if full_version !~ /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
|
220
|
+
|
221
|
+
self.epoch, self.version, self.iteration = $LAST_MATCH_INFO.captures
|
222
|
+
|
223
|
+
self.architecture = fields.delete('Architecture')
|
224
|
+
self.category = fields.delete('Section')
|
225
|
+
self.license = fields.delete('License') || license
|
226
|
+
self.maintainer = fields.delete('Maintainer')
|
227
|
+
self.name = fields.delete('Package')
|
228
|
+
self.url = fields.delete('Homepage')
|
229
|
+
self.vendor = fields.delete('Vendor') || vendor
|
230
|
+
attributes[:deb_priority] = fields.delete('Priority')
|
231
|
+
attributes[:deb_origin] = fields.delete('Origin')
|
232
|
+
attributes[:deb_installed_size] = fields.delete('Installed-Size')
|
233
|
+
|
234
|
+
# Packages manifest fields
|
235
|
+
filename = fields.delete('Filename')
|
236
|
+
self.url_filename = filename
|
237
|
+
self.sha1 = fields.delete('SHA1')
|
238
|
+
self.sha256 = fields.delete('SHA256')
|
239
|
+
self.md5 = fields.delete('MD5sum')
|
240
|
+
self.size = fields.delete('Size')
|
241
|
+
self.description = fields.delete('Description')
|
242
|
+
|
243
|
+
# self.config_files = config_files
|
244
|
+
|
245
|
+
self.dependencies += Array(parse_depends(fields.delete('Depends')))
|
246
|
+
|
247
|
+
attributes[:deb_recommends] = fields.delete('Recommends')
|
248
|
+
attributes[:deb_suggests] = fields.delete('Suggests')
|
249
|
+
attributes[:deb_enhances] = fields.delete('Enhances')
|
250
|
+
attributes[:deb_pre_depends] = fields.delete('Pre-Depends')
|
251
|
+
|
252
|
+
attributes[:deb_breaks] = fields.delete('Breaks')
|
253
|
+
attributes[:deb_conflicts] = fields.delete('Conflicts')
|
254
|
+
attributes[:deb_provides] = fields.delete('Provides')
|
255
|
+
attributes[:deb_replaces] = fields.delete('Replaces')
|
256
|
+
|
257
|
+
attributes[:deb_field] = fields.map do |k, v|
|
258
|
+
[k.sub(/\AX[BCS]{0,3}-/, ''), v]
|
259
|
+
end.to_h
|
260
|
+
end
|
235
261
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
self.architecture = fields.delete('Architecture')
|
244
|
-
self.category = fields.delete('Section')
|
245
|
-
self.license = fields.delete('License') || self.license
|
246
|
-
self.maintainer = fields.delete('Maintainer')
|
247
|
-
self.name = fields.delete('Package')
|
248
|
-
self.url = fields.delete('Homepage')
|
249
|
-
self.vendor = fields.delete('Vendor') || self.vendor
|
250
|
-
self.attributes[:deb_priority] = fields.delete('Priority')
|
251
|
-
self.attributes[:deb_origin] = fields.delete('Origin')
|
252
|
-
self.attributes[:deb_installed_size] = fields.delete('Installed-Size')
|
253
|
-
|
254
|
-
# Packages manifest fields
|
255
|
-
filename = fields.delete('Filename')
|
256
|
-
self.url_filename = filename && CGI.unescape(filename)
|
257
|
-
self.sha1 = fields.delete('SHA1')
|
258
|
-
self.sha256 = fields.delete('SHA256')
|
259
|
-
self.md5 = fields.delete('MD5sum')
|
260
|
-
self.size = fields.delete('Size')
|
261
|
-
self.description = fields.delete('Description')
|
262
|
-
|
263
|
-
#self.config_files = config_files
|
264
|
-
|
265
|
-
self.dependencies += Array(parse_depends(fields.delete('Depends')))
|
266
|
-
|
267
|
-
self.attributes[:deb_recommends] = fields.delete('Recommends')
|
268
|
-
self.attributes[:deb_suggests] = fields.delete('Suggests')
|
269
|
-
self.attributes[:deb_enhances] = fields.delete('Enhances')
|
270
|
-
self.attributes[:deb_pre_depends] = fields.delete('Pre-Depends')
|
271
|
-
|
272
|
-
self.attributes[:deb_breaks] = fields.delete('Breaks')
|
273
|
-
self.attributes[:deb_conflicts] = fields.delete('Conflicts')
|
274
|
-
self.attributes[:deb_provides] = fields.delete('Provides')
|
275
|
-
self.attributes[:deb_replaces] = fields.delete('Replaces')
|
276
|
-
|
277
|
-
self.attributes[:deb_field] = Hash[fields.map { |k, v|
|
278
|
-
[k.sub(/\AX[BCS]{0,3}-/, ''), v]
|
279
|
-
}]
|
280
|
-
end # def extract_info
|
281
|
-
|
282
|
-
def apply_file_info(file)
|
283
|
-
self.size = File.size(file)
|
284
|
-
self.sha1 = Digest::SHA1.file(file).hexdigest
|
285
|
-
self.sha256 = Digest::SHA2.file(file).hexdigest
|
286
|
-
self.md5 = Digest::MD5.file(file).hexdigest
|
287
|
-
end
|
262
|
+
def apply_file_info(file)
|
263
|
+
self.size = File.size(file)
|
264
|
+
self.sha1 = Digest::SHA1.file(file).hexdigest
|
265
|
+
self.sha256 = Digest::SHA2.file(file).hexdigest
|
266
|
+
self.md5 = Digest::MD5.file(file).hexdigest
|
267
|
+
end
|
288
268
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
269
|
+
def parse_control(control)
|
270
|
+
field = nil
|
271
|
+
value = ''
|
272
|
+
{}.tap do |fields|
|
273
|
+
control.each_line do |line|
|
274
|
+
case line
|
275
|
+
when /^(\s+)(\S.*)$/
|
276
|
+
indent = Regexp.last_match(1)
|
277
|
+
rest = Regexp.last_match(2)
|
278
|
+
# Continuation
|
279
|
+
if indent.size == 1 && rest == '.'
|
280
|
+
value << "\n"
|
281
|
+
rest = ''
|
282
|
+
elsif value.size.positive?
|
283
|
+
value << "\n"
|
284
|
+
end
|
285
|
+
value << rest
|
286
|
+
when /^([-\w]+):(.*)$/
|
287
|
+
fields[field] = value if field
|
288
|
+
field = Regexp.last_match(1)
|
289
|
+
value = Regexp.last_match(2).strip
|
290
|
+
end
|
302
291
|
end
|
303
|
-
value << rest
|
304
|
-
elsif line =~ /^([-\w]+):(.*)$/
|
305
292
|
fields[field] = value if field
|
306
|
-
field, value = $1, $2.strip
|
307
293
|
end
|
308
294
|
end
|
309
|
-
fields[field] = value if field
|
310
295
|
end
|
311
296
|
end
|
312
297
|
end
|