puppetlabs/puppet · error · ArgumentError

Expiry dates must be YYYY-MM-DD or the string "absent"

Error message

Expiry dates must be YYYY-MM-DD or the string "absent"

What it means

Raised by the validate block of the `expiry` property on the `user` type. The property accepts exactly two forms: the symbol/string 'absent' (never expires) or a zero-padded date matching /^\d{4}-\d{2}-\d{2}$/ (also declared via newvalues). Anything else — slashes, month names, ISO timestamps, 'never' — raises this ArgumentError. The property requires the provider feature :manages_expiry (useradd, pw, openbsd, aix).

Source

Thrown at lib/puppet/type/user.rb:460

        if munge(val)
          raise ArgumentError, _("User provider %{name} can not manage home directories") % { name: provider.class.name } if provider and !provider.class.manages_homedir?
        end
      end
    end

    newproperty(:expiry, :required_features => :manages_expiry) do
      desc "The expiry date for this user. Provide as either the special
           value `absent` to ensure that the account never expires, or as
           a zero-padded YYYY-MM-DD format -- for example, 2010-02-19."

      newvalues :absent
      newvalues(/^\d{4}-\d{2}-\d{2}$/)

      validate do |value|
        if value.intern != :absent and value !~ /^\d{4}-\d{2}-\d{2}$/
          # TRANSLATORS YYYY-MM-DD represents a date with a four-digit year, a two-digit month, and a two-digit day,
          # TRANSLATORS separated by dashes.
          raise ArgumentError, _("Expiry dates must be YYYY-MM-DD or the string \"absent\"")
        end
      end
    end

    # Autorequire the group, if it's around
    autorequire(:group) do
      autos = []

      # autorequire primary group, if managed
      obj = @parameters[:gid]
      groups = obj.shouldorig if obj
      if groups
        groups = groups.collect { |group|
          if group.is_a?(String) && group =~ /^\d+$/
            Integer(group)
          else
            group
          end

View on GitHub (pinned to e227c27540)

Solutions

  1. Use zero-padded YYYY-MM-DD: `expiry => '2010-02-19'`
  2. Use the special value `expiry => absent` for accounts that never expire
  3. Normalize in the profile: `expiry => strftime(String($ts), '%Y-%m-%d')` or pg_time-style conversion before passing through

Example fix

# before
user { 'alice':
  ensure => present,
  expiry => '2010/02/19',
}

# after
user { 'alice':
  ensure => present,
  expiry => '2010-02-19',
}
Defensive patterns

Strategy: validation

Validate before calling

# Validate date shape before use
raise ArgumentError, 'expiry must be YYYY-MM-DD or absent' unless %w[absent].include?(v.to_s) || v.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)

Type guard

def valid_expiry?(v)
  s = v.to_s
  s == 'absent' || s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
end

Prevention

When it happens

Trigger: `user { 'alice': expiry => '2010/02/19' }`, `=> 'Feb 19 2010'`, `=> '2010-2-19'` (no zero padding), or `=> 'never'` instead of 'absent'; Hiera dates stored in locale-dependent formats.

Common situations: Dates coming from CMDBs in DD/MM/YYYY or epoch form; copy-paste from `chage -l` ('Feb 19, 2010'); template code formatting dates without strftime zero-padding.

Related errors


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