Class: Berktacular::Berksfile

Inherits:
Object
  • Object
show all
Defined in:
lib/berktacular/berksfile.rb

Overview

This class represents a Berksfile

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env_path, opts = {}) ⇒ Berksfile

Creates a new Berksfile from a chef environment file.

Parameters:

  • environment (Hash)

    a parsed JSON chef environment config.

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

    a customizable set of options

Options Hash (opts):

  • :github_token (String) — default: nil

    the github token to use.

  • :upgrade (True, False) — default: False

    whether or not to check for upgraded cookbooks.

  • :verbose (True, False) — default: False

    be more verbose.

  • :source_list (Array<String>)

    additional Berkshelf API sources to include in the generated Berksfile.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/berktacular/berksfile.rb', line 41

def initialize( env_path, opts = {})
  @opts = {
    upgrade:       opts.has_key?(:upgrade)      ? opts[:upgrade]      : false,
    github_token:  opts.has_key?(:github_token) ? opts[:github_token] : nil,
    verbose:       opts.has_key?(:verbose)      ? opts[:verbose]      : false,
    source_list:   opts.has_key?(:source_list)  ? opts[:source_list]  : [],
    multi_cookbook_dir:  opts.has_key?(:multi_cookbook_dir) ? opts[:multi_cookbook_dir] : nil,
    versions_only:  opts.has_key?(:versions_only) ? opts[:versions_only] : false,
    max_depth:      opts.has_key?(:max_depth) ? opts[:max_depth] : 10,
    parent_env_dir: opts.has_key?(:parent_env_dir) ? opts[:parent_env_dir] : nil
  }
  @counter  = 0
  @env_hash =  expand_env_file(env_path)

  @name               = @env_hash['name']               || nil
  @description        = @env_hash['description']        || nil
  @cookbook_versions  = @env_hash['cookbook_versions']  || {}
  @cookbook_locations = @env_hash['cookbook_locations'] || {}
  @installed = {}
  # only connect once, pass the client to each cookbook.  and only if needed
  connect_to_git if @opts[:upgrade]
  @opts[:source_list] = (@opts[:source_list] + ["https://api.berkshelf.com"]).uniq
end

Instance Attribute Details

#descriptionString (readonly)

Returns a description of the enviroment.

Returns:

  • (String)

    a description of the enviroment.



31
# File 'lib/berktacular/berksfile.rb', line 31

attr_reader :name, :description, :installed, :missing_deps

#installedHash (readonly)

Returns a hash of installed cookbook directories.

Returns:

  • (Hash)

    a hash of installed cookbook directories.



31
# File 'lib/berktacular/berksfile.rb', line 31

attr_reader :name, :description, :installed, :missing_deps

#missing_depsObject (readonly)

Returns the value of attribute missing_deps.



31
# File 'lib/berktacular/berksfile.rb', line 31

attr_reader :name, :description, :installed, :missing_deps

#nameString (readonly)

Returns the name of the environment.

Returns:

  • (String)

    the name of the environment.



31
32
33
# File 'lib/berktacular/berksfile.rb', line 31

def name
  @name
end

Instance Method Details

#check_updatesObject

print out the cookbooks that have newer version available on github.



221
222
223
224
225
226
227
228
229
230
# File 'lib/berktacular/berksfile.rb', line 221

def check_updates
  connect_to_git
  cookbooks.each do |b|
    candidates = b.check_updates
    next unless candidates.any?
    puts "Cookbook: #{b.name} (auto upgrade: #{b.auto_upgrade ? 'enabled' : 'disabled'})",
         "\tCurrent: #{b.version_number}",
         "\tUpdates: #{candidates.join(", ")}"
  end
end

#clean(workdir = nil) ⇒ Object

param workdir [String,nil] the workdir to remove. If nil, remove all installed working directories.



181
182
183
184
185
186
187
188
189
190
# File 'lib/berktacular/berksfile.rb', line 181

def clean(workdir = nil)
  if workdir
    Fileutils.rm_r(workdir)
    @installed.delete(workdir)
  else
    # clean them all
    @installed.keys.each { |d| FileUtils.rm_r(d) }
    @installed = {}
  end
end

#cookbooksArray

Returns a list of Cookbook objects for this environment.

Returns:

  • (Array)

    a list of Cookbook objects for this environment.



214
215
216
217
218
# File 'lib/berktacular/berksfile.rb', line 214

def cookbooks
  @cookbooks ||= @cookbook_versions.sort.map do |book, version|
    Cookbook.new(book, version, @cookbook_locations[book], @opts)
  end
end

#env_fileHash

Returns representation of the env_file.

Returns:

  • (Hash)

    representation of the env_file.



66
67
68
69
70
71
72
73
# File 'lib/berktacular/berksfile.rb', line 66

def env_file
  if @opts[:upgrade]
    cookbooks.each do |book|
      @env_hash['cookbook_versions'][book.name] = book.version_specifier
    end
  end
  @env_hash
end

#env_file_jsonString

Returns representation of the env_file in pretty json.

Returns:

  • (String)

    representation of the env_file in pretty json.



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

def env_file_json
  if @opts[:upgrade]
    cookbooks.each do |book|
      @env_hash['cookbook_versions'][book.name] = book.version_specifier
    end
  end
  JSON.pretty_generate(@env_hash)
end

#install(workdir = nil) ⇒ String

Returns the directory path where the cookbooks were installed.

Parameters:

  • workdir (String) (defaults to: nil)

    the directory in which to install. If nil, Berktacular.best_temp_dir is used.

Returns:

  • (String)

    the directory path where the cookbooks were installed.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/berktacular/berksfile.rb', line 87

def install(workdir = nil)
  if workdir
    FileUtils.mkdir_p(workdir)
  else
    workdir = Berktacular.best_temp_dir
  end
  unless @installed[workdir]
    # remove the Berksfile.lock if it exists (it shouldn't).
    berksfile = File.join(workdir, "Berksfile")
    lck       = berksfile + ".lock"
    cookbooks = File.join(workdir, "cookbooks")
    FileUtils.rm(lck) if File.exists? lck
    File.write(berksfile, self)
    Berktacular.run_command("berks vendor --berksfile #{berksfile} #{cookbooks}")
    @installed[workdir] = {berksfile: berksfile, lck: lck, cookbooks: cookbooks}
  end
  workdir
end

Parameters:

  • where (IO)

    to write the data.



193
194
195
# File 'lib/berktacular/berksfile.rb', line 193

def print_berksfile( io = STDOUT )
  io.puts to_s
end

#to_sString

Returns the berksfile as a String object.

Returns:

  • (String)

    the berksfile as a String object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/berktacular/berksfile.rb', line 198

def to_s
  str = ''
  str << "# Name: '#{@name}'\n" if @name
  str << "# Description: #{@description}\n\n" if @description
  str << "# This file is auto-generated, changes will be overwritten\n"
  str << "# Modify the .json environment file and regenerate this Berksfile to make changes.\n\n"

  @opts[:source_list].each do |source_url|
    str << "source '#{source_url}'\n"
  end
  str << "\n"
  cookbooks.each { |l| str << l.to_s << "\n" }
  str
end

#upload(berks_conf, knife_conf, workdir = nil) ⇒ True

Returns or raise on error.

Parameters:

  • berks_conf (String)

    path to the berkshelf config file to use.

  • knife_conf (String)

    path to the knife config file to use.

  • workdir (String) (defaults to: nil)

    Path to use as the working directory. @default Berktacular.best_temp_dir

Returns:

  • (True)

    or raise on error.



170
171
172
173
174
175
176
177
178
# File 'lib/berktacular/berksfile.rb', line 170

def upload(berks_conf, knife_conf, workdir=nil)
  raise "No berks config, required for upload" unless berks_conf && File.exists?(berks_conf)
  raise "No knife config, required for upload" unless knife_conf && File.exists?(knife_conf)
  workdir       = install(workdir)
  new_env_file  = File.join(workdir, @name + ".json")
  File.write(new_env_file, env_file_json)
  Berktacular.run_command("berks upload --berksfile #{@installed[workdir][:berksfile]} -c #{berks_conf}")
  Berktacular.run_command("knife environment from file #{new_env_file} -c #{knife_conf}")
end

#verify(workdir = nil) ⇒ True, False

Returns the status of the verify.

Returns:

  • (True, False)

    the status of the verify.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/berktacular/berksfile.rb', line 108

def verify(workdir = nil)
  require 'ridley'
  @missing_deps = {}
  workdir       = install(workdir)
  versions      = {}
  dependencies  = {}
  Dir["#{@installed[workdir][:cookbooks]}/*"].each do |cookbook_dir|
    next unless File.directory?(cookbook_dir)
     = ['rb', 'json'].map {|ext| File.join(cookbook_dir, "metadata.#{ext}") }
     = .find {|f| File.exists?(f) }
    raise "Metadata file not found: #{}" if .nil?
     =
       =~ /\.json$/ ? (IO.read()) :
        Ridley::Chef::Cookbook::Metadata.from_file()
    cookbook_name = .name
    name_from_path = File.basename(cookbook_dir)
    unless cookbook_name == name_from_path
      if cookbook_name.empty?
        puts "Cookbook #{name_from_path} has no name specified in metadata.rb"
        cookbook_name = name_from_path
      else
        warn "Cookbook name from metadata.rb does not match the directory name!",
             "metadata.rb: '#{cookbook_name}'",
             "cookbook directory name: '#{name_from_path}'"
      end
    end
    versions[cookbook_name] = .version
    dependencies[cookbook_name] = .dependencies
  end
  errors = false
  dependencies.each do |name, deps|
    deps.each do |dep_name, constraint|
      actual_version = versions[dep_name]
      if !actual_version
        @missing_deps[name] = "#{name}-#{versions[name]} depends on #{dep_name} which was not installed!"
        warn @missing_deps[name]
        errors = true
      elsif constraint != []  # some cookbooks have '[]' as a dependency in their json metadata
        constraint_obj = begin
          Semverse::Constraint.new(constraint)
        rescue Semverse::InvalidConstraintFormat => ex
          warn "Could not parse version constraint '#{constraint}' " +
               "for dependency '#{dep_name}' of cookbook '#{name}'"
          raise ex
        end

        unless constraint_obj.satisfies?(actual_version)
          @missing_deps[name] = "#{name}-#{versions[name]} depends on #{dep_name} #{constraint} but #{dep_name} is #{actual_version}!"
          warn @missing_deps[name]
          errors = true
        end
      end
    end
  end
  !errors
end