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
175
176
177
178
179
180
181
# 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
     = 

    while !_levels.empty?
      if [current_level]
         = [current_level]
        current_level = _levels.shift
      else
        return nil
      end
    end

    return [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'

    _module_author, _module_name = module_name.split('/')

    unless _module_name
      _module_name = _module_author.dup
      _module_author = nil
    end

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

    if module_dependency
       = call_function('load_module_metadata', _module_name)

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

      if 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 '#{_module_name}')
        end

        module_version = coerce(['version'])

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

        unless version_requirement.include?(module_version)
          return %('#{_module_name}-#{module_version}' does not satisfy '#{version_requirement}')
        end
      end
    end
  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
      )
    )

    if module_dependencies
      if target_module
        tgt = target_module.gsub('-','/')

        if tgt.include?('/')
          target_dependency = module_dependencies.find {|d| d['name'].gsub('-','/') == tgt}
        else
          target_dependency = 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'].gsub('-','/'), dependency)
          results << result if result
        end

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