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



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

def app_home_dir
  config.home
end

#build_dirObject

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



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

def build_dir
  @build_dir ||= Dir.mktmpdir
end

#buildpack_for_appObject

Buildpack detected for the app, if any.



294
295
296
297
298
299
300
# File 'lib/pkgr/builder.rb', line 294

def buildpack_for_app
  raise "#{source_dir} does not exist" unless File.directory?(source_dir)
  @buildpack_for_app ||= buildpacks.find do |buildpack|
    buildpack.setup(config.edge, config.home)
    buildpack.detect(source_dir)
  end
end

#buildpacksObject

List of available buildpacks for the current distribution.



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

def buildpacks
  distribution.buildpacks
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
# File 'lib/pkgr/builder.rb', line 109

def compile
  if buildpack_for_app
    puts "-----> #{buildpack_for_app.banner} app"

    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)

    run_hook config.before_hook
    buildpack_for_app.compile(source_dir, compile_cache_dir, compile_env_dir)
    buildpack_for_app.release(source_dir)
    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.



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

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.



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

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

#config_fileObject



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

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

#distributionObject

Returns the current distribution we’re packaging for.



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

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



302
303
304
# File 'lib/pkgr/builder.rb', line 302

def fpm_command
  distribution.fpm_command(build_dir)
end

#package(remaining_attempts = 3) ⇒ Object

Launch the FPM command that will generate the package.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/pkgr/builder.rb', line 178

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.



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

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

#procfileObject

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



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

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

#procfile_entriesObject



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/pkgr/builder.rb', line 211

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.



232
233
234
# File 'lib/pkgr/builder.rb', line 232

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

#scaling_dirObject



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

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



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/pkgr/builder.rb', line 164

def setup_crons
  crons_dir = File.join("/", distribution.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.



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

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

#store_cacheObject



199
200
201
202
203
204
# File 'lib/pkgr/builder.rb', line 199

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



207
208
209
# File 'lib/pkgr/builder.rb', line 207

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



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

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

#verifyObject



194
195
196
197
# File 'lib/pkgr/builder.rb', line 194

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.



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

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.



151
152
153
154
155
156
157
158
159
160
161
# File 'lib/pkgr/builder.rb', line 151

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