pkgr 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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