Class: Berkshelf::Berksfile

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

Overview

Author:

Constant Summary collapse

@@active_group =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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



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

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:



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

def cached_cookbooks
  @cached_cookbooks
end

#downloaderBerkshelf::Downloader (readonly)



69
70
71
# File 'lib/berkshelf/berksfile.rb', line 69

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



66
67
68
# File 'lib/berkshelf/berksfile.rb', line 66

def filepath
  @filepath
end

Class Method Details

.from_file(file) ⇒ Berksfile

Parameters:

  • file (String)

    a path on disk to a Berksfile to instantiate from

Returns:



12
13
14
15
16
17
18
# File 'lib/berkshelf/berksfile.rb', line 12

def from_file(file)
  content = File.read(file)
  object = new(file)
  object.load(content)
rescue Errno::ENOENT => e
  raise BerksfileNotFound, "No Berksfile or Berksfile.lock found at: #{file}"
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



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
58
59
# File 'lib/berkshelf/berksfile.rb', line 30

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:



351
352
353
# File 'lib/berkshelf/berksfile.rb', line 351

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.



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

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

  options[:constraint] = constraint

  @sources[name] = CookbookSource.new(self, name, options)
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)


239
240
241
# File 'lib/berkshelf/berksfile.rb', line 239

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 source 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:



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

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

  options[:group] = Array(options[:group])

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

  add_source(name, constraint, options)
end

#group(*args) ⇒ Object



167
168
169
170
171
# File 'lib/berkshelf/berksfile.rb', line 167

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 @name="nginx">,
    #<Berkshelf::CookbookSource @name="mysql">,
  ],
  skarner: [
    #<Berkshelf::CookbookSource @name="nginx">
  ]
}.

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 @name="nginx">,
        #<Berkshelf::CookbookSource @name="mysql">,
      ],
      skarner: [
        #<Berkshelf::CookbookSource @name="nginx">
      ]
    }
    


336
337
338
339
340
341
342
343
344
345
# File 'lib/berkshelf/berksfile.rb', line 336

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>

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:



367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/berkshelf/berksfile.rb', line 367

def install(options = {})
  resolver = Resolver.new(self, sources: sources(options))

  @cached_cookbooks = resolver.resolve
  write_lockfile(resolver.sources) unless lockfile_present?

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

  self.cached_cookbooks
end

#load(content) ⇒ Berksfile

Reload this instance of Berksfile with the given content. The content is a string that may contain terms from the included DSL.

Parameters:

Returns:

Raises:



560
561
562
563
564
565
566
567
# File 'lib/berkshelf/berksfile.rb', line 560

def load(content)
  begin
    instance_eval(content)
  rescue => e
    raise BerksfileReadError.new(e), "An error occurred while reading the Berksfile: #{e.to_s}"
  end
  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



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/berkshelf/berksfile.rb', line 181

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

   = Berkshelf.(path)

  unless 
    raise CookbookNotFound, "No 'metadata.rb' found at #{path}"
  end

   = Ridley::Chef::Cookbook::Metadata.from_file(.to_s)

  name = if .name.empty? || .name.nil?
    File.basename(File.dirname())
  else
    .name
  end

  constraint = "= #{.version}"

  add_source(name, constraint, path: File.dirname())
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



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/berkshelf/berksfile.rb', line 430

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

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

    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

#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(options = {}) ⇒ Array<Berkshelf::CachedCookbooks]

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

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

  • :skip_dependencies (Boolean)

    Skip resolving of dependencies

Returns:

  • (Array<Berkshelf::CachedCookbooks])

    Array<Berkshelf::CachedCookbooks]



548
549
550
# File 'lib/berkshelf/berksfile.rb', line 548

def resolve(options = {})
  resolver(options).resolve
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)


217
218
219
# File 'lib/berkshelf/berksfile.rb', line 217

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

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

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



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/berkshelf/berksfile.rb', line 298

def sources(options = {})
  l_sources = @sources.collect { |name, source| source }.flatten

  cookbooks = Array(options.fetch(:cookbooks, nil))
  except    = Array(options.fetch(:except, nil)).collect(&:to_sym)
  only      = Array(options.fetch(:only, nil)).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| options[: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



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/berkshelf/berksfile.rb', line 388

def update(options = {})
  resolver = Resolver.new(self, sources: sources(options))

  cookbooks         = resolver.resolve
  sources           = resolver.sources
  missing_cookbooks = (options[:cookbooks] - cookbooks.map(&:cookbook_name))

  unless missing_cookbooks.empty?
    msg = "Could not find cookbooks #{missing_cookbooks.collect{|cookbook| "'#{cookbook}'"}.join(', ')}"
    msg << " in any of the sources. #{missing_cookbooks.size == 1 ? 'Is it' : 'Are they' } in your Berksfile?"
    raise Berkshelf::CookbookNotFound, msg
  end

  update_lockfile(sources)

  if options[:path]
    self.class.vendor(cookbooks, options[:path])
  end

  cookbooks
end

#upload(options = {}) ⇒ Object

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



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
# File 'lib/berkshelf/berksfile.rb', line 475

def upload(options = {})
  options = options.reverse_merge(
    force: false,
    freeze: true,
    ssl_verify: Berkshelf::Config.instance.ssl.verify,
    skip_dependencies: false,
    halt_on_frozen: false
  )

  ridley_options               = options.slice(:ssl)
  ridley_options[:server_url]  = options[:server_url] || Berkshelf::Config.instance.chef.chef_server_url
  ridley_options[:client_name] = Berkshelf::Config.instance.chef.node_name
  ridley_options[:client_key]  = Berkshelf::Config.instance.chef.client_key
  ridley_options[:ssl]         = { verify: options[:ssl_verify] }

  unless ridley_options[:server_url].present?
    raise UploadFailure, "Missing required attribute in your Berkshelf configuration: chef.server_url"
  end

  unless ridley_options[:client_name].present?
    raise UploadFailure, "Missing required attribute in your Berkshelf configuration: chef.node_name"
  end

  unless ridley_options[:client_key].present?
    raise UploadFailure, "Missing required attribute in your Berkshelf configuration: chef.client_key"
  end

  solution    = resolve(options)
  upload_opts = options.slice(:force, :freeze)
  conn        = Ridley.new(ridley_options)

  solution.each do |cb|
    Berkshelf.formatter.upload(cb.cookbook_name, cb.version, conn.server_url)

    begin
      conn.cookbook.upload(cb.path, upload_opts.merge(name: cb.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) - solution.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 UploadFailure, ex
ensure
  conn.terminate if conn && conn.alive?
end