Class: Pkgr::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/pkgr/builder.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tarball, config) ⇒ Builder

Accepts a path to a tarball (gzipped or not), or you can pass ‘-’ to read from stdin.



15
16
17
18
19
# File 'lib/pkgr/builder.rb', line 15

def initialize(tarball, config)
  @tarball = tarball
  @config = config
  Pkgr.debug "Initializing builder with the following config: #{config.inspect}"
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



12
13
14
# File 'lib/pkgr/builder.rb', line 12

def config
  @config
end

#tarballObject (readonly)

Returns the value of attribute tarball.



12
13
14
# File 'lib/pkgr/builder.rb', line 12

def tarball
  @tarball
end

Instance Method Details

#app_home_dirObject

Some buildpacks may need the target home dir to exist



273
274
275
# File 'lib/pkgr/builder.rb', line 273

def app_home_dir
  config.home
end

#build_dirObject

Build directory. Will be used by fpm to make the package.



250
251
252
# File 'lib/pkgr/builder.rb', line 250

def build_dir
  @build_dir ||= Dir.mktmpdir
end

#buildpacks_for_appObject

Buildpacks detected for the app, if any. If multiple buildpacks are explicitly specified, all are used



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/pkgr/builder.rb', line 293

def buildpacks_for_app
  raise "#{source_dir} does not exist" unless File.directory?(source_dir)
  @buildpacks_for_app ||= begin
    mode, buildpacks = distribution.buildpacks
    case mode
    when :custom
      buildpacks.find_all do |buildpack|
        buildpack.setup(config.edge, config.home)
        buildpack.detect(source_dir)
      end
    else
      [buildpacks.find do |buildpack|
        buildpack.setup(config.edge, config.home)
        buildpack.detect(source_dir)
      end].compact
    end
  end
end

#callObject

Launch the full packaging procedure



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/pkgr/builder.rb', line 22

def call
  extract
  update_config
  check
  setup
  setup_pipeline
  compile
  write_env
  write_init
  setup_crons
  package
  store_cache
ensure
  teardown if config.clean
end

#checkObject

Check configuration, and verifies that the current distribution’s requirements are satisfied



88
89
90
91
# File 'lib/pkgr/builder.rb', line 88

def check
  raise Errors::ConfigurationInvalid, config.errors.join("; ") unless config.valid?
  distribution.check
end

#compileObject

Pass the app through the buildpack



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/pkgr/builder.rb', line 109

def compile
  begin
    FileUtils.mkdir_p(app_home_dir)
  rescue Errno::EACCES => e
    Pkgr.logger.warn "Can't create #{app_home_dir.inspect}, which may be needed by some buildpacks."
  end
  FileUtils.mkdir_p(compile_cache_dir)
  FileUtils.mkdir_p(compile_env_dir)

  if buildpacks_for_app.size > 0
    run_hook config.before_hook

    buildpacks_for_app.each do |buildpack|
      puts "-----> #{buildpack.banner} app"
      buildpack.compile(source_dir, compile_cache_dir, compile_env_dir)
      buildpack.release(source_dir)
    end

    run_hook config.after_hook
  else
    raise Errors::UnknownAppType, "Can't find a buildpack for your app"
  end
end

#compile_cache_dirObject

Directory where the buildpacks can store stuff.



278
279
280
# File 'lib/pkgr/builder.rb', line 278

def compile_cache_dir
  config.compile_cache_dir || File.join(source_dir, ".git/cache")
end

#compile_env_dirObject

Directory where the buildpacks can store config envs.



283
284
285
# File 'lib/pkgr/builder.rb', line 283

def compile_env_dir
  config.compile_env_dir ||= Dir.mktmpdir
end

#config_fileObject



240
241
242
# File 'lib/pkgr/builder.rb', line 240

def config_file
  File.join(source_dir, ".pkgr.yml")
end

#distributionObject

Returns the current distribution we’re packaging for.



288
289
290
# File 'lib/pkgr/builder.rb', line 288

def distribution
  @distribution ||= Distributions.current(config)
end

#extractObject

Extract the given tarball to the target directory



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/pkgr/builder.rb', line 39

def extract
  FileUtils.mkdir_p source_dir

  opts = {}
  if tarball == "-"
    # FIXME: not really happy with reading everything in memory
    opts[:input] = $stdin.read
  end

  tarball_extract = Mixlib::ShellOut.new("tar xzf #{tarball} -C #{source_dir}", opts)
  tarball_extract.logger = Pkgr.logger
  tarball_extract.run_command
  tarball_extract.error!
end

#fpm_commandObject



312
313
314
# File 'lib/pkgr/builder.rb', line 312

def fpm_command
  distribution.fpm_command(build_dir)
end

#package(remaining_attempts = 3) ⇒ Object

Launch the FPM command that will generate the package.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/pkgr/builder.rb', line 182

def package(remaining_attempts = 3)
  app_package = Mixlib::ShellOut.new(fpm_command)
  app_package.logger = Pkgr.logger
  app_package.run_command
  app_package.error!
  begin
    verify
  rescue Mixlib::ShellOut::ShellCommandFailed => e
    if remaining_attempts > 0
      package(remaining_attempts - 1)
    else
      raise
    end
  end
end

#pipelineObject



77
78
79
80
81
82
83
84
85
# File 'lib/pkgr/builder.rb', line 77

def pipeline
  @pipeline ||= begin
    components = []
    unless config.wizards.empty? || config.installer == false
      components << Installer.new(config.installer, distribution).setup
    end
    components
  end
end

#proc_dirObject

Directory where binstubs will be created for the corresponding Procfile commands.



259
260
261
# File 'lib/pkgr/builder.rb', line 259

def proc_dir
  File.join(vendor_dir, "processes")
end

#procfileObject

Returns the path to the app’s (supposedly present) Procfile.



268
269
270
# File 'lib/pkgr/builder.rb', line 268

def procfile
  File.join(source_dir, "Procfile")
end

#procfile_entriesObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/pkgr/builder.rb', line 215

def procfile_entries
  @procfile_entries ||= begin
    default_process_types = YAML.load_file(release_file)["default_process_types"]

    default_process_types = {} unless default_process_types

    entries = if File.exist?(procfile)
      File.read(procfile).gsub("\r\n","\n").split("\n").map do |line|
        if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
          [$1, $2]
        end
      end.compact
    else
      []
    end

    default_process_types.merge(Hash[entries]).map{|name, command| Process.new(name, command)}
  end
end

#release_fileObject

Path to the release file generated after the buildpack compilation.



236
237
238
# File 'lib/pkgr/builder.rb', line 236

def release_file
  File.join(source_dir, ".release")
end

#scaling_dirObject



263
264
265
# File 'lib/pkgr/builder.rb', line 263

def scaling_dir
  File.join(vendor_dir, "scaling")
end

#setupObject

Setup the build directory structure



94
95
96
97
98
99
100
# File 'lib/pkgr/builder.rb', line 94

def setup
  Dir.chdir(build_dir) do
    distribution.templates.each do |template|
      template.install(config.sesame)
    end
  end
end

#setup_cronsObject

Write cron files



167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/pkgr/builder.rb', line 167

def setup_crons
  crons_dir = File.join("/", distribution.crons_dir)
  config.crons_dir = crons_dir

  config.crons.map! do |cron_path|
    Cron.new(File.expand_path(cron_path, config.home), File.join(crons_dir, File.basename(cron_path)))
  end

  config.crons.each do |cron|
    puts "-----> [cron] #{cron.source} => #{cron.destination}"
  end
end

#setup_pipelineObject



102
103
104
105
106
# File 'lib/pkgr/builder.rb', line 102

def setup_pipeline
  pipeline.each do |component|
    @config = component.call(config)
  end
end

#source_dirObject

Path to the directory containing the main app files.



245
246
247
# File 'lib/pkgr/builder.rb', line 245

def source_dir
  File.join(build_dir, config.home)
end

#store_cacheObject



203
204
205
206
207
208
# File 'lib/pkgr/builder.rb', line 203

def store_cache
  return true unless config.store_cache
  generate_cache_tarball = Mixlib::ShellOut.new %{tar czf cache.tar.gz -C #{compile_cache_dir} .}
  generate_cache_tarball.logger = Pkgr.logger
  generate_cache_tarball.run_command
end

#teardownObject

Make sure to get rid of the build directory



211
212
213
# File 'lib/pkgr/builder.rb', line 211

def teardown
  FileUtils.rm_rf(build_dir)
end

#update_configObject

Update existing config with the one from .pkgr.yml file, if any



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/pkgr/builder.rb', line 55

def update_config
  if File.exist?(config_file)
    Pkgr.debug "Loading #{distribution.slug} from #{config_file}."
    @config = Config.load_file(config_file, distribution.slug).merge(config)
    Pkgr.debug "Found .pkgr.yml file. Updated config is now: #{config.inspect}"

    # update distribution config
    distribution.config = @config

    # FIXME: make Config the authoritative source of the runner config (distribution only tells the default runner)
    if @config.runner
      type, *version = @config.runner.split("-")
      distribution.runner = Distributions::Runner.new(type, version.join("-"))
    end
  end
  config.distribution = distribution
  config.env.variables.push("TARGET=#{distribution.target}")
  # useful for templates that need to read files
  config.source_dir = source_dir
  config.build_dir = build_dir
end

#vendor_dirObject



254
255
256
# File 'lib/pkgr/builder.rb', line 254

def vendor_dir
  File.join(source_dir, "vendor", "pkgr")
end

#verifyObject



198
199
200
201
# File 'lib/pkgr/builder.rb', line 198

def verify
  return true unless config.verify
  distribution.verify(Dir.pwd)
end

#write_envObject

Parses the output of buildpack/bin/release executable to find out its default Procfile commands. Then merges those with the ones from the app’s Procfile (if any). Finally, generates a binstub in vendor/pkgr/processes/ so that these commands can be called using the app’s executable.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/pkgr/builder.rb', line 136

def write_env
  FileUtils.mkdir_p proc_dir

  procfile_entries.each do |process|
    process_file = File.join(proc_dir, process.name)

    File.open(process_file, "w+") do |f|
      f.puts "#!/bin/sh"
      f << "exec "
      f << process.command
      f << " $@"
    end

    FileUtils.chmod 0755, process_file
  end
end

#write_initObject

Write startup scripts.



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/pkgr/builder.rb', line 154

def write_init
  FileUtils.mkdir_p scaling_dir
  Dir.chdir(scaling_dir) do
    distribution.initializers_for(config.name, procfile_entries).each do |(process, file)|
      process_config = config.dup
      process_config.process_name = process.name
      process_config.process_command = process.command
      file.install(process_config.sesame)
    end
  end
end