Class: Berkshelf::Berksfile

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

Constant Summary collapse

@@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

#initialize(path) ⇒ Berksfile

Returns a new instance of Berksfile.

Parameters:

  • path (String)

    path on disk to the file containing the contents of this Berksfile



85
86
87
88
89
90
# File 'lib/berkshelf/berksfile.rb', line 85

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

Instance Attribute Details

#cached_cookbooksArray<Berkshelf::CachedCookbook> (readonly)

Returns:



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

def cached_cookbooks
  @cached_cookbooks
end

#downloaderBerkshelf::Downloader (readonly)



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

def downloader
  @downloader
end

#filepathString (readonly)

Returns The path on disk to the file representing this instance of Berksfile.

Returns:

  • (String)

    The path on disk to the file representing this instance of Berksfile



72
73
74
# File 'lib/berkshelf/berksfile.rb', line 72

def filepath
  @filepath
end

Class Method Details

.from_file(file) ⇒ Berksfile

Parameters:

  • file (#to_s)

    a path on disk to a Berksfile to instantiate from

Returns:



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

def from_file(file)
  new(file).dsl_eval_file(file)
rescue Errno::ENOENT => ex
  raise BerksfileNotFound, "No Berksfile or Berksfile.lock found at: #{file}"
rescue => ex
  raise BerksfileReadError.new(ex)
end

.vendor(cookbooks, path) ⇒ String

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

Parameters:

  • cookbooks (Array<CachedCookbook>)

    an array of CachedCookbooks to be copied to a vendor directory

  • path (String)

    filepath to vendor cookbooks to

Returns:

  • (String)

    expanded filepath to the vendor directory



26
27
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
# File 'lib/berkshelf/berksfile.rb', line 26

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

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

Parameters:

  • name (String)

    name of the source to return

Returns:



368
369
370
# File 'lib/berkshelf/berksfile.rb', line 368

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

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

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

Parameters:

  • name (String)

    the name of the source to add

  • constraint (String, Solve::Constraint) (defaults to: nil)

    the constraint to lock the source to

  • options (Hash) (defaults to: {})

Returns:

Raises:

  • (DuplicateSourceDefined)

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



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

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

#apply(environment_name, options = {}) ⇒ Object

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

Parameters:

  • environment_name (String)
  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :ssl_verify (Hash) — default: true

    Disable/Enable SSL verification during uploads

Raises:



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# File 'lib/berkshelf/berksfile.rb', line 559

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

#chef_api(value, options = {}) ⇒ Hash

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'

Parameters:

  • value (String, Symbol)
  • options (Hash) (defaults to: {})

Returns:

  • (Hash)


235
236
237
# File 'lib/berkshelf/berksfile.rb', line 235

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

#cookbook(name, version_constraint, options = {}) ⇒ Object #cookbook(name, options = {}) ⇒ Object

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:

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

    Parameters:

    • name (#to_s)
    • version_constraint (#to_s)
    • options (Hash) (defaults to: {})

    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:

  • #cookbook(name, options = {}) ⇒ Object

    Parameters:

    • name (#to_s)
    • options (Hash) (defaults to: {})

    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:



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

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

#find(name) ⇒ Berkshelf::CookbookSource?

Find a source defined in this berksfile by name.

Parameters:

  • name (String)

    the name of the cookbook source to search for

Returns:



335
336
337
# File 'lib/berkshelf/berksfile.rb', line 335

def find(name)
  @sources[name]
end

#group(*args) ⇒ Object



174
175
176
177
178
# File 'lib/berkshelf/berksfile.rb', line 174

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

#groupsHash

Returns a hash containing group names as keys and an array of CookbookSources that are a member of that group as values

Example:

{
  nautilus: [
    #<Berkshelf::CookbookSource: nginx (~> 1.0.0)>,
    #<Berkshelf::CookbookSource: mysql (~> 1.2.4)>
  ],
  skarner: [
    #<Berkshelf::CookbookSource: nginx (~> 1.0.0)>
  ]
}.

Returns:

  • (Hash)

    a hash containing group names as keys and an array of CookbookSources that are a member of that group as values

    Example:

    {
      nautilus: [
        #<Berkshelf::CookbookSource: nginx (~> 1.0.0)>,
        #<Berkshelf::CookbookSource: mysql (~> 1.2.4)>
      ],
      skarner: [
        #<Berkshelf::CookbookSource: nginx (~> 1.0.0)>
      ]
    }
    


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

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

#has_source?(source) ⇒ Boolean

Parameters:

  • source (#to_s)

    the source to check presence of

Returns:

  • (Boolean)


282
283
284
# File 'lib/berkshelf/berksfile.rb', line 282

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

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

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.

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of 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

  • :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.

Returns:

Raises:



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/berkshelf/berksfile.rb', line 411

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)

  self.cached_cookbooks
end

#lockfileBerkshelf::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.

Returns:

  • (Berkshelf::Lockfile)

    the lockfile corresponding to this berksfile, or a new Lockfile if one does not exist



673
674
675
# File 'lib/berkshelf/berksfile.rb', line 673

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

#metadata(options = {}) ⇒ Object

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.

Parameters:

  • options (Hash) (defaults to: {})

Options Hash (options):

  • :path (String)

    path to the metadata file



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

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

#outdated(options = {}) ⇒ Hash

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"
}

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

  • cookbooks (Hash)

    a customizable set of 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

Returns:

  • (Hash)

    a hash of cached cookbooks and their latest version. An empty hash is returned if there are no newer cookbooks for any of your sources



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/berkshelf/berksfile.rb', line 465

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

#package(name = nil, options = {}) ⇒ String

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

Parameters:

  • name (String) (defaults to: nil)

    the name of the cookbook to package

  • options (Hash) (defaults to: {})

    a list of options

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

Returns:

  • (String)

    the path to the package



598
599
600
601
602
603
604
605
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
# File 'lib/berkshelf/berksfile.rb', line 598

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

#remove_source(source) ⇒ Berkshelf::CookbookSource

Parameters:

  • source (#to_s)

    the source to remove

Returns:



274
275
276
# File 'lib/berkshelf/berksfile.rb', line 274

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

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

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

Parameters:

  • sources (Array<Berkshelf::CookbookSource>) (defaults to: [])

    Array of cookbook sources to resolve

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :skip_dependencies (Boolean)

    Skip resolving of dependencies

Returns:

  • (Array<Berkshelf::CachedCookbooks>)


656
657
658
659
660
661
662
663
664
# File 'lib/berkshelf/berksfile.rb', line 656

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

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

#site(value) ⇒ Hash

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"

Parameters:

Returns:

  • (Hash)


213
214
215
# File 'lib/berkshelf/berksfile.rb', line 213

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

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

The list of cookbook sources specified in this Berksfile

Parameters:

  • sources (Array)

    the list of sources to filter

  • options (Hash) (defaults to: {})

    a customizable set of options

  • cookbooks (Hash)

    a customizable set of options

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

Returns:

Raises:



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

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

#update(options = {}) ⇒ Object

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

  • cookbooks (Hash)

    a customizable set of 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



435
436
437
438
439
440
441
442
443
# File 'lib/berkshelf/berksfile.rb', line 435

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

#upload(options = {}) ⇒ Object

Upload the cookbooks installed by this Berksfile

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

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

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



513
514
515
516
517
518
519
520
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
# File 'lib/berkshelf/berksfile.rb', line 513

def upload(options = {})
  options = options.reverse_merge(force: false, freeze: true, skip_dependencies: false, halt_on_frozen: 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