class R10K::ModuleLoader::Puppetfile

Constants

DEFAULT_MODULEDIR
DEFAULT_PUPPETFILE_NAME

Attributes

default_branch_override[RW]
desired_contents[R]
environment[RW]
environment_name[R]
managed_directories[R]
moduledir[R]
modules[R]
puppetfile_path[R]
purge_exclusions[R]

Public Class Methods

new(basedir:, moduledir: DEFAULT_MODULEDIR, puppetfile: DEFAULT_PUPPETFILE_NAME, overrides: {}, environment: nil, module_exclude_regex: nil) click to toggle source

@param basedir [String] The path that contains the moduledir &

Puppetfile by default. May be an environment, project, or
simple directory.

@param puppetfile [String] The path to the Puppetfile, either an

absolute full path or a relative path with regards to the basedir.

@param moduledir [String] The path to the moduledir, either an

absolute full path or a relative path with regards to the basedir.

@param forge [String] The url (without protocol) to the Forge @param overrides [Hash] Configuration for loaded modules' behavior @param environment [R10K::Environment] When provided, the environment

in which loading takes place

@param module_exclude_regex [Regex] A regex to exclude modules from

installation. Helpful in CI environments.
# File lib/r10k/module_loader/puppetfile.rb, line 35
def initialize(basedir:,
               moduledir: DEFAULT_MODULEDIR,
               puppetfile: DEFAULT_PUPPETFILE_NAME,
               overrides: {},
               environment: nil,
               module_exclude_regex: nil)

  @basedir     = cleanpath(basedir)
  @moduledir   = resolve_path(@basedir, moduledir)
  @puppetfile_path  = resolve_path(@basedir, puppetfile)
  @overrides   = overrides
  @environment = environment
  @module_exclude_regex = module_exclude_regex
  @environment_name = @environment&.name
  @default_branch_override = @overrides.dig(:environments, :default_branch_override)
  @allow_puppetfile_forge = @overrides.dig(:forge, :allow_puppetfile_override)

  @existing_module_metadata = []
  @existing_module_versions_by_name = {}
  @modules = []

  @managed_directories = []
  @desired_contents = []
  @purge_exclusions = []
end

Public Instance Methods

add_module(name, info) click to toggle source

@param [String] name @param [Hash, String, Symbol, nil] info Calling with

anything but a Hash is deprecated. The DSL will now convert
String and Symbol versions to Hashes of the shape
  { version: <String or Symbol> }

String inputs should be valid module versions, the Symbol
`:latest` is allowed, as well as `nil`.

Non-Hash inputs are only ever used by Forge modules. In
future versions this method will require the caller (the
DSL class, not the Puppetfile author) to do this conversion
itself.
# File lib/r10k/module_loader/puppetfile.rb, line 152
def add_module(name, info)
  install_path, metadata_info, spec_deletable = parse_module_definition(name, info)

  mod = R10K::Module.from_metadata(name, install_path, metadata_info, @environment)
  mod.origin = :puppetfile
  mod.spec_deletable = spec_deletable

  # Do not save modules if they would conflict with the attached
  # environment
  if @environment && @environment.module_conflicts?(mod)
    return @modules
  end

  # If this module's metadata has a static version, and that version
  # matches the existing module declaration, and it ostensibly
  # has already has been deployed to disk, use it. Otherwise create a
  # regular module to sync.
  unless mod.version &&
         mod.version == @existing_module_versions_by_name[mod.name] &&
         File.directory?(mod.path)
      mod = mod.to_implementation
  end

  @modules << mod
end
add_module_metadata(name, info) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 111
def add_module_metadata(name, info)
  install_path, metadata_info, _ = parse_module_definition(name, info)

  mod = R10K::Module.from_metadata(name, install_path, metadata_info, @environment)

  @existing_module_metadata << mod
end
load() click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 61
def load
  with_readable_puppetfile(@puppetfile_path) do
    self.load!
  end
end
load!() click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 67
def load!
  logger.info _("Using Puppetfile '%{puppetfile}'") % {puppetfile: @puppetfile_path}
  logger.debug _("Using moduledir '%{moduledir}'") % {moduledir: @moduledir}

  dsl = R10K::ModuleLoader::Puppetfile::DSL.new(self)
  dsl.instance_eval(puppetfile_content(@puppetfile_path), @puppetfile_path)

  validate_no_duplicate_names(@modules)
  @modules = filter_modules(@modules, @module_exclude_regex) if @module_exclude_regex

  managed_content = @modules.group_by(&:dirname)

  @managed_directories = determine_managed_directories(managed_content)
  @desired_contents = determine_desired_contents(managed_content)
  @purge_exclusions = determine_purge_exclusions(@managed_directories)

  {
    modules: @modules,
    managed_directories: @managed_directories,
    desired_contents: @desired_contents,
    purge_exclusions: @purge_exclusions
  }

rescue SyntaxError, LoadError, ArgumentError, NameError => e
  raise R10K::Error.wrap(e, _("Failed to evaluate %{path}") % {path: @puppetfile_path})
end
load_metadata() click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 94
def load_metadata
  with_readable_puppetfile(@puppetfile_path) do
    self.load_metadata!
  end
end
load_metadata!() click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 100
def load_metadata!
  dsl = R10K::ModuleLoader::Puppetfile::DSL.new(self, metadata_only: true)
  dsl.instance_eval(puppetfile_content(@puppetfile_path), @puppetfile_path)

  @existing_module_versions_by_name = @existing_module_metadata.map {|mod| [ mod.name, mod.version ] }.to_h
  empty_load_output.merge(modules: @existing_module_metadata)

rescue SyntaxError, LoadError, ArgumentError, NameError => e
  logger.warn _("Unable to preload Puppetfile because of %{msg}" % { msg: e.message })
end
set_forge(forge) click to toggle source

@param [String] forge

# File lib/r10k/module_loader/puppetfile.rb, line 124
def set_forge(forge)
  if @allow_puppetfile_forge
    logger.debug _("Using Forge from Puppetfile: %{forge}") % { forge: forge }
    PuppetForge.host = forge
  else
    logger.debug _("Ignoring Forge declaration in Puppetfile, using value from settings: %{forge}.") % { forge: PuppetForge.host }
  end
end
set_moduledir(moduledir) click to toggle source

@param [String] moduledir

# File lib/r10k/module_loader/puppetfile.rb, line 134
def set_moduledir(moduledir)
  @moduledir = resolve_path(@basedir, moduledir)
end

Private Instance Methods

cleanpath(path) click to toggle source

.cleanpath is as close to a canonical path as we can do without touching the filesystem. The .realpath methods will choke if some of the intermediate paths are missing, even though in some cases we will create them later as needed.

# File lib/r10k/module_loader/puppetfile.rb, line 282
def cleanpath(path)
  Pathname.new(path).cleanpath.to_s
end
determine_desired_contents(managed_content) click to toggle source

Returns an array of the full paths to all the content being managed. @return [Array<String>]

# File lib/r10k/module_loader/puppetfile.rb, line 264
def determine_desired_contents(managed_content)
  managed_content.flat_map do |install_path, mods|
    mods.collect { |mod| File.join(install_path, mod.name) }
  end
end
determine_managed_directories(managed_content) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 258
def determine_managed_directories(managed_content)
  managed_content.keys.reject { |dir| dir == @basedir }
end
determine_purge_exclusions(managed_dirs) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 270
def determine_purge_exclusions(managed_dirs)
  if environment && environment.respond_to?(:desired_contents)
    managed_dirs + environment.desired_contents
  else
    managed_dirs
  end
end
empty_load_output() click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 180
def empty_load_output
  {
    modules: [],
    managed_directories: [],
    desired_contents: [],
    purge_exclusions: []
  }
end
filter_modules(modules, exclude_regex) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 224
def filter_modules(modules, exclude_regex)
  modules.reject { |mod| mod.name =~ /#{exclude_regex}/ }
end
parse_module_definition(name, info) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 199
def parse_module_definition(name, info)
  # The only valid (deprecated) way a module can be defined with a
  # non-hash info is if it is a Forge module.
  if !info.is_a?(Hash)
    info = { type: 'forge', version: info }
  end

  info[:overrides] = @overrides

  if @default_branch_override
    info[:default_branch_override] = @default_branch_override
  end

  spec_deletable = false
  if install_path = info.delete(:install_path)
    install_path = resolve_path(@basedir, install_path)
    validate_install_path(install_path, name)
  else
    install_path = @moduledir
    spec_deletable = true
  end

  return [ install_path, info, spec_deletable ]
end
puppetfile_content(path) click to toggle source

For testing purposes only

# File lib/r10k/module_loader/puppetfile.rb, line 287
def puppetfile_content(path)
  File.read(path)
end
resolve_path(base, path) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 242
def resolve_path(base, path)
  if Pathname.new(path).absolute?
    cleanpath(path)
  else
    cleanpath(File.join(base, path))
  end
end
validate_install_path(path, modname) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 250
def validate_install_path(path, modname)
  unless /^#{Regexp.escape(@basedir)}.*/ =~ path
    raise R10K::Error.new("Puppetfile cannot manage content '#{modname}' outside of containing environment: #{path} is not within #{@basedir}")
  end

  true
end
validate_no_duplicate_names(modules) click to toggle source

@param [Array<R10K::Module>] modules

# File lib/r10k/module_loader/puppetfile.rb, line 229
def validate_no_duplicate_names(modules)
  dupes = modules
          .group_by { |mod| mod.name }
          .select { |_, mods| mods.size > 1 }
          .map(&:first)
  unless dupes.empty?
    msg = _('Puppetfiles cannot contain duplicate module names.')
    msg += ' '
    msg += _("Remove the duplicates of the following modules: %{dupes}" % { dupes: dupes.join(' ') })
    raise R10K::Error.new(msg)
  end
end
with_readable_puppetfile(puppetfile_path, &block) click to toggle source
# File lib/r10k/module_loader/puppetfile.rb, line 189
def with_readable_puppetfile(puppetfile_path, &block)
  if File.readable?(puppetfile_path)
    block.call
  else
    logger.debug _("Puppetfile %{path} missing or unreadable") % {path: puppetfile_path.inspect}

    empty_load_output
  end
end