jenkins-bot has submitted this change and it was merged.

Change subject: Import wmflib from operations/puppet
......................................................................


Import wmflib from operations/puppet

This should really be done via git submodule, but I'm tired of waiting
for that and needed a better version of the php_ini function for some
hhvm cleanup. Imported from state at
43bb05b469e2a0f36bfc964a71e65f6fe93a5052 in operations/puppet.git.

Change-Id: I0069ce1210aeefca1ac45f2a997bcb3e089d4185
---
D puppet/modules/hhvm/lib/puppet/parser/functions/php_ini.rb
D puppet/modules/lib/lib/puppet/parser/functions/require_package.rb
A puppet/modules/wmflib/README.md
A puppet/modules/wmflib/lib/hiera/backend/mwyaml_backend.rb
A puppet/modules/wmflib/lib/hiera/backend/nuyaml_backend.rb
A puppet/modules/wmflib/lib/hiera/mwcache.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/apply_format.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/array_concat.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ensure_directory.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ensure_link.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ensure_service.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ordered_json.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ordered_yaml.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/php_ini.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/require_package.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/requires_realm.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/requires_ubuntu.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/shell_exports.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ssl_ciphersuite.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/to_milliseconds.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/to_seconds.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/ubuntu_version.rb
A puppet/modules/wmflib/lib/puppet/parser/functions/validate_ensure.rb
23 files changed, 1,319 insertions(+), 72 deletions(-)

Approvals:
  Ori.livneh: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/puppet/modules/hhvm/lib/puppet/parser/functions/php_ini.rb 
b/puppet/modules/hhvm/lib/puppet/parser/functions/php_ini.rb
deleted file mode 100644
index 98b5903..0000000
--- a/puppet/modules/hhvm/lib/puppet/parser/functions/php_ini.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# == Function: php_ini
-#
-# Serialize a hash into php.ini-style format. Takes one or more hashes
-# as arguments. If the argument list contains more than one hash, they
-# are merged together. In case of duplicate keys, hashes to the right
-# win.
-#
-# === Example
-#
-#   php_ini({'server' => {'port' => 80}}) # => server.port = 80
-#
-
-def flatten_map(map, prefix=nil)
-    map.inject({}) do |flat,(k,v)|
-        k = [prefix, k].compact.join('.')
-        v = v.include?('.') ? Float(v) : Integer(v) rescue v
-        flat.merge! v.is_a?(Hash) ? flatten_map(v, k) : Hash[k, v]
-    end
-end
-
-module Puppet::Parser::Functions
-  newfunction(
-    :php_ini,
-    :type => :rvalue,
-    :doc  => <<-END
-      Serialize a hash into php.ini-style format. Takes one or more hashes
-      as arguments. If the argument list contains more than one hash, they
-      are merged together. In case of duplicate keys, hashes to the right
-      win.
-
-      Example:
-
-         php_ini({'server' => {'port' => 80}}) # => server.port = 80
-
-    END
-  ) do |args|
-    fail 'php_ini() operates on hashes' if args.map(&:class).uniq != [Hash]
-    args.map { |arg| flatten_map(arg) }.
-         inject(:merge).
-         sort.
-         map { |kv| kv.join(' = ') }.
-         push('').
-         join("\n")
-  end
-end
diff --git a/puppet/modules/lib/lib/puppet/parser/functions/require_package.rb 
b/puppet/modules/lib/lib/puppet/parser/functions/require_package.rb
deleted file mode 100644
index 87781cb..0000000
--- a/puppet/modules/lib/lib/puppet/parser/functions/require_package.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# == Function: require_package
-#
-# Declare a package as a dependency for the current scope.
-#
-# === Examples
-#
-#  require_package('python-redis')
-#
-module Puppet::Parser::Functions
-  newfunction(:require_package, :arity => 1) do |args|
-    unless args.first.is_a?(String)
-      fail(ArgumentError, 'require_package(): string argument required.')
-    end
-
-    package_name = args.first
-    class_name = 'packages::' + package_name.tr('-', '_')
-
-    unless compiler.topscope.find_hostclass(class_name)
-      host = Puppet::Resource::Type.new(:hostclass, class_name)
-      known_resource_types.add_hostclass(host)
-      send Puppet::Parser::Functions.function(:create_resources),
-           ['package', { package_name => { :ensure => :present } }]
-    end
-
-    send Puppet::Parser::Functions.function(:require), [class_name]
-  end
-end
diff --git a/puppet/modules/wmflib/README.md b/puppet/modules/wmflib/README.md
new file mode 100644
index 0000000..882a17a
--- /dev/null
+++ b/puppet/modules/wmflib/README.md
@@ -0,0 +1,328 @@
+# wmflib
+
+Custom Puppet functions that help you get things done.
+
+
+## apply_format
+
+`apply_format( string $format, array $items )`
+
+Apply a format string to each element of an array.
+
+### Examples
+
+    $languages = [ 'finnish', 'french', 'greek', 'hebrew' ]
+    $packages = apply_format('texlive-lang-%s', $languages)
+
+
+## ensure_directory
+
+`ensure_directory( string|bool $ensure )`
+
+Takes a generic 'ensure' parameter value and convert it to an
+appropriate value for use with a directory declaration.
+
+If $ensure is 'true' or 'present', the return value is 'directory'.
+Otherwise, the return value is the unmodified $ensure parameter.
+
+### Examples
+
+    # Sample class which creates or removes '/srv/redis'
+    # based on the class's generic $ensure parameter:
+    class redis( $ensure = present ) {
+        package { 'redis-server':
+            ensure => $ensure,
+        }
+
+        file { '/srv/redis':
+          ensure => ensure_directory($ensure),
+        }
+    }
+
+
+## ensure_link
+
+`ensure_link( string|bool $ensure )`
+
+Takes a generic 'ensure' parameter value and convert it to an
+appropriate value for use with a symlink file declaration.
+
+If $ensure is 'true' or 'present', the return value is 'link'.
+Otherwise, the return value is the unmodified $ensure parameter.
+
+### Examples
+
+    # Sample class which creates or remove a symlink
+    # based on the class's generic $ensure parameter:
+    class rsyslog( $ensure = present ) {
+        package { 'rsyslog':
+            ensure => $ensure,
+        }
+
+        file { '/etc/rsyslog.d/50-default.conf':
+            ensure => ensure_link($ensure),
+            target => '/usr/share/rsyslog/50-default.conf',
+        }
+    }
+
+
+## ensure_service
+
+`ensure_service( string|bool $ensure )`
+
+Takes a generic 'ensure' parameter value and convert it to an
+appropriate value for use with a service declaration.
+
+If $ensure is 'true' or 'present', the return value is 'running'.
+Otherwise, the return value is 'stopped'.
+
+### Examples
+
+    # Sample class which starts or stops the redis service
+    # based on the class's generic $ensure parameter:
+    class redis( $ensure = present ) {
+        package { 'redis-server':
+            ensure => $ensure,
+        }
+        service { 'redis':
+            ensure  => ensure_service($ensure),
+            require => Package['redis-server'],
+        }
+    }
+
+
+## ordered_json
+
+`ordered_json( hash $data [, hash $... ] )`
+
+Serialize a hash into JSON with lexicographically sorted keys.
+
+Because the order of keys in Ruby 1.8 hashes is undefined, 'to_pson'
+is not idempotent: i.e., the serialized form of the same hash object
+can vary from one invocation to the next. This causes problems
+whenever a JSON-serialized hash is included in a file template,
+because the variations in key order are picked up as file updates by
+Puppet, causing Puppet to replace the file and refresh dependent
+resources on every run.
+
+### Examples
+
+    # Render a Puppet hash as a configuration file:
+    $options = { 'useGraphite' => true, 'minVal' => '0.1' }
+    file { '/etc/kibana/config.json':
+        content => ordered_json($options),
+    }
+
+
+## ordered_yaml
+
+`ordered_yaml( mixed $data )`
+
+Emit a hash as YAML with keys (both shallow and deep) in sorted order.
+
+### Examples
+
+    # Render a Puppet hash as a configuration file:
+    $options = { 'useGraphite' => true, 'minVal' => '0.1' }
+    file { '/etc/kibana/config.yaml':
+        content => ordered_yaml($options),
+    }
+
+
+## php_ini
+
+`php_ini( hash $ini_settings [, hash $... ] )`
+
+Serialize a hash into php.ini-style format. Takes one or more hashes as
+arguments. If the argument list contains more than one hash, they are
+merged together. In case of duplicate keys, hashes to the right win.
+
+### Example
+
+    php_ini({'server' => {'port' => 80}}) # => server.port = 80
+
+
+## require_package
+
+`require_package( string $package_name [, string $... ] )`
+
+Declare one or more packages a dependency for the current scope.
+This is equivalent to declaring and requiring the package resources.
+In other words, it ensures the package(s) are installed before
+evaluating any of the resources in the current scope.
+
+### Examples
+
+    # Single package
+    require_package('python-redis')
+
+    # Multiple packages as arguments
+    require_package('redis-server', 'python-redis')
+
+    # Multiple packages as array
+    $deps = [ 'redis-server', 'python-redis' ]
+    require_package($deps)
+
+
+## requires_realm
+
+`requires_realm( string $realm, [ string $message ] )`
+
+Validate that the host realm is equal to some value.
+Abort catalog compilation if it is not.
+
+### Examples
+
+    # Fail unless running in Labs:
+    requires_realm('labs')
+
+
+## requires_ubuntu
+
+`requires_ubuntu( string $version_predicate )`
+
+Validate that the host Ubuntu version satisfies a version
+check. Abort catalog compilation if not.
+
+See the documentation for ubuntu_version() for supported
+predicate syntax.
+
+### Examples
+
+    # Fail unless version is Trusty
+    requires_ubuntu('trusty')
+
+    # Fail unless Trusty or newer
+    requires_ubuntu('> trusty')
+
+
+
+## shell_exports
+
+`shell_exports( hash $variables [, bool $uppercase_keys = true ] )`
+
+Generate shell environment variable declarations out of a Puppet hash.
+
+The hash keys are used as the variable names, and the values as
+the variable's values. Values are automatically quoted with double
+quotes. If the second parameter is true (the default), keys are
+automatically uppercased.
+
+### Examples
+
+Invocation:
+
+    shell_exports({
+        apache_run_user => 'apache',
+        apache_pid_file => '/var/run/apache2/apache2.pid',
+    })
+
+Output:
+
+    export APACHE_RUN_USER="apache"
+    export APACHE_PID_FILE="/var/run/apache2/apache2.pid"
+
+
+## ssl_ciphersuite
+
+`ssl_ciphersuite( string $servercode, string $encryption_type, int $hsts_days 
)`
+
+Outputs the ssl configuration directives for use with either Nginx
+or Apache using our selection of ciphers and SSL options.
+
+Takes three arguments:
+
+- The servercode, or which browser-version combination to
+  support. At the moment only 'apache-2.2', 'apache-2.4' and 'nginx'
+  are supported.
+- The compatibility mode,indicating the degree of compatibility we
+  want to retain with older browsers (basically, IE6, IE7 and
+  Android prior to 3.0)
+- An optional argument, that if non-nil will set HSTS to max-age of
+  N days
+
+Whenever called, this function will output a list of strings that
+can be safely used in your configuration file as the ssl
+configuration part.
+
+### Examples
+
+    ssl_ciphersuite('apache-2.4', 'compat')
+    ssl_ciphersuite('nginx', 'strong')
+
+
+## to_milliseconds
+`to_milliseconds( string $time_spec )`
+
+Convert a unit of time expressed as a string to milliseconds.
+
+### Examples
+
+    to_milliseconds('1s')        # 1000
+    to_milliseconds('1 second')  # 1000
+
+
+## to_seconds
+`to_seconds( string $time_spec )`
+
+Convert a unit of time expressed as a string to seconds.
+
+### Examples
+
+    to_seconds('9000ms')  # 9
+    to_seconds('1hr')     # 3600
+    to_seconds('2 days')  # 172800
+
+
+## ubuntu_version
+
+`ubuntu_version( string $version_predicate )`
+
+Performs semantic Ubuntu version comparison.
+
+Takes a single string argument containing a comparison operator
+followed by an optional space, followed by a comparison target,
+provided as Ubuntu version number or release name.
+
+The host's Ubuntu version will be compared to to the comparison target
+using the specified operator, returning a boolean. If no operator is
+present, the equality operator is assumed.
+
+Release names are case-insensitive. The comparison operator and
+comparison target can be provided as two separate arguments, if you
+prefer.
+
+### Examples
+
+    # True if Precise or newer
+    ubuntu_version('>= precise')
+    ubuntu_version('>= 12.04.4')
+
+    # True if older than Utopic
+    ubuntu_version('< utopic')
+
+    # True if newer than Precise
+    ubuntu_version('> precise')
+
+    # True if Trusty or older
+    ubuntu_version('<= trusty')
+
+    # True if exactly Trusty
+    ubuntu_version('trusty')
+    ubuntu_version('== trusty')
+
+    # True if anything but Trusty
+    ubuntu_version('!trusty')
+    ubuntu_version('!= trusty')
+
+
+## validate_ensure
+
+`validate_ensure( string $ensure )`
+
+Throw an error if the $ensure argument is not 'present' or 'absent'.
+
+### Examples
+
+    # Abort compilation if $ensure is invalid
+    validate_ensure($ensure)
diff --git a/puppet/modules/wmflib/lib/hiera/backend/mwyaml_backend.rb 
b/puppet/modules/wmflib/lib/hiera/backend/mwyaml_backend.rb
new file mode 100644
index 0000000..6e0c638
--- /dev/null
+++ b/puppet/modules/wmflib/lib/hiera/backend/mwyaml_backend.rb
@@ -0,0 +1,49 @@
+require "hiera/mwcache"
+class Hiera
+  module Backend
+    class Mwyaml_backend
+      def initialize(cache=nil)
+        @cache = cache || Mwcache.new
+      end
+
+      def lookup(key, scope, order_override, resolution_type)
+        answer = nil
+        Hiera.debug("Looking up #{key}")
+
+        Backend.datasources(scope, order_override) do |source|
+          # Small hack: - we don't want to search any datasource but the
+          # labs/%{::instanceproject} hierarchy here; so we plainly exit
+          # in any other case
+          if m = /labs\/([^\/]+)$/.match(source)
+            source = m[1].capitalize
+          else
+            next
+          end
+          data = @cache.read(source, Hash, {}) do |content|
+            YAML.load(content)
+          end
+
+          next if data.empty?
+          next unless data.include?(key)
+
+          new_answer = Backend.parse_answer(data[key], scope)
+          case resolution_type
+          when :array
+            raise Exception, "Hiera type mismatch: expected Array and got 
#{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? 
String
+            answer ||= []
+            answer << new_answer
+          when :hash
+            raise Exception, "Hiera type mismatch: expected Hash and got 
#{new_answer.class}" unless new_answer.kind_of? Hash
+            answer ||= {}
+            answer = Backend.merge_answer(new_answer,answer)
+          else
+            answer = new_answer
+            break
+          end
+        end
+
+        return answer
+      end
+    end
+  end
+end
diff --git a/puppet/modules/wmflib/lib/hiera/backend/nuyaml_backend.rb 
b/puppet/modules/wmflib/lib/hiera/backend/nuyaml_backend.rb
new file mode 100644
index 0000000..2f4e376
--- /dev/null
+++ b/puppet/modules/wmflib/lib/hiera/backend/nuyaml_backend.rb
@@ -0,0 +1,183 @@
+# Nuyaml Hiera backend - the yaml backend with some sugar on top
+#
+# Based on the original yaml_backend from hiera distribution, any
+# modification/addition:
+# Author: Giuseppe Lavagetto <[email protected]>
+# Copyright  (c) 2014 Wikimedia Foundation
+#
+#
+# This backend allows some more flexibility over the vanilla yaml
+# backend, as path expansion in the lookup and even dynamic lookups.
+#
+# == Private path
+#
+# If you define a 'private' data source in hiera, we will look up
+# in a data.yaml file in the data dir we've specified as the datadir
+# for a 'private' backend, or in the default datadir as a fallback.
+#
+# == Path expansion
+#
+# Any hierarchy named in the backend-configuration section
+# :expand_path be expanded when looking the file up on disk. This
+# allows both to have a more granular set of files, but also to avoid
+# unnecessary cache evictions for cached data.
+#
+# === Example
+#
+# Say your hiera.yaml has defined
+#
+# :nuyaml:
+#   :expand_path:
+#     - module_data
+#
+# :hierarchy:
+#   - common
+#   - module_data
+#
+# then when searching hiera for say passwords::mysql::s1, hiera will
+# first load the #{datadir}/common.yaml file and search for
+# passwords::mysql::s1, then if not found, it will search for 's1'
+# inside the file #{datadir}/module_data/passwords/mysql.yaml
+#
+# Unless very small, all files should be split up like this.
+#
+# == Dynamic lookup
+#
+# Sometimes we want to search for data based on variables... that are
+# hosted within hiera! Dynamic lookup allows to define hierachies that
+# will determine the full path based on results from hiera
+# itself. Tricky? Let's see with an example
+#
+# === Example
+#
+# Say you have in your hiera config
+# :nuyaml:
+#   :dynamic_lookup:
+#      - role
+# :hierarchy:
+#   - "host/%{fqdn}"
+#   - role
+#
+# What will happen will be that any key we search (say $cluster) will
+# be first searched in the specific file for that host
+# (host/hostname.yaml), if not found, it will be searched as follows:
+# - if host/hostname.yaml contains a value for role, say
+#   'refrigerator', then lookup will continue in the
+#  'role/refrigerator.yaml' file
+# - else it will looked up in the 'role/default.yaml'
+#
+# Note that for added fun you may declare one part of the hierarchy to
+# be both dynamically looked up and expanded. It works!
+class Hiera
+  module Backend
+    class Nuyaml_backend
+
+      def initialize(cache=nil)
+        require 'yaml'
+        @cache = cache || Filecache.new
+        config = Config[:nuyaml]
+        @dynlookup = config[:dynlookup] || []
+        @expand_path = config[:expand_path] || []
+      end
+
+      def get_path(key, scope, source)
+        config_section = :nuyaml
+        # Special case: 'private' repository.
+        # We use a different datadir in this case.
+        # Example: private/common will search in the common source
+        # within the private datadir
+        if m = /private\/(.*)/.match(source)
+          config_section = :private
+          source = m[1]
+        end
+
+        Hiera.debug("The source is: #{source}")
+        # If the source is in the expand_path list, perform path
+        # expansion. This is thought to allow large codebases to live
+        # with fairly small yaml files as opposed to a very large one.
+        # Example:
+        # $apache::mpm::worker => 'worker' in common/apache/mpm.yaml
+        if @expand_path.include? source
+          namespaces = key.gsub(/^::/,'').split('::')
+          newkey = namespaces.pop
+
+          unless namespaces.empty?
+            source += "/".concat(namespaces.join('/'))
+            key = newkey
+          end
+        end
+
+        return key, Backend.datafile(config_section, scope, source, "yaml")
+      end
+
+      def lookup(key, scope, order_override, resolution_type)
+        answer = nil
+
+        Hiera.debug("Looking up #{key}")
+
+        Backend.datasources(scope, order_override) do |source|
+          # Yes this is kind of hacky. We look it up again on hiera,
+          # and build a source based on the lookup.
+          if @dynlookup.include? source
+            Hiera.debug("Dynamic lookup for source #{source}")
+            if key == source
+              next
+            end
+            dynsource = lookup(source, scope, order_override, :priority)
+            dynsource ||= 'default'
+            source += "/#{dynsource}"
+          end
+
+          Hiera.debug("Loading info from #{source} for #{key}")
+
+          lookup_key, yamlfile = get_path(key, scope, source)
+
+          Hiera.debug("Searching for #{lookup_key} in #{yamlfile}")
+
+          next if yamlfile.nil?
+
+          Hiera.debug("Loading file #{yamlfile}")
+
+          next unless File.exist?(yamlfile)
+
+          data = @cache.read(yamlfile, Hash) do |content|
+            YAML.load(content)
+          end
+
+          next if data.empty?
+          next unless data.include?(lookup_key)
+
+          # Extra logging that we found the key. This can be outputted
+          # multiple times if the resolution type is array or hash but that
+          # should be expected as the logging will then tell the user ALL the
+          # places where the key is found.
+          Hiera.debug("Found #{lookup_key} in #{source}")
+
+          # for array resolution we just append to the array whatever
+          # we find, we then goes onto the next file and keep adding to
+          # the array
+          #
+          # for priority searches we break after the first found data
+          # item
+
+          new_answer = Backend.parse_answer(data[lookup_key], scope)
+          case resolution_type
+          when :array
+            raise Exception, "Hiera type mismatch: expected Array and got 
#{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? 
String
+            answer ||= []
+            answer << new_answer
+          when :hash
+            raise Exception, "Hiera type mismatch: expected Hash and got 
#{new_answer.class}" unless new_answer.kind_of? Hash
+            answer ||= {}
+            answer = Backend.merge_answer(new_answer,answer)
+          else
+            answer = new_answer
+            break
+          end
+        end
+
+        return answer
+      end
+    end
+  end
+end
diff --git a/puppet/modules/wmflib/lib/hiera/mwcache.rb 
b/puppet/modules/wmflib/lib/hiera/mwcache.rb
new file mode 100644
index 0000000..485f167
--- /dev/null
+++ b/puppet/modules/wmflib/lib/hiera/mwcache.rb
@@ -0,0 +1,100 @@
+class Hiera
+  class Mwcache < Filecache
+    def initialize
+      super
+      require 'httpclient'
+      require 'yaml'
+      require 'json'
+      config = Config[:mwyaml]
+      @httphost = config[:host] || 'https://wikitech.wikimedia.org'
+      @endpoint = config[:endpoint] || '/w/api.php'
+      @http = HTTPClient.new(:agent_name => 'HieraMwCache/0.1')
+      @stat_ttl = config[:cache_ttl] || 60
+      if defined? @http.ssl_config.ssl_version
+        @http.ssl_config.ssl_version = 'TLSv1_2'
+      else
+        # Note: this seem to work in later versions of the library,
+        # but has no effect. How cute, I <3 ruby.
+        @http.ssl_config.options = OpenSSL::SSL::OP_NO_SSLv3
+      end
+    end
+
+    def read_file(path, expected_type = Object, &block)
+      if stale?(path)
+        resp = get_from_mediawiki(path, true)
+        data = resp["*"]
+        @cache[path][:data] = block_given? ? yield(data) : data
+
+        if !@cache[path][:data].is_a?(expected_type)
+          raise TypeError, "Data retrieved from #{path} is #{data.class} not 
#{expected_type}"
+        end
+      end
+
+      @cache[path][:data]
+    end
+
+    private
+
+    def stale?(path)
+      # Performs a request for the revision only
+      meta = path_metadata(path)
+
+      if @cache[path][:meta].nil?
+        @cache[path][:meta] = meta
+        return true
+      end
+      if @cache[path][:meta][:revision] == meta[:revision]
+        @cache[path][:meta][:ts] = meta[:ts]
+        return false
+      else
+        @cache[path][:meta] = meta
+        return true
+      end
+    rescue => detail
+      error = "Retrieving metadata from #{path} failed: #{detail}"
+      Hiera.warn(error)
+      # Fill  this up with very safe defaults - we cache non-existence
+      # for cache_ttl as well.
+      @cache[path][:meta] = {:ts => Time.now.to_i, :revision => 0}
+      return true
+    end
+
+    def path_metadata(path)
+      now = Time.now.to_i
+      if @cache[path].nil?
+        @cache[path] = {:data => nil, :meta => nil}
+      elsif (now - @cache[path][:meta][:ts]) <= @stat_ttl
+        # if we already fetched the result within the last stat_ttl seconds,
+        # we don't bother killing the mediawiki instance with a flood of 
requests
+        return @cache[path][:meta]
+      end
+      # TODO: add some locking mechanism for requests? Maybe overkill, maybe 
not.
+      revision = get_from_mediawiki(path, false)["revid"]
+
+      return {:ts => now, :revision => revision}
+    end
+
+
+    def get_from_mediawiki(path,want_content)
+      what = want_content ? 'content' : 'ids'
+      query_string = 
"action=query&prop=revisions&format=json&rvprop=#{what}&titles=Hiera:#{path}"
+      url = "#{@httphost}#{@endpoint}?#{query_string}"
+      Hiera.debug("Fetching #{url}")
+      res = @http.get(url)
+      if res.status_code != 200
+        raise IOError, "Could not correctly fetch revision for #{path}, HTTP 
status code #{res.status_code}"
+      end
+      # We shamelessly throw exceptions here, and catch them upper in the chain
+      # specifically in stale? and Filecache.read
+      body = JSON.parse(res.body)
+      pages = body["query"]["pages"]
+      # Quoting Yuvi: "MediaWiki API doesn't give a fuck about HTTP status 
codes"
+      if pages.keys.include? "-1"
+        raise IndexError, "The mediawiki page was non-existent"
+      end
+      #yes, it's that convoluted.
+      key = pages.keys[0]
+      return pages[key]["revisions"][0]
+    end
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/apply_format.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/apply_format.rb
new file mode 100644
index 0000000..d948a73
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/apply_format.rb
@@ -0,0 +1,16 @@
+# == Function: apply_format( string $format, array $items )
+#
+# Apply a format string to each element of an array.
+#
+# === Examples
+#
+#  $languages = [ 'finnish', 'french', 'greek', 'hebrew' ]
+#  $packages = apply_format('texlive-lang-%s', $languages)
+#
+module Puppet::Parser::Functions
+  newfunction(:apply_format, :type => :rvalue, :arity => 2) do |args|
+    format, *items = args
+    fail(ArgumentError, 'apply_format(): a format string is required') unless 
format.is_a?(String)
+    items.flatten.map { |item| format % item }
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/array_concat.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/array_concat.rb
new file mode 100644
index 0000000..df9f60b
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/array_concat.rb
@@ -0,0 +1,28 @@
+# == Function: array_concat( $args... )
+#
+# Concatenates things into an array.
+# Array arguments are concatenated together
+# Other types (e.g. Hashes, Strings) are included as whole single elements
+#
+# === Examples
+#
+# $a1 = [ 'a', 'b', 'c' ]
+# $a2 = [ 'd', 'e' ]
+# $a3 = 'f'
+# $a4 = { 'g' => 'h' }
+# $all = array_concat($a1, $a2, $a3, $a4)
+# ### $all == [ 'a', 'b', 'c', 'd', 'e', 'f', { 'g' => 'h' } ]
+#
+module Puppet::Parser::Functions
+  newfunction(:array_concat, :type => :rvalue) do |args|
+    retval = Array.new
+    args.each do |arg|
+        if arg.is_a? Array
+            retval += arg
+        else
+            retval += [ arg ]
+        end
+    end
+    retval
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_directory.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_directory.rb
new file mode 100644
index 0000000..ef0f4ac
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_directory.rb
@@ -0,0 +1,31 @@
+# == Function: ensure_directory( string|bool $ensure )
+#
+# Takes a generic 'ensure' parameter value and convert it to an
+# appropriate value for use with a directory declaration.
+#
+# If $ensure is 'true' or 'present', the return value is 'directory'.
+# Otherwise, the return value is the unmodified $ensure parameter.
+#
+# === Examples
+#
+#  # Sample class which creates or removes '/srv/redis'
+#  # based on the class's generic $ensure parameter:
+#  class redis( $ensure = present ) {
+#    package { 'redis-server':
+#      ensure => $ensure,
+#    }
+#    file { '/srv/redis':
+#      ensure => ensure_directory($ensure),
+#    }
+#  }
+#
+module Puppet::Parser::Functions
+  newfunction(:ensure_directory, :type => :rvalue, :arity => 1) do |args|
+    ensure_param = args.first
+    case ensure_param
+    when 'present', 'true', true then 'directory'
+    when 'absent', 'false', false then ensure_param
+    else fail(ArgumentError, "ensure_directory(): invalid argument: 
'#{ensure_param}'.")
+    end
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_link.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_link.rb
new file mode 100644
index 0000000..fd21f5a
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_link.rb
@@ -0,0 +1,32 @@
+# == Function: ensure_link( string|bool $ensure )
+#
+# Takes a generic 'ensure' parameter value and convert it to an
+# appropriate value for use with a symlink file declaration.
+#
+# If $ensure is 'true' or 'present', the return value is 'link'.
+# Otherwise, the return value is the unmodified $ensure parameter.
+#
+# === Examples
+#
+#  # Sample class which creates or remove a symlink
+#  # based on the class's generic $ensure parameter:
+#  class rsyslog( $ensure = present ) {
+#    package { 'rsyslog':
+#      ensure => $ensure,
+#    }
+#    file { '/etc/rsyslog.d/50-default.conf':
+#      ensure => ensure_link($ensure),
+#      target => '/usr/share/rsyslog/50-default.conf',
+#    }
+#  }
+#
+module Puppet::Parser::Functions
+  newfunction(:ensure_link, :type => :rvalue, :arity => 1) do |args|
+    ensure_param = args.first
+    case ensure_param
+    when 'present', 'true', true then 'link'
+    when 'absent', 'false', false then ensure_param
+    else fail(ArgumentError, "ensure_link(): invalid argument: 
'#{ensure_param}'.")
+    end
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_service.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_service.rb
new file mode 100644
index 0000000..7b42086
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ensure_service.rb
@@ -0,0 +1,32 @@
+# == Function: ensure_service( string|bool $ensure )
+#
+# Takes a generic 'ensure' parameter value and convert it to an
+# appropriate value for use with a service declaration.
+#
+# If $ensure is 'true' or 'present', the return value is 'running'.
+# Otherwise, the return value is 'stopped'.
+#
+# === Examples
+#
+#  # Sample class which starts or stops the redis service
+#  # based on the class's generic $ensure parameter:
+#  class redis( $ensure = present ) {
+#    package { 'redis-server':
+#      ensure => $ensure,
+#    }
+#    service { 'redis':
+#      ensure  => ensure_service($ensure),
+#      require => Package['redis-server'],
+#    }
+#  }
+#
+module Puppet::Parser::Functions
+  newfunction(:ensure_service, :type => :rvalue, :arity => 1) do |args|
+    ensure_param = args.first
+    case ensure_param
+    when 'running', 'present', 'true', true then 'running'
+    when 'stopped', 'absent', 'false', false then 'stopped'
+    else fail(ArgumentError, "ensure_service(): invalid argument: 
'#{ensure_param}'.")
+    end
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_json.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_json.rb
new file mode 100644
index 0000000..74a4e2c
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_json.rb
@@ -0,0 +1,36 @@
+# == Function: ordered_json( hash $data [, hash $... ] )
+#
+# Serialize a hash into JSON with lexicographically sorted keys.
+#
+# Because the order of keys in Ruby 1.8 hashes is undefined, 'to_pson'
+# is not idempotent: i.e., the serialized form of the same hash object
+# can vary from one invocation to the next. This causes problems
+# whenever a JSON-serialized hash is included in a file template,
+# because the variations in key order are picked up as file updates by
+# Puppet, causing Puppet to replace the file and refresh dependent
+# resources on every run.
+#
+# === Examples
+#
+#   # Render a Puppet hash as a configuration file:
+#   $options = { 'useGraphite' => true, 'minVal' => '0.1' }
+#   file { '/etc/kibana/config.json':
+#     content => ordered_json($options),
+#   }
+#
+def ordered_json(o)
+  case o
+  when Array
+    '[' + o.map { |x| ordered_json(x) }.join(', ') + ']'
+  when Hash
+    '{' + o.sort.map { |k, v| k.to_pson + ': ' + ordered_json(v) }.join(', ') 
+ '}'
+  else
+    o.include?('.') ? Float(o).to_s : Integer(o).to_s rescue o.to_pson
+  end
+end
+
+module Puppet::Parser::Functions
+  newfunction(:ordered_json, :type => :rvalue, :arity => -2) do |args|
+    ordered_json(args.reduce(&:merge))
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_yaml.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_yaml.rb
new file mode 100644
index 0000000..64973f4
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ordered_yaml.rb
@@ -0,0 +1,49 @@
+# == Function: ordered_yaml( mixed $data )
+#
+# Emit a hash as YAML with keys (both shallow and deep) in sorted order.
+#
+# === Examples
+#
+#   # Render a Puppet hash as a configuration file:
+#   $options = { 'useGraphite' => true, 'minVal' => '0.1' }
+#   file { '/etc/kibana/config.yaml':
+#     content => ordered_yaml($options),
+#   }
+#
+require 'puppet/util/zaml.rb'
+
+def sort_keys_recursive(value)
+  # Prepare a value for YAML serialization by sorting its keys (if it is
+  # a hash) and the keys of any hash object that is contained within the
+  # value. Returns a new value.
+  case value
+  when Array
+    value.map { |elem| sort_keys_recursive(elem) }
+  when Hash
+    map = {}
+    def map.each_pair
+      map.sort.each { |p| yield p }
+    end
+    value.sort.reduce(map) { |h, (k, v)| h[k] = sort_keys_recursive(v); h }
+  when 'true', 'false'
+    value == 'true'
+  when :undef
+    nil
+  else
+    value.include?('.') ? Float(value) : Integer(value) rescue value
+  end
+end
+
+def dedent_string(string)
+  lines = string.split("\n")
+  return string if lines.empty?
+  min_indent = lines.map { |line| line.start_with?(" ") ? line.match(/^ 
+/).offset(0)[1] : 0 }.min
+  return string if min_indent.zero?
+  lines.map { |line| line.gsub(/^ {#{min_indent}}/, "") }.join("\n")
+end
+
+module Puppet::Parser::Functions
+  newfunction(:ordered_yaml, :type => :rvalue, :arity => 1) do |args|
+    dedent_string(ZAML.dump(sort_keys_recursive(args.first)).gsub(/^---.*?\n/, 
'')) << "\n"
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/php_ini.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/php_ini.rb
new file mode 100644
index 0000000..6593989
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/php_ini.rb
@@ -0,0 +1,37 @@
+# == Function: php_ini( hash $ini_settings [, hash $... ] )
+#
+# Serialize a hash into php.ini-style format. Takes one or more hashes
+# as arguments. If the argument list contains more than one hash, they
+# are merged together. In case of duplicate keys, hashes to the right
+# win.
+#
+# === Example
+#
+#   php_ini({'server' => {'port' => 80}}) # => server.port = 80
+#
+def ini_flatten(map, prefix = nil)
+  map.reduce({}) do |flat, (k, v)|
+    k = [prefix, k].compact.join('.')
+    flat.merge! v.is_a?(Hash) ? ini_flatten(v, k) : Hash[k, v]
+  end
+end
+
+def ini_cast(v)
+  v.include?('.') ? Float(v) : Integer(v) rescue v
+end
+
+module Puppet::Parser::Functions
+  newfunction(:php_ini, :type => :rvalue, :arity => -2) do |args|
+    if args.map(&:class).uniq != [Hash]
+      fail(ArgumentError, 'php_ini(): hash arguments required')
+    end
+    flat = args.map { |arg| ini_flatten(arg) }.inject(:merge)
+    options = flat.map do |k, vs|
+      case vs
+      when Array then vs.map { |v| "#{k}[#{v}] = #{ini_cast(v)}" }
+      else "#{k} = #{ini_cast(vs)}"
+      end
+    end
+    options.flatten.sort.push('').join("\n")
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/require_package.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/require_package.rb
new file mode 100644
index 0000000..91d69cd
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/require_package.rb
@@ -0,0 +1,33 @@
+# == Function: require_package( string $package_name [, string $... ] )
+#
+# Declare one or more packages a dependency for the current scope.
+# This is equivalent to declaring and requiring the package resources.
+# In other words, it ensures the package(s) are installed before
+# evaluating any of the resources in the current scope.
+#
+# === Examples
+#
+#  # Single package
+#  require_package('python-redis')
+#
+#  # Multiple packages as arguments
+#  require_package('redis-server', 'python-redis')
+#
+#  # Multiple packages as array
+#  $deps = [ 'redis-server', 'python-redis' ]
+#  require_package($deps)
+#
+module Puppet::Parser::Functions
+  newfunction(:require_package, :arity => -2) do |args|
+    args.each do |package_name|
+      class_name = 'packages::' + package_name.tr('-', '_')
+      unless compiler.topscope.find_hostclass(class_name)
+        host = Puppet::Resource::Type.new(:hostclass, class_name)
+        known_resource_types.add_hostclass(host)
+        send Puppet::Parser::Functions.function(:create_resources),
+             ['package', { package_name => { :ensure => :present } }]
+      end
+      send Puppet::Parser::Functions.function(:require), [class_name]
+    end
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/requires_realm.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/requires_realm.rb
new file mode 100644
index 0000000..37ddab9
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/requires_realm.rb
@@ -0,0 +1,17 @@
+# == Function: requires_realm( string $realm, [ string $message ] )
+#
+# Validate that the host realm is equal to some value.
+# Abort catalog compilation if it is not.
+#
+# === Examples
+#
+#  # Fail unless running in Labs:
+#  requires_realm('labs')
+#
+module Puppet::Parser::Functions
+  newfunction(:requires_realm, :arity => 1) do |args|
+    realm, message = args
+    fail(ArgumentError, 'requires_realm(): string argument required') unless 
realm.is_a?(String)
+    fail(Puppet::ParseError, message || "Realm '#{realm}' required.") unless 
realm == lookupvar('realm')
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/requires_ubuntu.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/requires_ubuntu.rb
new file mode 100644
index 0000000..3e68cdc
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/requires_ubuntu.rb
@@ -0,0 +1,23 @@
+# == Function: requires_ubuntu( string $version_predicate )
+#
+# Validate that the host Ubuntu version satisfies a version
+# check. Abort catalog compilation if not.
+#
+# See the documentation for ubuntu_version() for supported
+# predicate syntax.
+#
+# === Examples
+#
+#  # Fail unless version is Trusty
+#  requires_ubuntu('trusty')
+#
+#  # Fail unless Trusty or newer
+#  requires_ubuntu('> trusty')
+#
+module Puppet::Parser::Functions
+  newfunction(:requires_ubuntu, :arity => 1) do |args|
+    Puppet::Parser::Functions.function(:ubuntu_version)
+    fail(ArgumentError, 'requires_ubuntu(): string argument required') unless 
args.first.is_a?(String)
+    fail(Puppet::ParseError, "Ubuntu #{args.first} required.") unless 
function_ubuntu_version(args)
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/shell_exports.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/shell_exports.rb
new file mode 100644
index 0000000..233ef3b
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/shell_exports.rb
@@ -0,0 +1,31 @@
+# == Function: shell_exports( hash $variables [, bool $uppercase_keys = true ] 
)
+#
+# Generate shell environment variable declarations out of a Puppet hash.
+#
+# The hash keys are used as the variable names, and the values as
+# the variable's values. Values are automatically quoted with double
+# quotes. If the second parameter is true (the default), keys are
+# automatically uppercased.
+#
+# === Examples
+#
+# Invocation:
+#
+#  shell_exports({
+#    apache_run_user => 'apache',
+#    apache_pid_file => '/var/run/apache2/apache2.pid',
+#  })
+#
+# Output:
+#
+#  export APACHE_RUN_USER="apache"
+#  export APACHE_PID_FILE="/var/run/apache2/apache2.pid"
+#
+module Puppet::Parser::Functions
+  newfunction(:shell_exports, :type  => :rvalue, :arity => 1) do |args|
+    vars, uppercase_keys = args
+    fail(ArgumentError, 'validate_ensure(): hash argument required') unless 
vars.is_a?(Hash)
+    vars = Hash[vars.map { |k, v| [k.upcase, v] }] unless uppercase_keys == 
false
+    vars.sort.map { |k, v| "export #{k}=#{v.to_pson}" }.push('').join("\n")
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/ssl_ciphersuite.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ssl_ciphersuite.rb
new file mode 100644
index 0000000..26d0bf7
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ssl_ciphersuite.rb
@@ -0,0 +1,139 @@
+# == Function: ssl_ciphersuite( string $servercode, string $encryption_type, 
int $hsts_days )
+#
+# Outputs the ssl configuration directives for use with either Nginx
+# or Apache using our selection of ciphers and SSL options.
+#
+# === Arguments
+#
+# Takes three arguments:
+#
+# - The servercode, or which browser-version combination to
+#   support. At the moment only 'apache-2.2', 'apache-2.4' and 'nginx'
+#   are supported.
+# - The compatibility mode,indicating the degree of compatibility we
+#   want to retain with older browsers (basically, IE6, IE7 and
+#   Android prior to 3.0)
+# - An optional argument, that if non-nil will set HSTS to max-age of
+#   N days
+#
+# Whenever called, this function will output a list of strings that
+# can be safely used in your configuration file as the ssl
+# configuration part.
+#
+# == Examples
+#
+#     ssl_ciphersuite('apache-2.4', 'compat')
+#     ssl_ciphersuite('nginx', 'strong')
+#
+# == License
+#
+# Author: Giuseppe Lavagetto
+# Copyright 2014 Wikimedia Foundation
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+require 'puppet/util/package'
+
+module Puppet::Parser::Functions
+  ciphersuites = {
+    'compat' => 
'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!DH',
+    'strong' => 
'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK:!DH'
+  }
+  newfunction(
+              :ssl_ciphersuite,
+              :type => :rvalue,
+              :doc  => <<-END
+Outputs the ssl configuration part of the webserver config.
+Function parameters are:
+ servercode - either nginx, apache-2.2 or apache-2.4
+ encryption_type - either strong for PFS only, or compat for maximum 
compatibility
+ hsts_days  - how many days should the STS header live. If not expressed, HSTS 
will
+              be disabled
+
+Examples:
+
+   ssl_ciphersuite('apache-2.4', 'compat') # Compatible config for apache 2.4
+   ssl_ciphersuite('nginx', 'strong', '365') # PFS-only, use HSTS for 365 days
+END
+              ) do |args|
+
+
+    if args.length < 2 || args.length > 3
+      fail(ArgumentError, 'ssl_ciphersuite() requires at least 2 arguments')
+    end
+
+    servercode = args.shift
+    case servercode
+    when 'apache-2.4' then
+      server = 'apache'
+      server_version = 24
+    when 'apache-2.2' then
+      server = 'apache'
+      server_version = 22
+    when 'nginx' then
+      server = 'nginx'
+      server_version = nil
+    else
+      fail(ArgumentError, "ssl_ciphersuite(): unknown server string 
'#{servercode}'")
+    end
+
+    ciphersuite = args.shift
+    unless ciphersuites.has_key?(ciphersuite)
+      fail(ArgumentError, "ssl_ciphersuite(): unknown ciphersuite 
'#{ciphersuite}'")
+    end
+
+    cipherlist = ciphersuites[ciphersuite]
+
+    if ciphersuite == 'strong' && server == 'apache' && server_version < 24
+      fail(ArgumentError, 'ssl_ciphersuite(): apache 2.2 cannot work in strong 
PFS mode')
+    end
+    if args.length == 1
+      hsts_days = args.shift.to_i
+    else
+      hsts_days = nil
+    end
+
+    output = []
+
+    if server == 'apache'
+      case ciphersuite
+      when 'strong' then
+        output.push('SSLProtocol all -SSLv2 -SSLv3 -TLSv1')
+      when 'compat' then
+        output.push('SSLProtocol all -SSLv2 -SSLv3')
+      end
+      output.push("SSLCipherSuite #{cipherlist}")
+      output.push('SSLHonorCipherOrder On')
+      unless hsts_days.nil?
+        hsts_seconds = hsts_days * 86400
+        output.push("Header set Strict-Transport-Security 
\"max-age=#{hsts_seconds}\"")
+      end
+    else
+      # nginx
+      case ciphersuite
+      when 'strong' then
+        output.push('ssl_protocols TLSv1.1 TLSv1.2;')
+      when 'compat' then
+        output.push('ssl_protocols TLSv1 TLSv1.1 TLSv1.2;')
+      end
+      output.push("ssl_ciphers #{cipherlist};")
+      output.push('ssl_prefer_server_ciphers on;')
+      unless hsts_days.nil?
+        hsts_seconds = hsts_days * 86400
+        output.push("add_header Strict-Transport-Security 
\"max-age=#{hsts_seconds}\";")
+      end
+    end
+    return output
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/to_milliseconds.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/to_milliseconds.rb
new file mode 100644
index 0000000..2421afe
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/to_milliseconds.rb
@@ -0,0 +1,33 @@
+# -*- coding: UTF-8 -*-
+#
+# == Function: to_milliseconds( string $time_spec )
+#
+# Convert a unit of time expressed as a string to milliseconds.
+#
+# === Examples
+#
+#  to_milliseconds('1s')        # 1000
+#  to_milliseconds('1 second')  # 1000
+#
+module Puppet::Parser::Functions
+  newfunction(:to_milliseconds, :type => :rvalue, :arity => 1) do |args|
+    time_spec = args.first
+    /^([0-9.+e]+)\s*(.*).?$/ =~ time_spec.downcase
+    count, unit = $1, $2
+    factor = case unit
+             when /^n/         then 1.0e-6      # nanoseconds
+             when /^u/         then 1.0e-3      # microseconds
+             when /^(ms|mil)/  then 1.0         # milliseconds
+             when /^s/         then 1.0e3       # seconds
+             when /^(m|min)/   then 6.0e4       # minutes
+             when /^h/         then 3.6e6       # hours
+             when /^d/         then 8.64e7      # days
+             when /^w/         then 6.048e8     # weeks
+             when /^mo/        then 2.62974e9   # months
+             when /^y/         then 3.15569e10  # years
+             else fail(ArgumentError, "to_milliseconds(): Invalid time spec 
#{time_spec.inspect}")
+    end
+    ms = factor * Float(count)
+    ms.to_i == ms ? ms.to_i : ms
+  end
+end
diff --git a/puppet/modules/wmflib/lib/puppet/parser/functions/to_seconds.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/to_seconds.rb
new file mode 100644
index 0000000..9340396
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/to_seconds.rb
@@ -0,0 +1,16 @@
+# == Function: to_seconds( string $time_spec )
+#
+# Convert a unit of time expressed as a string to seconds.
+#
+# === Examples
+#
+#  to_seconds('9000ms')  # 9
+#  to_seconds('1hr')     # 3600
+#  to_seconds('2 days')  # 172800
+#
+module Puppet::Parser::Functions
+  newfunction(:to_seconds, :type => :rvalue, :arity => 1) do |args|
+    s = send(Puppet::Parser::Functions.function(:to_milliseconds), args) / 
1000.0
+    s.to_i == s ? s.to_i : s
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/ubuntu_version.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/ubuntu_version.rb
new file mode 100644
index 0000000..6c998b3
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/ubuntu_version.rb
@@ -0,0 +1,90 @@
+# == Function: ubuntu_version( string $version_predicate )
+#
+# Performs semantic Ubuntu version comparison.
+#
+# Takes a single string argument containing a comparison operator
+# followed by an optional space, followed by a comparison target,
+# provided as Ubuntu version number or release name.
+#
+# The host's Ubuntu version will be compared to to the comparison target
+# using the specified operator, returning a boolean. If no operator is
+# present, the equality operator is assumed.
+#
+# Release names are case-insensitive. The comparison operator and
+# comparison target can be provided as two separate arguments, if you
+# prefer.
+#
+# === Examples
+#
+#  # True if Precise or newer
+#  ubuntu_version('>= precise')
+#  ubuntu_version('>= 12.04.4')
+#
+#  # True if older than Utopic
+#  ubuntu_version('< utopic')
+#
+#  # True if newer than Precise
+#  ubuntu_version('> precise')
+#
+#  # True if Trusty or older
+#  ubuntu_version('<= trusty')
+#
+#  # True if exactly Trusty
+#  ubuntu_version('trusty')
+#  ubuntu_version('== trusty')
+#
+#  # True if anything but Trusty
+#  ubuntu_version('!trusty')
+#  ubuntu_version('!= trusty')
+#
+require 'puppet/util/package'
+
+module Puppet::Parser::Functions
+  ubuntu_releases = {
+    'hardy'    => '8.04',
+    'intrepid' => '8.10',
+    'jaunty'   => '9.04',
+    'karmic'   => '9.10',
+    'lucid'    => '10.04.4',
+    'maverick' => '10.10',
+    'natty'    => '11.04',
+    'oneiric'  => '11.10',
+    'precise'  => '12.04.4',
+    'quantal'  => '12.10',
+    'raring'   => '13.04',
+    'saucy'    => '13.10',
+    'trusty'   => '14.04',
+    'utopic'   => '14.10'
+  }
+
+  newfunction(:ubuntu_version, :type => :rvalue, :arity => 1) do |args|
+    return false unless lookupvar('lsbdistid') == 'Ubuntu'
+
+    unless args.length <= 2 && args.map(&:class).uniq == [String]
+      fail(ArgumentError, 'ubuntu_version() requires a string argument')
+    end
+
+    expr = args.join(' ')
+    unless expr =~ /^([<>=]*) *([\w\.]+)$/
+      fail(ArgumentError, "ubuntu_version(): invalid expression '#{expr}'")
+    end
+
+    current = lookupvar('lsbdistrelease')
+    operator = $1
+    other = ubuntu_releases[$2.downcase] || $2
+    unless /^[\d.]+$/ =~ other
+      fail(ArgumentError, "ubuntu_version(): unknown release '#{other}'")
+    end
+
+    cmp = Puppet::Util::Package.versioncmp(current, other)
+    case operator
+    when '', '=', '==' then cmp == 0
+    when '!=', '!' then cmp != 0
+    when '>'  then cmp == 1
+    when '<'  then cmp == -1
+    when '>=' then cmp >= 0
+    when '<=' then cmp <= 0
+    else fail(ArgumentError, "ubuntu_version(): unknown comparison operator 
'#{operator}'")
+    end
+  end
+end
diff --git 
a/puppet/modules/wmflib/lib/puppet/parser/functions/validate_ensure.rb 
b/puppet/modules/wmflib/lib/puppet/parser/functions/validate_ensure.rb
new file mode 100644
index 0000000..f17a9dd
--- /dev/null
+++ b/puppet/modules/wmflib/lib/puppet/parser/functions/validate_ensure.rb
@@ -0,0 +1,16 @@
+# == Function: validate_ensure( string $ensure )
+#
+# Throw an error if the $ensure argument is not 'present' or 'absent'.
+#
+# === Examples
+#
+#  # Abort compilation if $ensure is invalid
+#  validate_ensure($ensure)
+#
+module Puppet::Parser::Functions
+  newfunction(:validate_ensure, :arity => 1) do |args|
+    unless %w(present absent).include?(args.first)
+      fail(Puppet::ParseError, "$ensure must be \"present\" or \"absent\" 
(got: #{args.first.inspect}).")
+    end
+  end
+end

-- 
To view, visit https://gerrit.wikimedia.org/r/172004
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0069ce1210aeefca1ac45f2a997bcb3e089d4185
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/vagrant
Gerrit-Branch: master
Gerrit-Owner: BryanDavis <[email protected]>
Gerrit-Reviewer: BryanDavis <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to