Class: Bolt::Puppetfile

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/puppetfile.rb,
lib/bolt/puppetfile/module.rb,
lib/bolt/puppetfile/installer.rb

Defined Under Namespace

Classes: Installer, Module

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(modules = []) ⇒ Puppetfile

Returns a new instance of Puppetfile.



14
15
16
17
# File 'lib/bolt/puppetfile.rb', line 14

def initialize(modules = [])
  @modules = Set.new
  add_modules(modules)
end

Instance Attribute Details

#modulesObject (readonly)

Returns the value of attribute modules.



12
13
14
# File 'lib/bolt/puppetfile.rb', line 12

def modules
  @modules
end

Class Method Details

.parse(path, skip_unsupported_modules: false) ⇒ Object

Loads a Puppetfile and parses its module specifications, returning a Bolt::Puppetfile object with the modules set.



22
23
24
25
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
56
57
# File 'lib/bolt/puppetfile.rb', line 22

def self.parse(path, skip_unsupported_modules: false)
  require 'puppetfile-resolver'
  require 'puppetfile-resolver/puppetfile/parser/r10k_eval'

  begin
    parsed = ::PuppetfileResolver::Puppetfile::Parser::R10KEval.parse(File.read(path))
  rescue StandardError => e
    raise Bolt::Error.new(
      "Unable to parse Puppetfile #{path}: #{e.message}",
      'bolt/puppetfile-parsing'
    )
  end

  unless parsed.valid?
    # valid? Just checks if validation_errors is empty, so if we get here we know it's not.
    raise Bolt::ValidationError, <<~MSG
    Unable to parse Puppetfile #{path}:
    #{parsed.validation_errors.join("\n\n")}.
    This may not be a Puppetfile managed by Bolt.
    MSG
  end

  modules = parsed.modules.each_with_object([]) do |mod, acc|
    unless mod.instance_of? PuppetfileResolver::Puppetfile::ForgeModule
      next if skip_unsupported_modules

      raise Bolt::ValidationError,
            "Module '#{mod.title}' is not a Puppet Forge module. Unable to "\
            "parse Puppetfile #{path}."
    end

    acc << Bolt::Puppetfile::Module.new(mod.owner, mod.name, mod.version)
  end

  new(modules)
end

Instance Method Details

#add_modules(modules) ⇒ Object

Adds to the set of modules.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/bolt/puppetfile.rb', line 134

def add_modules(modules)
  Array(modules).each do |mod|
    case mod
    when Bolt::Puppetfile::Module
      @modules << mod
    when Hash
      @modules << Bolt::Puppetfile::Module.from_hash(mod)
    else
      raise Bolt::ValidationError, "Module must be a Bolt::Puppetfile::Module or Hash."
    end
  end

  @modules
end

#resolveObject

Resolves module dependencies using the puppetfile-resolver library. The resolver will return a document model including all module dependencies and the latest version that can be installed for each. The document model is parsed and turned into a Set of Bolt::Puppetfile::Module objects.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/bolt/puppetfile.rb', line 87

def resolve
  require 'puppetfile-resolver'

  # Build the document model from the modules.
  model = PuppetfileResolver::Puppetfile::Document.new('')

  @modules.each do |mod|
    model.add_module(
      PuppetfileResolver::Puppetfile::ForgeModule.new(mod.title).tap do |tap|
        tap.version = mod.version || :latest
      end
    )
  end

  # Make sure the Puppetfile model is valid.
  unless model.valid?
    raise Bolt::ValidationError,
          "Unable to resolve dependencies for modules: #{@modules.map(&:title).join(', ')}"
  end

  # Create the resolver using the Puppetfile model. nil disables Puppet
  # version restrictions.
  resolver = PuppetfileResolver::Resolver.new(model, nil)

  # Configure and resolve the dependency graph, catching any errors
  # raised by puppetfile-resolver and re-raising them as Bolt errors.
  begin
    result = resolver.resolve(
      cache:                 nil,
      ui:                    nil,
      module_paths:          [],
      allow_missing_modules: false
    )
  rescue StandardError => e
    raise Bolt::Error.new(e.message, 'bolt/puppetfile-resolver-error')
  end

  # Turn specifications into module objects. This will skip over anything that is not
  # a module specification (i.e. a Puppet version specification).
  @modules = result.specifications.each_with_object(Set.new) do |(_name, spec), acc|
    next unless spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
    acc << Bolt::Puppetfile::Module.new(spec.owner, spec.name, spec.version.to_s)
  end
end

#write(path, moduledir = nil) ⇒ Object

Writes a Puppetfile that includes specifications for each of the modules.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/bolt/puppetfile.rb', line 62

def write(path, moduledir = nil)
  File.open(path, 'w') do |file|
    if moduledir
      file.puts "# This Puppetfile is managed by Bolt. Do not edit."
      file.puts "# For more information, see https://pup.pt/bolt-modules"
      file.puts
      file.puts "# The following directive installs modules to the managed moduledir."
      file.puts "moduledir '#{moduledir.basename}'"
      file.puts
    end

    modules.each { |mod| file.puts mod.to_spec }
  end
rescue SystemCallError => e
  raise Bolt::FileError.new(
    "#{e.message}: unable to write Puppetfile.",
    path
  )
end