pkgr 1.3.2 → 1.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 +7 -0
- data/README.md +23 -18
- data/data/build_dependencies/fedora.yml +13 -0
- data/data/buildpacks/centos-6 +3 -2
- data/data/buildpacks/debian-7 +3 -2
- data/data/buildpacks/fedora-20 +2 -0
- data/data/buildpacks/ubuntu-12.04 +3 -2
- data/data/buildpacks/ubuntu-14.04 +3 -2
- data/data/cli/cli.sh.erb +83 -19
- data/data/dependencies/fedora.yml +9 -0
- data/data/environment/default.erb +5 -0
- data/data/hooks/postinstall.sh +15 -6
- data/data/hooks/postuninstall.sh +26 -0
- data/data/hooks/preinstall.sh +8 -2
- data/data/hooks/preuninstall.sh +18 -0
- data/lib/pkgr/addon.rb +110 -0
- data/lib/pkgr/builder.rb +91 -21
- data/lib/pkgr/cli.rb +6 -3
- data/lib/pkgr/command.rb +102 -0
- data/lib/pkgr/config.rb +43 -1
- data/lib/pkgr/cron.rb +12 -0
- data/lib/pkgr/dispatcher.rb +1 -2
- data/lib/pkgr/distributions.rb +4 -2
- data/lib/pkgr/distributions/base.rb +43 -15
- data/lib/pkgr/distributions/centos.rb +10 -2
- data/lib/pkgr/distributions/debian.rb +77 -27
- data/lib/pkgr/distributions/fedora.rb +58 -0
- data/lib/pkgr/distributions/redhat.rb +1 -43
- data/lib/pkgr/distributions/ubuntu.rb +6 -0
- data/lib/pkgr/installer.rb +66 -0
- data/lib/pkgr/version.rb +1 -1
- metadata +55 -60
data/data/hooks/preinstall.sh
CHANGED
@@ -9,9 +9,15 @@ export APP_HOME="<%= home %>"
|
|
9
9
|
|
10
10
|
if ! getent passwd "${APP_USER}" > /dev/null; then
|
11
11
|
if [ -f /etc/redhat-release ]; then
|
12
|
-
|
12
|
+
if ! getent group "${APP_GROUP}" > /dev/null ; then
|
13
|
+
groupadd --system "${APP_GROUP}"
|
14
|
+
fi
|
15
|
+
adduser "${APP_USER}" -g "${APP_GROUP}" --system --create-home --shell /bin/bash
|
13
16
|
else
|
14
|
-
|
17
|
+
if ! getent group "${APP_GROUP}" > /dev/null; then
|
18
|
+
addgroup "${APP_GROUP}" --system --quiet
|
19
|
+
fi
|
20
|
+
adduser "${APP_USER}" --disabled-login --ingroup "${APP_GROUP}" --system --quiet --shell /bin/bash
|
15
21
|
fi
|
16
22
|
fi
|
17
23
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
export APP_NAME="<%= name %>"
|
6
|
+
export APP_USER="<%= user %>"
|
7
|
+
export APP_GROUP="<%= group %>"
|
8
|
+
export APP_HOME="<%= home %>"
|
9
|
+
|
10
|
+
<% if before_remove && File.readable?(before_remove) %>
|
11
|
+
CUSTOM_PREUNINSTALL_SCRIPT="<%= Base64.encode64 File.read(before_remove) %>"
|
12
|
+
|
13
|
+
tmpfile=$(mktemp)
|
14
|
+
chmod a+x "${tmpfile}"
|
15
|
+
echo "${CUSTOM_PREUNINSTALL_SCRIPT}" | base64 -d - > ${tmpfile}
|
16
|
+
|
17
|
+
"${tmpfile}" "$@"
|
18
|
+
<% end %>
|
data/lib/pkgr/addon.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Pkgr
|
2
|
+
class Addon
|
3
|
+
attr_reader :nickname, :addons_dir
|
4
|
+
attr_reader :config
|
5
|
+
|
6
|
+
def initialize(nickname, addons_dir, config)
|
7
|
+
@nickname = nickname
|
8
|
+
@addons_dir = addons_dir
|
9
|
+
@config = config
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
File.basename(url_without_branch, ".git").sub("addon-", "")
|
14
|
+
end
|
15
|
+
|
16
|
+
def url_without_branch
|
17
|
+
nickname.split("#")[0].sub(/\.git$/,'')
|
18
|
+
end
|
19
|
+
|
20
|
+
def url
|
21
|
+
if nickname.start_with?("http")
|
22
|
+
url_without_branch
|
23
|
+
else
|
24
|
+
user, repo = nickname.split("/", 2)
|
25
|
+
user, repo = "pkgr", user if repo.nil?
|
26
|
+
repo = "addon-#{repo}" unless repo.start_with?("addon-")
|
27
|
+
|
28
|
+
"https://github.com/#{user}/#{repo}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def branch
|
33
|
+
nickname.split("#")[1] || "master"
|
34
|
+
end
|
35
|
+
|
36
|
+
def tarball_url
|
37
|
+
"#{url}/archive/#{branch}.tar.gz"
|
38
|
+
end
|
39
|
+
|
40
|
+
def debian_dependency_name
|
41
|
+
[
|
42
|
+
[config.name, name].join("-"),
|
43
|
+
"(= #{config.version}-#{config.iteration})"
|
44
|
+
].join(" ")
|
45
|
+
end
|
46
|
+
|
47
|
+
def debtemplates
|
48
|
+
debtemplates_file = File.join(dir, "debian", "templates")
|
49
|
+
if File.exists?(debtemplates_file)
|
50
|
+
File.new(debtemplates_file)
|
51
|
+
else
|
52
|
+
StringIO.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def debconfig
|
57
|
+
debconfig_file = File.join(dir, "debian", "config")
|
58
|
+
if File.exists?(debconfig_file)
|
59
|
+
File.new(debconfig_file)
|
60
|
+
else
|
61
|
+
StringIO.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def install!(src_dir)
|
66
|
+
install_addon = Mixlib::ShellOut.new %{curl -L --max-redirs 3 --retry 5 -s '#{tarball_url}' | tar xzf - --strip-components=1 -C '#{dir}'}
|
67
|
+
install_addon.logger = Pkgr.logger
|
68
|
+
install_addon.run_command
|
69
|
+
install_addon.error!
|
70
|
+
|
71
|
+
# TODO: remove args from command once all addons use env variables
|
72
|
+
compile_addon = Mixlib::ShellOut.new(%{#{dir}/bin/compile '#{config.name}' '#{config.version}' '#{config.iteration}' '#{src_dir}' 2>&1}, {
|
73
|
+
:environment => {
|
74
|
+
"APP_NAME" => config.name,
|
75
|
+
"APP_VERSION" => config.version,
|
76
|
+
"APP_ITERATION" => config.iteration,
|
77
|
+
"APP_SAFE_NAME" => config.name.gsub("-", "_"),
|
78
|
+
"APP_USER" => config.user,
|
79
|
+
"APP_GROUP" => config.group,
|
80
|
+
"APP_WORKSPACE" => src_dir
|
81
|
+
}
|
82
|
+
})
|
83
|
+
compile_addon.logger = Pkgr.logger
|
84
|
+
compile_addon.live_stream = LiveStream.new(STDOUT)
|
85
|
+
compile_addon.run_command
|
86
|
+
compile_addon.error!
|
87
|
+
end
|
88
|
+
|
89
|
+
def dir
|
90
|
+
@dir ||= begin
|
91
|
+
directory = File.join(addons_dir, File.basename(name))
|
92
|
+
FileUtils.mkdir_p(directory)
|
93
|
+
directory
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class LiveStream
|
98
|
+
attr_reader :stream
|
99
|
+
def initialize(stream = STDOUT)
|
100
|
+
@stream = stream
|
101
|
+
end
|
102
|
+
|
103
|
+
def <<(data)
|
104
|
+
data.split("\n").each do |line|
|
105
|
+
stream.puts " #{line}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/pkgr/builder.rb
CHANGED
@@ -3,6 +3,9 @@ require 'fileutils'
|
|
3
3
|
require 'pkgr/config'
|
4
4
|
require 'pkgr/distributions'
|
5
5
|
require 'pkgr/process'
|
6
|
+
require 'pkgr/addon'
|
7
|
+
require 'pkgr/cron'
|
8
|
+
require 'pkgr/installer'
|
6
9
|
|
7
10
|
module Pkgr
|
8
11
|
class Builder
|
@@ -21,31 +24,23 @@ module Pkgr
|
|
21
24
|
update_config
|
22
25
|
check
|
23
26
|
setup
|
27
|
+
|
28
|
+
if config.installer
|
29
|
+
setup_pipeline
|
30
|
+
else
|
31
|
+
setup_addons
|
32
|
+
end
|
33
|
+
|
24
34
|
compile
|
25
35
|
write_env
|
26
36
|
write_init
|
37
|
+
setup_crons
|
27
38
|
package
|
39
|
+
store_cache
|
28
40
|
ensure
|
29
41
|
teardown if config.clean
|
30
42
|
end
|
31
43
|
|
32
|
-
# Check configuration, and verifies that the current distribution's requirements are satisfied
|
33
|
-
def check
|
34
|
-
raise Errors::ConfigurationInvalid, config.errors.join("; ") unless config.valid?
|
35
|
-
distribution.check(config)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Setup the build directory structure
|
39
|
-
def setup
|
40
|
-
Dir.chdir(build_dir) do
|
41
|
-
# useful for templates that need to read files
|
42
|
-
config.source_dir = source_dir
|
43
|
-
distribution.templates(config).each do |template|
|
44
|
-
template.install(config.sesame)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
44
|
# Extract the given tarball to the target directory
|
50
45
|
def extract
|
51
46
|
FileUtils.mkdir_p source_dir
|
@@ -69,12 +64,62 @@ module Pkgr
|
|
69
64
|
@config = Config.load_file(config_file, distribution.slug).merge(config)
|
70
65
|
Pkgr.debug "Found .pkgr.yml file. Updated config is now: #{config.inspect}"
|
71
66
|
|
67
|
+
# update distribution config
|
68
|
+
distribution.config = @config
|
69
|
+
|
72
70
|
# FIXME: make Config the authoritative source of the runner config (distribution only tells the default runner)
|
73
71
|
if @config.runner
|
74
72
|
type, *version = @config.runner.split("-")
|
75
73
|
distribution.runner = Distributions::Runner.new(type, version.join("-"))
|
76
74
|
end
|
77
75
|
end
|
76
|
+
# required to build proper Addon objects
|
77
|
+
config.addons_dir = addons_dir
|
78
|
+
# useful for templates that need to read files
|
79
|
+
config.source_dir = source_dir
|
80
|
+
config.build_dir = build_dir
|
81
|
+
end
|
82
|
+
|
83
|
+
def pipeline
|
84
|
+
@pipeline ||= begin
|
85
|
+
components = []
|
86
|
+
unless config.wizards.empty? || config.installer == false
|
87
|
+
components << Installer.new(config.installer, distribution).setup
|
88
|
+
end
|
89
|
+
components
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check configuration, and verifies that the current distribution's requirements are satisfied
|
94
|
+
def check
|
95
|
+
raise Errors::ConfigurationInvalid, config.errors.join("; ") unless config.valid?
|
96
|
+
distribution.check
|
97
|
+
end
|
98
|
+
|
99
|
+
# Setup the build directory structure
|
100
|
+
def setup
|
101
|
+
Dir.chdir(build_dir) do
|
102
|
+
distribution.templates.each do |template|
|
103
|
+
template.install(config.sesame)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def setup_pipeline
|
109
|
+
pipeline.each do |component|
|
110
|
+
@config = component.call(config)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# LEGACY, remove once openproject no longer needs it
|
115
|
+
# If addons are declared in .pkgr.yml, add them
|
116
|
+
def setup_addons
|
117
|
+
config.addons.each do |addon|
|
118
|
+
puts "-----> [addon] #{addon.name} (#{addon.url} @ #{addon.branch})"
|
119
|
+
addon.install!(source_dir)
|
120
|
+
dependency = distribution.add_addon(addon)
|
121
|
+
config.dependencies.push(dependency) if dependency
|
122
|
+
end
|
78
123
|
end
|
79
124
|
|
80
125
|
# Pass the app through the buildpack
|
@@ -126,6 +171,20 @@ module Pkgr
|
|
126
171
|
end
|
127
172
|
end
|
128
173
|
|
174
|
+
# Write cron files
|
175
|
+
def setup_crons
|
176
|
+
crons_dir = File.join("/", distribution.crons_dir)
|
177
|
+
|
178
|
+
config.crons.map! do |cron_path|
|
179
|
+
Cron.new(File.expand_path(cron_path, config.home), File.join(crons_dir, File.basename(cron_path)))
|
180
|
+
end
|
181
|
+
|
182
|
+
config.crons.each do |cron|
|
183
|
+
puts "-----> [cron] #{cron.source} => #{cron.destination}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
129
188
|
# Launch the FPM command that will generate the package.
|
130
189
|
def package
|
131
190
|
app_package = Mixlib::ShellOut.new(fpm_command)
|
@@ -134,6 +193,13 @@ module Pkgr
|
|
134
193
|
app_package.error!
|
135
194
|
end
|
136
195
|
|
196
|
+
def store_cache
|
197
|
+
return true unless config.store_cache
|
198
|
+
generate_cache_tarball = Mixlib::ShellOut.new %{tar czf cache.tar.gz -C #{compile_cache_dir} .}
|
199
|
+
generate_cache_tarball.logger = Pkgr.logger
|
200
|
+
generate_cache_tarball.run_command
|
201
|
+
end
|
202
|
+
|
137
203
|
# Make sure to get rid of the build directory
|
138
204
|
def teardown
|
139
205
|
FileUtils.rm_rf(build_dir)
|
@@ -182,6 +248,10 @@ module Pkgr
|
|
182
248
|
File.join(source_dir, "vendor", "pkgr")
|
183
249
|
end
|
184
250
|
|
251
|
+
def addons_dir
|
252
|
+
File.join(vendor_dir, "addons")
|
253
|
+
end
|
254
|
+
|
185
255
|
# Directory where binstubs will be created for the corresponding Procfile commands.
|
186
256
|
def proc_dir
|
187
257
|
File.join(vendor_dir, "processes")
|
@@ -203,12 +273,12 @@ module Pkgr
|
|
203
273
|
|
204
274
|
# Returns the current distribution we're packaging for.
|
205
275
|
def distribution
|
206
|
-
@distribution ||= Distributions.current(config
|
276
|
+
@distribution ||= Distributions.current(config)
|
207
277
|
end
|
208
278
|
|
209
279
|
# List of available buildpacks for the current distribution.
|
210
280
|
def buildpacks
|
211
|
-
distribution.buildpacks
|
281
|
+
distribution.buildpacks
|
212
282
|
end
|
213
283
|
|
214
284
|
# Buildpack detected for the app, if any.
|
@@ -221,14 +291,14 @@ module Pkgr
|
|
221
291
|
end
|
222
292
|
|
223
293
|
def fpm_command
|
224
|
-
distribution.fpm_command(build_dir
|
294
|
+
distribution.fpm_command(build_dir)
|
225
295
|
end
|
226
296
|
|
227
297
|
protected
|
228
298
|
def run_hook(file)
|
229
299
|
return true if file.nil?
|
230
300
|
|
231
|
-
cmd = %{env -i PATH="$PATH"#{config.env} bash '#{file}' 2>&1}
|
301
|
+
cmd = %{env -i APP_NAME="#{config.name}" PATH="$PATH"#{config.env} bash '#{file}' 2>&1}
|
232
302
|
|
233
303
|
Pkgr.logger.debug "Running hook in #{source_dir}: #{file.inspect}"
|
234
304
|
puts "-----> Running hook: #{file.inspect}"
|
data/lib/pkgr/cli.rb
CHANGED
@@ -21,7 +21,7 @@ module Pkgr
|
|
21
21
|
:desc => "Directory where to store the buildpacks",
|
22
22
|
:default => Pkgr::Buildpack.buildpacks_cache_dir
|
23
23
|
|
24
|
-
desc "package TARBALL", "Package the given tarball or directory, as a deb or rpm depending on the build machine"
|
24
|
+
desc "package TARBALL|DIRECTORY", "Package the given tarball or directory, as a deb or rpm depending on the build machine"
|
25
25
|
|
26
26
|
method_option :buildpack,
|
27
27
|
:type => :string,
|
@@ -110,6 +110,9 @@ module Pkgr
|
|
110
110
|
method_option :force_os,
|
111
111
|
:type => :string,
|
112
112
|
:desc => 'Force a specific distribution to build for (e.g. --force-os "ubuntu-12.04"). This may result in a broken package.'
|
113
|
+
method_option :store_cache,
|
114
|
+
:type => :boolean,
|
115
|
+
:desc => 'Output a tarball of the cache in the current directory (name: cache.tar.gz)'
|
113
116
|
|
114
117
|
def package(tarball)
|
115
118
|
Pkgr.level = Logger::INFO if options[:verbose]
|
@@ -122,13 +125,13 @@ module Pkgr
|
|
122
125
|
rescue => e
|
123
126
|
Pkgr.debug "#{e.class.name} : #{e.message}"
|
124
127
|
e.backtrace.each{|line| Pkgr.debug line}
|
125
|
-
puts "
|
128
|
+
puts " ! ERROR: #{e.message}"
|
126
129
|
exit 1
|
127
130
|
# Only used for logging. Re-raise immediately.
|
128
131
|
rescue Exception => e
|
129
132
|
Pkgr.debug "#{e.class.name} : #{e.message}"
|
130
133
|
e.backtrace.each{|line| Pkgr.debug line}
|
131
|
-
puts "
|
134
|
+
puts " ! SYSTEM ERROR: #{e.class.name} : #{e.message}"
|
132
135
|
raise e
|
133
136
|
end
|
134
137
|
end
|
data/lib/pkgr/command.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'mixlib/shellout'
|
3
|
+
|
4
|
+
module Pkgr
|
5
|
+
class Command
|
6
|
+
class CommandFailed < RuntimeError; end
|
7
|
+
|
8
|
+
attr_accessor :logger, :log_tag
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :default_timeout
|
12
|
+
default_timeout = 60
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(logger = nil, log_tag = nil)
|
16
|
+
@logger = logger || Logger.new(STDOUT)
|
17
|
+
@log_tag = log_tag
|
18
|
+
end
|
19
|
+
|
20
|
+
def stream!(command, env = {})
|
21
|
+
_stream(command, {env: env, fail: true, live_stream: logger})
|
22
|
+
end
|
23
|
+
|
24
|
+
def stream(command, env = {})
|
25
|
+
_stream(command, {env: env, live_stream: logger})
|
26
|
+
end
|
27
|
+
|
28
|
+
def capture!(command, error_message, env = {})
|
29
|
+
value = _run(command, {env: env, fail: true})
|
30
|
+
value = yield(value) if block_given?
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def capture(command, env = {})
|
35
|
+
value = _run(command, {env: env})
|
36
|
+
value = yield(value) if block_given?
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
def run(command, env = {})
|
41
|
+
_run(command, {env: env})
|
42
|
+
end
|
43
|
+
|
44
|
+
def run!(command, env = {})
|
45
|
+
_run(command, {env: env, fail: true})
|
46
|
+
end
|
47
|
+
|
48
|
+
def _stream(command, opts = {})
|
49
|
+
env = opts[:env] || {}
|
50
|
+
fail = !! opts[:fail]
|
51
|
+
env_string = env.map{|(k,v)| [k,"\"#{v}\""].join("=")}.join(" ")
|
52
|
+
|
53
|
+
logger.debug "sh(#{command})"
|
54
|
+
|
55
|
+
IO.popen("#{env_string} #{command}") do |io|
|
56
|
+
until io.eof?
|
57
|
+
data = io.gets
|
58
|
+
puts data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
raise CommandFailed, "Failure during packaging" if fail && ! $?.exitstatus.zero?
|
63
|
+
# raised when file does not exist
|
64
|
+
rescue Errno::ENOENT, RuntimeError => e
|
65
|
+
raise CommandFailed, e.message
|
66
|
+
end
|
67
|
+
|
68
|
+
def _run(command, opts = {})
|
69
|
+
env = opts[:env] || {}
|
70
|
+
live_stream = opts[:live_stream]
|
71
|
+
fail = !! opts[:fail]
|
72
|
+
|
73
|
+
cmd = Mixlib::ShellOut.new(
|
74
|
+
command,
|
75
|
+
environment: env,
|
76
|
+
timeout: self.class.default_timeout
|
77
|
+
)
|
78
|
+
|
79
|
+
# live stream is buggy, using IO.popen instead
|
80
|
+
# cmd.live_stream = logger if live_stream
|
81
|
+
cmd.logger = logger
|
82
|
+
cmd.log_level = :debug
|
83
|
+
cmd.log_tag = log_tag if log_tag
|
84
|
+
cmd.run_command
|
85
|
+
|
86
|
+
cmd.error! if fail
|
87
|
+
|
88
|
+
cmd.stdout.chomp
|
89
|
+
rescue RuntimeError, Errno::ENOENT => e
|
90
|
+
logger.error "Command failed: #{e.message}"
|
91
|
+
|
92
|
+
msg = ["Command failed"]
|
93
|
+
cmd.stdout.split("\n").each do |line|
|
94
|
+
msg.push "STDOUT -- #{line}"
|
95
|
+
end
|
96
|
+
cmd.stderr.split("\n").each do |line|
|
97
|
+
msg.push "STDERR -- #{line}"
|
98
|
+
end
|
99
|
+
raise CommandFailed, msg.join("\n")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|