Class: Berkshelf::Berksfile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Mixin::DSLEval, Mixin::Logging
Defined in:
lib/berkshelf/berksfile.rb

Constant Summary

@@active_group =
nil

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from Mixin::DSLEval

#dsl_eval, #dsl_eval_file, included

Methods included from Mixin::Logging

#log, #log_exception

Constructor Details

- (Berksfile) initialize(path)



87
88
89
90
91
92
# File 'lib/berkshelf/berksfile.rb', line 87

def initialize(path)
  @filepath         = path
  @sources          = Hash.new
  @downloader       = Downloader.new(Berkshelf.cookbook_store)
  @cached_cookbooks = nil
end

Instance Attribute Details

- (Array<Berkshelf::CachedCookbook>) cached_cookbooks (readonly)



80
81
82
# File 'lib/berkshelf/berksfile.rb', line 80

def cached_cookbooks
  @cached_cookbooks
end

- (Berkshelf::Downloader) downloader (readonly)



77
78
79
# File 'lib/berkshelf/berksfile.rb', line 77

def downloader
  @downloader
end

- (String) filepath (readonly)



74
75
76
# File 'lib/berkshelf/berksfile.rb', line 74

def filepath
  @filepath
end

Class Method Details

+ (Berksfile) from_file(file)

Raises:



8
9
10
11
12
13
14
15
16
# File 'lib/berkshelf/berksfile.rb', line 8

def from_file(file)
  raise BerksfileNotFound.new(file) unless File.exist?(file)

  begin
    new(file).dsl_eval_file(file)
  rescue => ex
    raise BerksfileReadError.new(ex)
  end
end

+ (String) vendor(cookbooks, path)

Copy all cached_cookbooks to the given directory. Each cookbook will be contained in a directory named after the name of the cookbook.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/berkshelf/berksfile.rb', line 28

def vendor(cookbooks, path)
  chefignore = nil
  path       = File.expand_path(path)
  scratch    = Berkshelf.mktmpdir

  FileUtils.mkdir_p(path)

  unless (ignore_file = Berkshelf::Chef::Cookbook::Chefignore.find_relative_to(Dir.pwd)).nil?
    chefignore = Berkshelf::Chef::Cookbook::Chefignore.new(ignore_file)
  end

  cookbooks.each do |cb|
    dest = File.join(scratch, cb.cookbook_name, '/')
    FileUtils.mkdir_p(dest)

    # Dir.glob does not support backslash as a File separator
    src = cb.path.to_s.gsub('\\', '/')
    files = Dir.glob(File.join(src, '*'))

    # Filter out files using chefignore
    files = chefignore.remove_ignores_from(files) if chefignore

    FileUtils.cp_r(files, dest)
  end

  FileUtils.remove_dir(path, force: true)
  FileUtils.mv(scratch, path)

  path
end

Instance Method Details

- (Berkshelf::CookbookSource) [](name) Also known as: get_source



370
371
372
# File 'lib/berkshelf/berksfile.rb', line 370

def [](name)
  @sources[name]
end

- (Array<Berkshelf::CookbookSource]) add_source(name, constraint = nil, options = {})

Add a source of the given name and constraint to the array of sources.

Raises:

  • (DuplicateSourceDefined)

    if a source is added whose name conflicts with a source who has already been added.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/berkshelf/berksfile.rb', line 253

def add_source(name, constraint = nil, options = {})
  if has_source?(name)
    # Only raise an exception if the source is a true duplicate
    groups = (options[:group].nil? || options[:group].empty?) ? [:default] : options[:group]
    if !(@sources[name].groups & groups).empty?
      raise DuplicateSourceDefined,
        "Berksfile contains multiple sources named '#{name}'. Use only one, or put them in different groups."
    end
  end

  if options[:path]
     = File.join(options[:path], 'metadata.rb')
  end

  options[:constraint] = constraint

  @sources[name] = CookbookSource.new(self, name, options)
end

- (Object) apply(environment_name, options = {})

Resolve this Berksfile and apply the locks found in the generated Berksfile.lock to the target Chef environment

Options Hash (options):

  • :ssl_verify (Hash) — default: true

    Disable/Enable SSL verification during uploads

Raises:



567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
# File 'lib/berkshelf/berksfile.rb', line 567

def apply(environment_name, options = {})
  conn        = ridley_connection(options)
  environment = conn.environment.find(environment_name)

  if environment
    install

    environment.cookbook_versions = {}.tap do |cookbook_versions|
      lockfile.sources.each { |source| cookbook_versions[source.name] = source.locked_version.to_s }
    end

    environment.save
  else
    raise EnvironmentNotFound.new(environment_name)
  end
rescue Ridley::Errors::RidleyError => ex
  raise ChefConnectionError, ex
ensure
  conn.terminate if conn && conn.alive?
end

- (Hash) chef_api(value, options = {})

Note:

specifying the symbol :config as the value of the chef_api default location will attempt to use the contents of your Berkshelf configuration to find the Chef API to interact with.

Add a 'Chef API' default location which will be used to resolve cookbook sources that do not contain an explicit location.

Examples:

using the symbol :config to add a Chef API default location

chef_api :config

using a URL, node_name, and client_key to add a Chef API default location

chef_api 'https://api.opscode.com/organizations/vialstudios', node_name: 'reset',
  client_key: '/Users/reset/.chef/knife.rb'


237
238
239
# File 'lib/berkshelf/berksfile.rb', line 237

def chef_api(value, options = {})
  add_location(:chef_api, value, options)
end

- (Object) cookbook(name, version_constraint, options = {}) - (Object) cookbook(name, options = {})

Add a cookbook dependency to the Berksfile to be retrieved and have it's dependencies recursively retrieved and resolved.

Examples:

a cookbook source that will be retrieved from one of the default locations

cookbook 'artifact'

a cookbook source that will be retrieved from a path on disk

cookbook 'artifact', path: '/Users/reset/code/artifact'

a cookbook source that will be retrieved from a remote community site

cookbook 'artifact', site: 'http://cookbooks.opscode.com/api/v1/cookbooks'

a cookbook source that will be retrieved from the latest API of the Opscode Community Site

cookbook 'artifact', site: :opscode

a cookbook source that will be retrieved from a Git server

cookbook 'artifact', git: 'git://github.com/RiotGames/artifact-cookbook.git'

a cookbook source that will be retrieved from a Chef API (Chef Server)

cookbook 'artifact', chef_api: 'https://api.opscode.com/organizations/vialstudios',
  node_name: 'reset', client_key: '/Users/reset/.chef/knife.rb'

a cookbook source that will be retrieved from a Chef API using your Berkshelf config

cookbook 'artifact', chef_api: :config

Overloads:

  • - (Object) cookbook(name, version_constraint, options = {})

    Options Hash (options):

    • :group (Symbol, Array)

      the group or groups that the cookbook belongs to

    • :chef_api (String, Symbol)

      a URL to a Chef API. Alternatively the symbol :config can be provided which will instantiate this location with the values found in your Berkshelf configuration.

    • :site (String)

      a URL pointing to a community API endpoint

    • :path (String)

      a filepath to the cookbook on your local disk

    • :git (String)

      the Git URL to clone

    See Also:

  • - (Object) cookbook(name, options = {})

    Options Hash (options):

    • :group (Symbol, Array)

      the group or groups that the cookbook belongs to

    • :chef_api (String, Symbol)

      a URL to a Chef API. Alternatively the symbol :config can be provided which will instantiate this location with the values found in your Berkshelf configuration.

    • :site (String)

      a URL pointing to a community API endpoint

    • :path (String)

      a filepath to the cookbook on your local disk

    • :git (String)

      the Git URL to clone

    See Also:



162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/berkshelf/berksfile.rb', line 162

def cookbook(*args)
  options = args.last.is_a?(Hash) ? args.pop : Hash.new
  name, constraint = args

  options[:path] &&= File.expand_path(options[:path], File.dirname(filepath))
  options[:group] = Array(options[:group])

  if @@active_group
    options[:group] += @@active_group
  end

  add_source(name, constraint, options)
end

- (Berkshelf::CookbookSource?) find(name)

Find a source defined in this berksfile by name.



337
338
339
# File 'lib/berkshelf/berksfile.rb', line 337

def find(name)
  @sources[name]
end

- (Object) group(*args)



176
177
178
179
180
# File 'lib/berkshelf/berksfile.rb', line 176

def group(*args)
  @@active_group = args
  yield
  @@active_group = nil
end

- (Hash) groups



355
356
357
358
359
360
361
362
363
364
# File 'lib/berkshelf/berksfile.rb', line 355

def groups
  {}.tap do |groups|
    sources.each do |source|
      source.groups.each do |group|
        groups[group] ||= []
        groups[group] << source
      end
    end
  end
end

- (Boolean) has_source?(source)



284
285
286
# File 'lib/berkshelf/berksfile.rb', line 284

def has_source?(source)
  @sources.has_key?(source.to_s)
end

- (Array<Berkshelf::CachedCookbook>) install(options = {})

Install the sources listed in the Berksfile, respecting the locked versions in the Berksfile.lock.

  1. Check that a lockfile exists. If a lockfile does not exist, all sources are considered to be “unlocked”. If a lockfile is specified, a definition is created via the following algorithm:

    • For each source, see if there exists a locked version that still satisfies the version constraint in the Berksfile. If there exists such a source, remove it from the list of unlocked sources. If not, then either a version constraint has changed, or a new source has been added to the Berksfile. In the event that a locked_source exists, but it no longer satisfies the constraint, this method will raise a OutdatedCookbookSource, and inform the user to run berks update COOKBOOK to remedy the issue.

    • Remove any locked sources that no longer exist in the Berksfile (i.e. a cookbook source was removed from the Berksfile).

  2. Resolve the collection of locked and unlocked sources.

  3. Write out a new lockfile.

Options Hash (options):

  • :except (Symbol, Array)

    Group(s) to exclude which will cause any sources marked as a member of the group to not be installed

  • :only (Symbol, Array)

    Group(s) to include which will cause any sources marked as a member of the group to be installed and all others to be ignored

  • :path (String)

    a path to “vendor” the cached_cookbooks resolved by the resolver. Vendoring is a technique for packaging all cookbooks resolved by a Berksfile.

  • :update_lockfile (Boolean) — default: true

    a boolean method indicating whether we should update the lockfile

Raises:



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/berkshelf/berksfile.rb', line 415

def install(options = {})
  local_sources = apply_lockfile(sources(options))

  resolver          = resolve(local_sources)
  @cached_cookbooks = resolver[:solution]
  local_sources     = resolver[:sources]

  verify_licenses!

  self.class.vendor(@cached_cookbooks, options[:path]) if options[:path]

  lockfile.update(local_sources) unless options[:update_lockfile] == false

  self.cached_cookbooks
end

- (Berkshelf::Lockfile) lockfile

Get the lockfile corresponding to this Berksfile. This is necessary because the user can specify a different path to the Berksfile. So assuming the lockfile is named “Berksfile.lock” is a poor assumption.



681
682
683
# File 'lib/berkshelf/berksfile.rb', line 681

def lockfile
  @lockfile ||= Berkshelf::Lockfile.new(self)
end

- (Object) metadata(options = {})

Use a Cookbook metadata file to determine additional cookbook sources to retrieve. All sources found in the metadata will use the default locations set in the Berksfile (if any are set) or the default locations defined by Berkshelf.

Options Hash (options):

  • :path (String)

    path to the metadata file



190
191
192
193
194
195
196
197
198
199
# File 'lib/berkshelf/berksfile.rb', line 190

def (options = {})
  path = options[:path] || File.dirname(filepath)

   = File.expand_path(File.join(path, 'metadata.rb'))
   = Ridley::Chef::Cookbook::Metadata.from_file()

  name = .name.presence || File.basename(File.expand_path(path))

  add_source(name, nil, { path: path, metadata: true })
end

- (Hash) outdated(options = {})

Get a list of all the cookbooks which have newer versions found on the community site versus what your current constraints allow

Examples:

berksfile.outdated => {
  #<CachedCookbook name="artifact"> => "0.11.2"
}

Options Hash (options):

  • :except (Symbol, Array)

    Group(s) to exclude which will cause any sources marked as a member of the group to not be installed

  • :only (Symbol, Array)

    Group(s) to include which will cause any sources marked as a member of the group to be installed and all others to be ignored



469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/berkshelf/berksfile.rb', line 469

def outdated(options = {})
  outdated = Hash.new

  sources(options).each do |cookbook|
    location = cookbook.location || Location.init(cookbook.name, cookbook.version_constraint, site: :opscode)

    if location.is_a?(SiteLocation)
      latest_version = location.latest_version

      unless cookbook.version_constraint.satisfies?(latest_version)
        outdated[cookbook] = latest_version
      end
    end
  end

  outdated
end

- (String) package(name = nil, options = {})

Package the given cookbook for distribution outside of berkshelf. If the name attribute is not given, all cookbooks in the Berksfile will be packaged.

Options Hash (options):

  • :output (String)

    the path to output the tarball

  • :skip_dependencies (Boolean)

    package cookbook dependencies as well

  • :ignore_chefignore (Boolean)

    do not apply the chefignore file to the packed cookbooks



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
# File 'lib/berkshelf/berksfile.rb', line 606

def package(name = nil, options = {})
  tar_name = "#{name || 'package'}.tar.gz"
  output = File.expand_path(File.join(options[:output], tar_name))

  unless name.nil?
    source = self.find(name)
    raise CookbookNotFound, "Cookbook '#{name}' is not in your Berksfile" unless source

    package = Berkshelf.ui.mute {
      self.resolve(source, options)[:solution]
    }
  else
    package = Berkshelf.ui.mute {
      self.resolve(sources, options)[:solution]
    }
  end

  package.each do |cookbook|
    validate_files!(cookbook)
  end

  Dir.mktmpdir do |tmp|
    package.each do |cached_cookbook|
      path = cached_cookbook.path.to_s
      destination = File.join(tmp, cached_cookbook.cookbook_name)

      FileUtils.cp_r(path, destination)

      unless options[:ignore_chefignore]
        if ignore_file = Berkshelf::Chef::Cookbook::Chefignore.find_relative_to(path)
          chefignore = Berkshelf::Chef::Cookbook::Chefignore.new(ignore_file)
          chefignore.remove_ignores_from(destination) if chefignore
        end
      end
    end

    FileUtils.mkdir_p(options[:output])

    Dir.chdir(tmp) do |dir|
      tgz = Zlib::GzipWriter.new(File.open(output, 'wb'))
      Archive::Tar::Minitar.pack('.', tgz)
    end
  end

  Berkshelf.formatter.package(name, output)

  output
end

- (Berkshelf::CookbookSource) remove_source(source)



276
277
278
# File 'lib/berkshelf/berksfile.rb', line 276

def remove_source(source)
  @sources.delete(source.to_s)
end

- (Array<Berkshelf::CachedCookbooks>) resolve(sources = [], options = {})

Finds a solution for the Berksfile and returns an array of CachedCookbooks.

Options Hash (options):

  • :skip_dependencies (Boolean)

    Skip resolving of dependencies



664
665
666
667
668
669
670
671
672
# File 'lib/berkshelf/berksfile.rb', line 664

def resolve(sources = [], options = {})
  resolver = Resolver.new(
    self,
    sources: sources,
    skip_dependencies: options[:skip_dependencies]
  )

  { solution: resolver.resolve, sources: resolver.sources }
end

- (Hash) site(value)

Note:

specifying the symbol :opscode as the value of the site default location is an alias for the latest API of the Opscode Community Site.

Add a 'Site' default location which will be used to resolve cookbook sources that do not contain an explicit location.

Examples:

site :opscode
site "http://cookbooks.opscode.com/api/v1/cookbooks"


215
216
217
# File 'lib/berkshelf/berksfile.rb', line 215

def site(value)
  add_location(:site, value)
end

- (Array<Berkshelf::CookbookSource>) sources(options = {})

The list of cookbook sources specified in this Berksfile

Options Hash (options):

  • :except (Symbol, Array)

    group(s) to exclude to exclude from the returned Array of sources group to not be installed

  • :only (Symbol, Array)

    group(s) to include which will cause any sources marked as a member of the group to be installed and all others to be ignored

Raises:



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/berkshelf/berksfile.rb', line 307

def sources(options = {})
  l_sources = @sources.values

  cookbooks = Array(options[:cookbooks])
  except    = Array(options[:except]).collect(&:to_sym)
  only      = Array(options[:only]).collect(&:to_sym)

  case
  when !except.empty? && !only.empty?
    raise Berkshelf::ArgumentError, 'Cannot specify both :except and :only'
  when !cookbooks.empty?
    if !except.empty? && !only.empty?
      Berkshelf.ui.warn 'Cookbooks were specified, ignoring :except and :only'
    end
    l_sources.select { |source| cookbooks.include?(source.name) }
  when !except.empty?
    l_sources.select { |source| (except & source.groups).empty? }
  when !only.empty?
    l_sources.select { |source| !(only & source.groups).empty? }
  else
    l_sources
  end
end

- (Object) update(options = {})

Options Hash (options):

  • :except (Symbol, Array)

    Group(s) to exclude which will cause any sources marked as a member of the group to not be installed

  • :only (Symbol, Array)

    Group(s) to include which will cause any sources marked as a member of the group to be installed and all others to be ignored



439
440
441
442
443
444
445
446
447
# File 'lib/berkshelf/berksfile.rb', line 439

def update(options = {})
  validate_cookbook_names!(options)

  # Unlock any/all specified cookbooks
  sources(options).each { |source| lockfile.unlock(source) }

  # NOTE: We intentionally do NOT pass options to the installer
  self.install
end

- (Object) upload(options = {})

Upload the cookbooks installed by this Berksfile

Options Hash (options):

  • :force (Boolean) — default: false

    Upload the Cookbook even if the version already exists and is frozen on the target Chef Server

  • :freeze (Boolean) — default: true

    Freeze the uploaded Cookbook on the Chef Server so that it cannot be overwritten

  • :except (Symbol, Array)

    Group(s) to exclude which will cause any sources marked as a member of the group to not be installed

  • :only (Symbol, Array)

    Group(s) to include which will cause any sources marked as a member of the group to be installed and all others to be ignored

  • :cookbooks (String, Array)

    Names of the cookbooks to retrieve sources for

  • :ssl_verify (Hash) — default: true

    Disable/Enable SSL verification during uploads

  • :skip_dependencies (Boolean) — default: false

    Skip uploading dependent cookbook(s).

  • :halt_on_frozen (Boolean) — default: false

    Raise a FrozenCookbook error if one of the cookbooks being uploaded is already located on the remote Chef Server and frozen.

  • :server_url (String)

    An overriding Chef Server to upload the cookbooks to

  • :client_name (String)

    An overriding client name to use for connecting to the chef server

  • :client_key (String)

    An overriding client key to use for connecting to the chef server

Raises:

  • (UploadFailure)

    if you are uploading cookbooks with an invalid or not-specified client key

  • (Berkshelf::FrozenCookbook)

    if an attempt to upload a cookbook which has been frozen on the target server is made and the :halt_on_frozen option was true



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/berkshelf/berksfile.rb', line 521

def upload(options = {})
  options = options.reverse_merge(force: false, freeze: true, skip_dependencies: false, halt_on_frozen: false, update_lockfile: false)

  cached_cookbooks = install(options)
  upload_opts      = options.slice(:force, :freeze)
  conn             = ridley_connection(options)

  cached_cookbooks.each do |cookbook|
    Berkshelf.formatter.upload(cookbook.cookbook_name, cookbook.version, conn.server_url)
    validate_files!(cookbook)

    begin
      conn.cookbook.upload(cookbook.path, upload_opts.merge(name: cookbook.cookbook_name))
    rescue Ridley::Errors::FrozenCookbook => ex
      if options[:halt_on_frozen]
        raise Berkshelf::FrozenCookbook, ex
      end
    end
  end

  if options[:skip_dependencies]
    missing_cookbooks = options.fetch(:cookbooks, nil) - cached_cookbooks.map(&:cookbook_name)
    unless missing_cookbooks.empty?
      msg = "Unable to upload cookbooks: #{missing_cookbooks.sort.join(', ')}\n"
      msg << "Specified cookbooks must be defined within the Berkshelf file when using the"
      msg << " `--skip-dependencies` option"
      raise ExplicitCookbookNotFound.new(msg)
    end
  end
rescue Ridley::Errors::RidleyError => ex
  log_exception(ex)
  raise ChefConnectionError, ex # todo implement
ensure
  conn.terminate if conn && conn.alive?
end