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.
@@ -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
- adduser "${APP_USER}" --user-group --system --create-home --shell /bin/bash
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
- adduser "${APP_USER}" --disabled-login --group --system --quiet --shell /bin/bash
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 %>
@@ -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
@@ -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.force_os)
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(config)
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, config)
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}"
@@ -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 " ! ERROR: #{e.message}"
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 " ! SYSTEM ERROR: #{e.class.name} : #{e.message}"
134
+ puts " ! SYSTEM ERROR: #{e.class.name} : #{e.message}"
132
135
  raise e
133
136
  end
134
137
  end
@@ -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