Class: Berkshelf::Berksfile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
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

Constructor Details

#initialize(path) ⇒ Berksfile

Returns a new instance of Berksfile.



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

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:



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

def cached_cookbooks
  @cached_cookbooks
end

#downloaderBerkshelf::Downloader (readonly)



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

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



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

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:



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

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



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

def vendor(cookbooks, path)
  chefignore_file = [
    File.join(Dir.pwd, 'chefignore'),
    File.join(Dir.pwd, 'cookbooks', 'chefignore')
  ].find { |f| File.exists?(f) }

  chefignore = chefignore_file && ::Chef::Cookbook::Chefignore.new(chefignore_file)
  path       = File.expand_path(path)
  FileUtils.mkdir_p(path)

  scratch = Berkshelf.mktmpdir
  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:



348
349
350
# File 'lib/berkshelf/berksfile.rb', line 348

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.



253
254
255
256
257
258
259
260
261
262
263
264
265
# 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

  options[:constraint] = constraint

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


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

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:



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

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



165
166
167
168
169
# File 'lib/berkshelf/berksfile.rb', line 165

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


333
334
335
336
337
338
339
340
341
342
# File 'lib/berkshelf/berksfile.rb', line 333

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)


279
280
281
# File 'lib/berkshelf/berksfile.rb', line 279

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:



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

def install(options = {})
  resolver = Resolver.new(
    self.downloader,
    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:



525
526
527
528
529
530
531
532
# File 'lib/berkshelf/berksfile.rb', line 525

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



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

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

   = Berkshelf.(path)

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

   = Chef::Cookbook::Metadata.new
  .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



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

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 = SiteLocation.new(cookbook.name, cookbook.version_constraint).latest_version[0]

      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:



271
272
273
# File 'lib/berkshelf/berksfile.rb', line 271

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

Returns:

  • (Array<Berkshelf::CachedCookbooks])

    Array<Berkshelf::CachedCookbooks]



510
511
512
513
514
515
# File 'lib/berkshelf/berksfile.rb', line 510

def resolve(options = {})
  Resolver.new(
    self.downloader,
    sources: sources(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)


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

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:



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

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

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

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

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

  • cookbooks (Hash)

    a customizable set of options

Options Hash (options):

  • :server_url (String)

    URL to the Chef API

  • :client_name (String)

    name of the client used to authenticate with the Chef API

  • :client_key (String)

    filepath to the client’s private key used to authenticate with the Chef API

  • :organization (String)

    the Organization to connect to. This is only used if you are connecting to private Chef or hosted Chef

  • :force (Boolean)

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

  • :freeze (Boolean)

    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

  • :params (Hash)

    URI query unencoded key/value pairs

  • :headers (Hash)

    unencoded HTTP header key/value pairs

  • :request (Hash)

    request options

  • :ssl (Hash)

    SSL options

  • :proxy (URI, String, Hash)

    URI, String, or Hash of HTTP proxy options

Raises:

  • (UploadFailure)

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



483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/berkshelf/berksfile.rb', line 483

def upload(options = {})
  uploader = Uploader.new(options)
  solution = resolve(options)

  solution.each do |cb|
    Berkshelf.formatter.upload cb.cookbook_name, cb.version, options[:server_url]
    uploader.upload(cb, options)
  end

rescue Ridley::Errors::ClientKeyFileNotFound => e
  msg = "Could not upload cookbooks: Missing Chef client key: '#{Berkshelf::Config.instance.chef.client_key}'."
  msg << " Generate or update your Berkshelf configuration that contains a valid path to a Chef client key."
  raise UploadFailure, msg
end