Puppet Function: simplib::assert_optional_dependency

Defined in:
lib/puppet/functions/simplib/assert_optional_dependency.rb
Function type:
Ruby 4.x API

Overview

simplib::assert_optional_dependency(String[1] $source_module, Optional[String[1]] $target_module, Optional[String[1]] $dependency_tree)None

Fails a compile if the system does not contain a correct version of the required module in the current environment.

Provides a message about exactly which version of the module is required.

Examples:

Check for the ‘puppet/foo’ optional dependency


### metadata.json ###
"simp": {
  "optional_dependencies" [
    {
      "name": "puppet/foo",
      "version_requirement": ">= 1.2.3 < 4.5.6"
    }
  ]
}

### myclass.pp ###
# Check all dependencies
simplib::assert_optional_dependency($module_name)

# Check the module 'foo'
simplib::assert_optional_dependency($module_name, 'foo')

# Check the module 'foo' by author 'puppet'
simplib::assert_optional_dependency($module_name, 'puppet/foo')

# Check an alternate dependency target
simplib::assert_optional_dependency($module_name, 'puppet/foo', 'my:deps')

Parameters:

  • source_module (String[1])

    The name of the module containing the dependency information (usually the module that this function is being called from)

  • target_module (Optional[String[1]])

    The target module to check. If not specified, all optional dependencies in the tree will be checked.

    • This may optionally be the full module name with the author in ‘author/module` form which allows for different logic paths that can use multiple vendor modules

  • dependency_tree (Optional[String[1]])

    The root of the dependency tree in the module’s ‘metadata.json` that contains the optional dependencies.

    • Nested levels should be separated by colons (‘:`)

Returns:

  • (None)


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
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
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/puppet/functions/simplib/assert_optional_dependency.rb', line 6

Puppet::Functions.create_function(:'simplib::assert_optional_dependency') do
  # @param source_module
  #   The name of the module containing the dependency information (usually the
  #   module that this function is being called from)
  #
  # @param target_module
  #   The target module to check. If not specified, all optional dependencies
  #   in the tree will be checked.
  #
  #   * This may optionally be the full module name with the author in
  #     `author/module` form which allows for different logic paths that can use
  #     multiple vendor modules
  #
  # @param dependency_tree
  #   The root of the dependency tree in the module's `metadata.json` that
  #   contains the optional dependencies.
  #
  #   * Nested levels should be separated by colons (`:`)
  #
  # @example Check for the 'puppet/foo' optional dependency
  #
  #   ### metadata.json ###
  #   "simp": {
  #     "optional_dependencies" [
  #       {
  #         "name": "puppet/foo",
  #         "version_requirement": ">= 1.2.3 < 4.5.6"
  #       }
  #     ]
  #   }
  #
  #   ### myclass.pp ###
  #   # Check all dependencies
  #   simplib::assert_optional_dependency($module_name)
  #
  #   # Check the module 'foo'
  #   simplib::assert_optional_dependency($module_name, 'foo')
  #
  #   # Check the module 'foo' by author 'puppet'
  #   simplib::assert_optional_dependency($module_name, 'puppet/foo')
  #
  #   # Check an alternate dependency target
  #   simplib::assert_optional_dependency($module_name, 'puppet/foo', 'my:deps')
  #
  # @return [None]
  #
  dispatch :assert_optional_dependency do
    required_param 'String[1]', :source_module
    optional_param 'String[1]', :target_module
    optional_param 'String[1]', :dependency_tree
  end

  def get_module_dependencies(dependency_tree_levels, )
    levels = Array(dependency_tree_levels.dup)
    current_level = levels.shift
     = 

    until levels.empty?
      return nil unless [current_level]
       = [current_level]
      current_level = levels.shift

    end

    [current_level]
  end

  # Concept lifted from 'node-semver'
  def coerce(version)
    version
      .split('-')
      .first
      .split('.')[0, 3]
      .join('.')
  end

  def check_dependency(module_name, module_dependency)
    require 'semantic_puppet'

    author, name = module_name.split('/')

    unless name
      name = author.dup
      author = nil
    end

    unless call_function('simplib::module_exist', name)
      return "optional dependency '#{name}' not found"
    end

    return unless module_dependency
     = call_function('load_module_metadata', name)

    if author
      cmp_author = ['name'].strip.tr('-', '/').split('/').first
      unless author.strip == cmp_author
        return %('#{module_name}' does not match '#{['name']}')
      end
    end

    return unless module_dependency['version_requirement']
    begin
      version_requirement = SemanticPuppet::VersionRange.parse(module_dependency['version_requirement'])
    rescue ArgumentError
      return %(invalid version range '#{module_dependency['version_requirement']}' for '#{name}')
    end

    module_version = coerce(['version'])

    begin
      module_version = SemanticPuppet::Version.parse(module_version)
    rescue ArgumentError
      return %(invalid version '#{module_version}' found for '#{name}')
    end

    return if version_requirement.include?(module_version)
    %('#{name}-#{module_version}' does not satisfy '#{version_requirement}')
  end

  def raise_error(msg, env)
    raise(Puppet::ParseError, %(assert_optional_dependency(): #{msg} in environment '#{env}'))
  end

  def assert_optional_dependency(
    source_module,
    target_module = nil,
    dependency_tree = 'simp:optional_dependencies'
  )
    current_environment = closure_scope.compiler.environment.to_s

    module_dependencies = get_module_dependencies(
      dependency_tree.split(':'),
      call_function(
        'load_module_metadata',
        source_module,
      ),
    )

    return unless module_dependencies
    if target_module
      tgt = target_module.tr('-', '/')

      target_dependency = if tgt.include?('/')
                            module_dependencies.find { |d| d['name'].tr('-', '/') == tgt }
                          else
                            module_dependencies.find { |d| d['name'] =~ %r{(/|-)#{tgt}$} }
                          end

      if target_dependency
        result = check_dependency(tgt, target_dependency)

        raise_error(result, current_environment) if result
      else
        raise_error(%(module '#{target_module}' not found in metadata.json for '#{source_module}'), current_environment)
      end
    else
      results = []

      module_dependencies.each do |dependency|
        result = check_dependency(dependency['name'].tr('-', '/'), dependency)
        results << result if result
      end

      unless results.empty?
        raise_error(%(\n* #{results.join("\n* ")}\n), current_environment)
      end
    end
  end
end