puppetlabs/puppet · error · Puppet::Util::Package::Version::Range::ValidationFailure

Unable to parse '#{simple}' as a version range identifier

Error message

Unable to parse '#{simple}' as a version range identifier

What it means

Inside Range.parse (range.rb:37): each whitespace-separated token must match FULL_REGEX = /\A((?:[<>=])*)(.+)\Z/. The capture group (.+) requires at least one version character after the operator run, so a bare operator token (no version attached) fails to match and raises ValidationFailure 'Unable to parse ... as a version range identifier'. Because tokens are split on /\s+/, writing a space between operator and version turns the operator into its own token and hits exactly this raise.

Source

Thrown at lib/puppet/util/package/version/range.rb:37

    #   * ex. `"1.0.0"`, `"1.2.3-pre"`
    # * Inequalities
    #   * ex. `">1.0.0"`, `"<3.2.0"`, `">=4.0.0"`
    # * Range Intersections (min is always first)
    #   * ex. `">1.0.0 <=2.3.0"`
    #
    RANGE_SPLIT = /\s+/
    FULL_REGEX = /\A((?:[<>=])*)(.+)\Z/

    # @param range_string [String] the version range string to parse
    # @param version_class [Version] a version class implementing comparison operators and parse method
    # @return [Range] a new {Range} instance
    # @api public
    def self.parse(range_string, version_class)
      raise ValidationFailure, "Unable to parse '#{range_string}' as a string" unless range_string.is_a?(String)

      simples = range_string.split(RANGE_SPLIT).map do |simple|
        match, operator, version = *simple.match(FULL_REGEX)
        raise ValidationFailure, "Unable to parse '#{simple}' as a version range identifier" unless match

        case operator
        when '>'
          Gt.new(version_class.parse(version))
        when '>='
          GtEq.new(version_class.parse(version))
        when '<'
          Lt.new(version_class.parse(version))
        when '<='
          LtEq.new(version_class.parse(version))
        when ''
          Eq.new(version_class.parse(version))
        else
          raise ValidationFailure, "Operator '#{operator}' is not implemented"
        end
      end
      simples.size == 1 ? simples[0] : MinMax.new(simples[0], simples[1])
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Remove the space between operator and version: '>1.0', '>=2.0', '<=3.2.0'.
  2. Normalize before parsing: squeeze each operator token onto its version (e.g. str.gsub(/([<>=]+)\s+/, '\1')).
  3. Pre-validate each token against /\A(?:[<>=]*)(.+)\Z/ plus a non-empty operator when an operator was intended.
  4. Rescue Range::ValidationFailure and report the offending token to the user.

Example fix

// before
rng = Puppet::Util::Package::Version::Range.parse(constraint, version_class) # constraint = '>= 1.2.0'

// after
normalized = constraint.strip.gsub(/([<>=]+)\s+/, '\1')
rng = Puppet::Util::Package::Version::Range.parse(normalized, version_class)
Defensive patterns

Strategy: validation

Validate before calling

def range_tokens_ok?(str)
  str.is_a?(String) && str.split(/\s+/).all? { |t| t.match?(/\A(?:[<>=]*)(.+\z)/m) && !t.match?(/\A[<>=]+\z/) }
end

Try / catch

begin
  Range.parse(normalized, version_class)
rescue Puppet::Util::Package::Version::Range::ValidationFailure => e
  raise ArgumentError, "bad range #{str.inspect}: #{e.message}"
end

Prevention

When it happens

Trigger: Range.parse('> 1.0', klass) splits into '>' and '1.0'; the token '>' has an empty (.+) part so match is nil and line 37 raises. Also '>= 2.0 <= 3.0' (both operators detached), or any token made only of [<>=] characters.

Common situations: Version constraints authored for readability as '>= 1.2.0' (with a space) in manifests or Hiera data; constraints copied from Gemfile/pip syntax that assumes space-tolerant parsing; templates interpolating "#{op} #{ver}" with an unintended space.

Understand the failure class

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/10c6cb4e85986168. Report an issue: GitHub.