summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/facter/facter_dot_d.rb202
-rw-r--r--lib/facter/netmask_cidr_interface.rb22
-rw-r--r--lib/facter/pe_version.rb53
-rw-r--r--lib/facter/puppet_vardir.rb26
-rw-r--r--lib/facter/root_home.rb32
-rw-r--r--lib/facter/util/puppet_settings.rb21
-rw-r--r--lib/leap_cli/commands/README11
-rw-r--r--lib/leap_cli/commands/ca.rb541
-rw-r--r--lib/leap_cli/commands/clean.rb16
-rw-r--r--lib/leap_cli/commands/compile.rb531
-rw-r--r--lib/leap_cli/commands/db.rb86
-rw-r--r--lib/leap_cli/commands/deploy.rb374
-rw-r--r--lib/leap_cli/commands/env.rb76
-rw-r--r--lib/leap_cli/commands/facts.rb100
-rw-r--r--lib/leap_cli/commands/info.rb15
-rw-r--r--lib/leap_cli/commands/inspect.rb144
-rw-r--r--lib/leap_cli/commands/list.rb132
-rw-r--r--lib/leap_cli/commands/node.rb188
-rw-r--r--lib/leap_cli/commands/node_init.rb169
-rw-r--r--lib/leap_cli/commands/ssh.rb225
-rw-r--r--lib/leap_cli/commands/test.rb74
-rw-r--r--lib/leap_cli/commands/user.rb136
-rw-r--r--lib/leap_cli/commands/util.rb50
-rw-r--r--lib/leap_cli/commands/vagrant.rb180
-rw-r--r--lib/leap_cli/macros.rb16
-rw-r--r--lib/leap_cli/macros/core.rb92
-rw-r--r--lib/leap_cli/macros/files.rb124
-rw-r--r--lib/leap_cli/macros/haproxy.rb73
-rw-r--r--lib/leap_cli/macros/hosts.rb90
-rw-r--r--lib/leap_cli/macros/keys.rb97
-rw-r--r--lib/leap_cli/macros/nodes.rb88
-rw-r--r--lib/leap_cli/macros/provider.rb90
-rw-r--r--lib/leap_cli/macros/secrets.rb39
-rw-r--r--lib/leap_cli/macros/stunnel.rb106
-rw-r--r--lib/puppet/functions/type_of.rb17
-rw-r--r--lib/puppet/parser/functions/abs.rb36
-rw-r--r--lib/puppet/parser/functions/any2array.rb33
-rw-r--r--lib/puppet/parser/functions/base64.rb37
-rw-r--r--lib/puppet/parser/functions/basename.rb34
-rw-r--r--lib/puppet/parser/functions/bool2num.rb26
-rw-r--r--lib/puppet/parser/functions/bool2str.rb27
-rw-r--r--lib/puppet/parser/functions/camelcase.rb33
-rw-r--r--lib/puppet/parser/functions/capitalize.rb33
-rw-r--r--lib/puppet/parser/functions/chomp.rb34
-rw-r--r--lib/puppet/parser/functions/chop.rb36
-rw-r--r--lib/puppet/parser/functions/concat.rb41
-rw-r--r--lib/puppet/parser/functions/count.rb22
-rw-r--r--lib/puppet/parser/functions/deep_merge.rb44
-rw-r--r--lib/puppet/parser/functions/defined_with_params.rb35
-rw-r--r--lib/puppet/parser/functions/delete.rb49
-rw-r--r--lib/puppet/parser/functions/delete_at.rb49
-rw-r--r--lib/puppet/parser/functions/delete_undef_values.rb34
-rw-r--r--lib/puppet/parser/functions/delete_values.rb26
-rw-r--r--lib/puppet/parser/functions/difference.rb36
-rw-r--r--lib/puppet/parser/functions/dirname.rb15
-rw-r--r--lib/puppet/parser/functions/downcase.rb32
-rw-r--r--lib/puppet/parser/functions/empty.rb27
-rw-r--r--lib/puppet/parser/functions/ensure_packages.rb35
-rw-r--r--lib/puppet/parser/functions/ensure_resource.rb46
-rw-r--r--lib/puppet/parser/functions/flatten.rb33
-rw-r--r--lib/puppet/parser/functions/floor.rb25
-rw-r--r--lib/puppet/parser/functions/fqdn_rotate.rb45
-rw-r--r--lib/puppet/parser/functions/get_module_path.rb17
-rw-r--r--lib/puppet/parser/functions/getparam.rb35
-rw-r--r--lib/puppet/parser/functions/getvar.rb29
-rw-r--r--lib/puppet/parser/functions/grep.rb33
-rw-r--r--lib/puppet/parser/functions/has_interface_with.rb68
-rw-r--r--lib/puppet/parser/functions/has_ip_address.rb25
-rw-r--r--lib/puppet/parser/functions/has_ip_network.rb25
-rw-r--r--lib/puppet/parser/functions/has_key.rb28
-rw-r--r--lib/puppet/parser/functions/hash.rb41
-rw-r--r--lib/puppet/parser/functions/intersection.rb34
-rw-r--r--lib/puppet/parser/functions/is_array.rb22
-rw-r--r--lib/puppet/parser/functions/is_bool.rb22
-rw-r--r--lib/puppet/parser/functions/is_domain_name.rb50
-rw-r--r--lib/puppet/parser/functions/is_float.rb30
-rw-r--r--lib/puppet/parser/functions/is_function_available.rb26
-rw-r--r--lib/puppet/parser/functions/is_hash.rb22
-rw-r--r--lib/puppet/parser/functions/is_integer.rb45
-rw-r--r--lib/puppet/parser/functions/is_ip_address.rb32
-rw-r--r--lib/puppet/parser/functions/is_mac_address.rb27
-rw-r--r--lib/puppet/parser/functions/is_numeric.rb75
-rw-r--r--lib/puppet/parser/functions/is_string.rb26
-rw-r--r--lib/puppet/parser/functions/join.rb41
-rw-r--r--lib/puppet/parser/functions/join_keys_to_values.rb47
-rw-r--r--lib/puppet/parser/functions/keys.rb26
-rw-r--r--lib/puppet/parser/functions/loadyaml.rb20
-rw-r--r--lib/puppet/parser/functions/lstrip.rb32
-rw-r--r--lib/puppet/parser/functions/max.rb21
-rw-r--r--lib/puppet/parser/functions/member.rb62
-rw-r--r--lib/puppet/parser/functions/merge.rb34
-rw-r--r--lib/puppet/parser/functions/min.rb21
-rw-r--r--lib/puppet/parser/functions/num2bool.rb43
-rw-r--r--lib/puppet/parser/functions/obfuscate_email.rb16
-rw-r--r--lib/puppet/parser/functions/parsejson.rb24
-rw-r--r--lib/puppet/parser/functions/parseyaml.rb24
-rw-r--r--lib/puppet/parser/functions/pick.rb29
-rw-r--r--lib/puppet/parser/functions/pick_default.rb35
-rw-r--r--lib/puppet/parser/functions/prefix.rb45
-rw-r--r--lib/puppet/parser/functions/private.rb29
-rw-r--r--lib/puppet/parser/functions/range.rb88
-rw-r--r--lib/puppet/parser/functions/reject.rb31
-rw-r--r--lib/puppet/parser/functions/reverse.rb27
-rw-r--r--lib/puppet/parser/functions/rstrip.rb31
-rw-r--r--lib/puppet/parser/functions/shuffle.rb45
-rw-r--r--lib/puppet/parser/functions/size.rb48
-rw-r--r--lib/puppet/parser/functions/sort.rb27
-rw-r--r--lib/puppet/parser/functions/squeeze.rb36
-rw-r--r--lib/puppet/parser/functions/str2bool.rb46
-rw-r--r--lib/puppet/parser/functions/str2saltedsha1.rb32
-rw-r--r--lib/puppet/parser/functions/str2saltedsha512.rb32
-rw-r--r--lib/puppet/parser/functions/str2sha1_and_salt.rb36
-rw-r--r--lib/puppet/parser/functions/str_and_salt2sha1.rb32
-rw-r--r--lib/puppet/parser/functions/strftime.rb107
-rw-r--r--lib/puppet/parser/functions/strip.rb38
-rw-r--r--lib/puppet/parser/functions/suffix.rb45
-rw-r--r--lib/puppet/parser/functions/swapcase.rb38
-rw-r--r--lib/puppet/parser/functions/time.rb49
-rw-r--r--lib/puppet/parser/functions/to_bytes.rb31
-rw-r--r--lib/puppet/parser/functions/type.rb19
-rw-r--r--lib/puppet/parser/functions/type3x.rb51
-rw-r--r--lib/puppet/parser/functions/union.rb34
-rw-r--r--lib/puppet/parser/functions/unique.rb50
-rw-r--r--lib/puppet/parser/functions/upcase.rb40
-rw-r--r--lib/puppet/parser/functions/uriescape.rb34
-rw-r--r--lib/puppet/parser/functions/validate_absolute_path.rb69
-rw-r--r--lib/puppet/parser/functions/validate_array.rb33
-rw-r--r--lib/puppet/parser/functions/validate_augeas.rb83
-rw-r--r--lib/puppet/parser/functions/validate_bool.rb34
-rw-r--r--lib/puppet/parser/functions/validate_cmd.rb63
-rw-r--r--lib/puppet/parser/functions/validate_hash.rb33
-rw-r--r--lib/puppet/parser/functions/validate_ipv4_address.rb48
-rw-r--r--lib/puppet/parser/functions/validate_ipv6_address.rb49
-rw-r--r--lib/puppet/parser/functions/validate_re.rb40
-rw-r--r--lib/puppet/parser/functions/validate_slength.rb71
-rw-r--r--lib/puppet/parser/functions/validate_string.rb38
-rw-r--r--lib/puppet/parser/functions/values.rb39
-rw-r--r--lib/puppet/parser/functions/values_at.rb99
-rw-r--r--lib/puppet/parser/functions/zip.rb39
-rw-r--r--lib/puppet/provider/file_line/ruby.rb85
-rw-r--r--lib/puppet/type/anchor.rb46
-rw-r--r--lib/puppet/type/file_line.rb75
142 files changed, 3863 insertions, 4548 deletions
diff --git a/lib/facter/facter_dot_d.rb b/lib/facter/facter_dot_d.rb
deleted file mode 100644
index b0584370..00000000
--- a/lib/facter/facter_dot_d.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-# A Facter plugin that loads facts from /etc/facter/facts.d
-# and /etc/puppetlabs/facter/facts.d.
-#
-# Facts can be in the form of JSON, YAML or Text files
-# and any executable that returns key=value pairs.
-#
-# In the case of scripts you can also create a file that
-# contains a cache TTL. For foo.sh store the ttl as just
-# a number in foo.sh.ttl
-#
-# The cache is stored in /tmp/facts_cache.yaml as a mode
-# 600 file and will have the end result of not calling your
-# fact scripts more often than is needed
-
-class Facter::Util::DotD
- require 'yaml'
-
- def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache"))
- @dir = dir
- @cache_file = cache_file
- @cache = nil
- @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml}
- end
-
- def entries
- Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) }
- rescue
- []
- end
-
- def fact_type(file)
- extension = File.extname(file)
-
- type = @types[extension] || :unknown
-
- type = :script if type == :unknown && File.executable?(file)
-
- return type
- end
-
- def txt_parser(file)
- File.readlines(file).each do |line|
- if line =~ /^([^=]+)=(.+)$/
- var = $1; val = $2
-
- Facter.add(var) do
- setcode { val }
- end
- end
- end
- rescue Exception => e
- Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}")
- end
-
- def json_parser(file)
- begin
- require 'json'
- rescue LoadError
- retry if require 'rubygems'
- raise
- end
-
- JSON.load(File.read(file)).each_pair do |f, v|
- Facter.add(f) do
- setcode { v }
- end
- end
- rescue Exception => e
- Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}")
- end
-
- def yaml_parser(file)
- require 'yaml'
-
- YAML.load_file(file).each_pair do |f, v|
- Facter.add(f) do
- setcode { v }
- end
- end
- rescue Exception => e
- Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}")
- end
-
- def script_parser(file)
- result = cache_lookup(file)
- ttl = cache_time(file)
-
- unless result
- result = Facter::Util::Resolution.exec(file)
-
- if ttl > 0
- Facter.debug("Updating cache for #{file}")
- cache_store(file, result)
- cache_save!
- end
- else
- Facter.debug("Using cached data for #{file}")
- end
-
- result.split("\n").each do |line|
- if line =~ /^(.+)=(.+)$/
- var = $1; val = $2
-
- Facter.add(var) do
- setcode { val }
- end
- end
- end
- rescue Exception => e
- Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}")
- Facter.debug(e.backtrace.join("\n\t"))
- end
-
- def cache_save!
- cache = load_cache
- File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) }
- rescue
- end
-
- def cache_store(file, data)
- load_cache
-
- @cache[file] = {:data => data, :stored => Time.now.to_i}
- rescue
- end
-
- def cache_lookup(file)
- cache = load_cache
-
- return nil if cache.empty?
-
- ttl = cache_time(file)
-
- if cache[file]
- now = Time.now.to_i
-
- return cache[file][:data] if ttl == -1
- return cache[file][:data] if (now - cache[file][:stored]) <= ttl
- return nil
- else
- return nil
- end
- rescue
- return nil
- end
-
- def cache_time(file)
- meta = file + ".ttl"
-
- return File.read(meta).chomp.to_i
- rescue
- return 0
- end
-
- def load_cache
- unless @cache
- if File.exist?(@cache_file)
- @cache = YAML.load_file(@cache_file)
- else
- @cache = {}
- end
- end
-
- return @cache
- rescue
- @cache = {}
- return @cache
- end
-
- def create
- entries.each do |fact|
- type = fact_type(fact)
- parser = "#{type}_parser"
-
- if respond_to?("#{type}_parser")
- Facter.debug("Parsing #{fact} using #{parser}")
-
- send(parser, fact)
- end
- end
- end
-end
-
-
-mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/)
-if mdata
- (major, minor, patch) = mdata.captures.map { |v| v.to_i }
- if major < 2
- # Facter 1.7 introduced external facts support directly
- unless major == 1 and minor > 6
- Facter::Util::DotD.new("/etc/facter/facts.d").create
- Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create
-
- # Windows has a different configuration directory that defaults to a vendor
- # specific sub directory of the %COMMON_APPDATA% directory.
- if Dir.const_defined? 'COMMON_APPDATA' then
- windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d')
- Facter::Util::DotD.new(windows_facts_dot_d).create
- end
- end
- end
-end
diff --git a/lib/facter/netmask_cidr_interface.rb b/lib/facter/netmask_cidr_interface.rb
deleted file mode 100644
index d628d08c..00000000
--- a/lib/facter/netmask_cidr_interface.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# adds netmask facts for each interface in cidr notation
-# i.e.:
-# ...
-# netmask_cidr_eth2 => 24
-# netmask_cidr_lo => 8
-# netmask_cidr_tun0 => 32
-# netmask_cidr_virbr0 => 24
-# ...
-
-require 'facter/util/ip'
-
-Facter::Util::IP.get_interfaces.each do |interface|
- netmask = Facter.value("netmask_#{interface}")
- if netmask != nil
- Facter.add("netmask_cidr_" + interface ) do
- setcode do
- cidr_netmask=IPAddr.new(netmask).to_i.to_s(2).count("1")
- cidr_netmask
- end
- end
- end
-end
diff --git a/lib/facter/pe_version.rb b/lib/facter/pe_version.rb
deleted file mode 100644
index 0cc0f64e..00000000
--- a/lib/facter/pe_version.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# Fact: is_pe, pe_version, pe_major_version, pe_minor_version, pe_patch_version
-#
-# Purpose: Return various facts about the PE state of the system
-#
-# Resolution: Uses a regex match against puppetversion to determine whether the
-# machine has Puppet Enterprise installed, and what version (overall, major,
-# minor, patch) is installed.
-#
-# Caveats:
-#
-Facter.add("pe_version") do
- setcode do
- pe_ver = Facter.value("puppetversion").match(/Puppet Enterprise (\d+\.\d+\.\d+)/)
- pe_ver[1] if pe_ver
- end
-end
-
-Facter.add("is_pe") do
- setcode do
- if Facter.value(:pe_version).to_s.empty? then
- false
- else
- true
- end
- end
-end
-
-Facter.add("pe_major_version") do
- confine :is_pe => true
- setcode do
- if pe_version = Facter.value(:pe_version)
- pe_version.to_s.split('.')[0]
- end
- end
-end
-
-Facter.add("pe_minor_version") do
- confine :is_pe => true
- setcode do
- if pe_version = Facter.value(:pe_version)
- pe_version.to_s.split('.')[1]
- end
- end
-end
-
-Facter.add("pe_patch_version") do
- confine :is_pe => true
- setcode do
- if pe_version = Facter.value(:pe_version)
- pe_version.to_s.split('.')[2]
- end
- end
-end
diff --git a/lib/facter/puppet_vardir.rb b/lib/facter/puppet_vardir.rb
deleted file mode 100644
index 0e6af40e..00000000
--- a/lib/facter/puppet_vardir.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# This facter fact returns the value of the Puppet vardir setting for the node
-# running puppet or puppet agent. The intent is to enable Puppet modules to
-# automatically have insight into a place where they can place variable data,
-# regardless of the node's platform.
-#
-# The value should be directly usable in a File resource path attribute.
-
-
-begin
- require 'facter/util/puppet_settings'
-rescue LoadError => e
- # puppet apply does not add module lib directories to the $LOAD_PATH (See
- # #4248). It should (in the future) but for the time being we need to be
- # defensive which is what this rescue block is doing.
- rb_file = File.join(File.dirname(__FILE__), 'util', 'puppet_settings.rb')
- load rb_file if File.exists?(rb_file) or raise e
-end
-
-Facter.add(:puppet_vardir) do
- setcode do
- # This will be nil if Puppet is not available.
- Facter::Util::PuppetSettings.with_puppet do
- Puppet[:vardir]
- end
- end
-end
diff --git a/lib/facter/root_home.rb b/lib/facter/root_home.rb
deleted file mode 100644
index b4f87ff2..00000000
--- a/lib/facter/root_home.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# A facter fact to determine the root home directory.
-# This varies on PE supported platforms and may be
-# reconfigured by the end user.
-
-module Facter::Util::RootHome
- class << self
- def get_root_home
- root_ent = Facter::Util::Resolution.exec("getent passwd root")
- # The home directory is the sixth element in the passwd entry
- # If the platform doesn't have getent, root_ent will be nil and we should
- # return it straight away.
- root_ent && root_ent.split(":")[5]
- end
- end
-end
-
-Facter.add(:root_home) do
- setcode { Facter::Util::RootHome.get_root_home }
-end
-
-Facter.add(:root_home) do
- confine :kernel => :darwin
- setcode do
- str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root")
- hash = {}
- str.split("\n").each do |pair|
- key,value = pair.split(/:/)
- hash[key] = value
- end
- hash['dir'].strip
- end
-end
diff --git a/lib/facter/util/puppet_settings.rb b/lib/facter/util/puppet_settings.rb
deleted file mode 100644
index 1ad94521..00000000
--- a/lib/facter/util/puppet_settings.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module Facter
- module Util
- module PuppetSettings
- # This method is intended to provide a convenient way to evaluate a
- # Facter code block only if Puppet is loaded. This is to account for the
- # situation where the fact happens to be in the load path, but Puppet is
- # not loaded for whatever reason. Perhaps the user is simply running
- # facter without the --puppet flag and they happen to be working in a lib
- # directory of a module.
- def self.with_puppet
- begin
- Module.const_get("Puppet")
- rescue NameError
- nil
- else
- yield
- end
- end
- end
- end
-end
diff --git a/lib/leap_cli/commands/README b/lib/leap_cli/commands/README
new file mode 100644
index 00000000..bec78179
--- /dev/null
+++ b/lib/leap_cli/commands/README
@@ -0,0 +1,11 @@
+This directory contains ruby source files that define the available sub-
+commands of the `leap` executable.
+
+For example, the command:
+
+ leap compile
+
+Lives in lib/leap_cli/commands/init.rb
+
+These files use a DSL (called GLI) for defining command suites.
+See https://github.com/davetron5000/gli for more information.
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
new file mode 100644
index 00000000..1b311eee
--- /dev/null
+++ b/lib/leap_cli/commands/ca.rb
@@ -0,0 +1,541 @@
+autoload :OpenSSL, 'openssl'
+autoload :CertificateAuthority, 'certificate_authority'
+autoload :Date, 'date'
+require 'digest/md5'
+
+module LeapCli; module Commands
+
+ desc "Manage X.509 certificates"
+ command :cert do |cert|
+
+ cert.desc 'Creates two Certificate Authorities (one for validating servers and one for validating clients).'
+ cert.long_desc 'See see what values are used in the generation of the certificates (like name and key size), run `leap inspect provider` and look for the "ca" property. To see the details of the created certs, run `leap inspect <file>`.'
+ cert.command :ca do |ca|
+ ca.action do |global_options,options,args|
+ assert_config! 'provider.ca.name'
+ generate_new_certificate_authority(:ca_key, :ca_cert, provider.ca.name)
+ generate_new_certificate_authority(:client_ca_key, :client_ca_cert, provider.ca.name + ' (client certificates only!)')
+ end
+ end
+
+ cert.desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.'
+ cert.long_desc 'This command will a generate new certificate for a node if some value in the node has changed ' +
+ 'that is included in the certificate (like hostname or IP address), or if the old certificate will be expiring soon. ' +
+ 'Sometimes, you might want to force the generation of a new certificate, ' +
+ 'such as in the cases where you have changed a CA parameter for server certificates, like bit size or digest hash. ' +
+ 'In this case, use --force. If <node-filter> is empty, this command will apply to all nodes.'
+ cert.arg_name 'FILTER'
+ cert.command :update do |update|
+ update.switch 'force', :desc => 'Always generate new certificates', :negatable => false
+ update.action do |global_options,options,args|
+ update_certificates(manager.filter!(args), options)
+ end
+ end
+
+ cert.desc 'Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.' # (needed for server-side of some TLS connections)
+ cert.command :dh do |dh|
+ dh.action do |global_options,options,args|
+ long_running do
+ if cmd_exists?('certtool')
+ log 0, 'Generating DH parameters (takes a long time)...'
+ output = assert_run!('certtool --generate-dh-params --sec-param high')
+ output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1'
+ output << "\n"
+ write_file!(:dh_params, output)
+ else
+ log 0, 'Generating DH parameters (takes a REALLY long time)...'
+ output = OpenSSL::PKey::DH.generate(3248).to_pem
+ write_file!(:dh_params, output)
+ end
+ end
+ end
+ end
+
+ #
+ # hints:
+ #
+ # inspect CSR:
+ # openssl req -noout -text -in files/cert/x.csr
+ #
+ # generate CSR with openssl to see how it compares:
+ # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
+ #
+ # validate a CSR:
+ # http://certlogik.com/decoder/
+ #
+ # nice details about CSRs:
+ # http://www.redkestrel.co.uk/Articles/CSR.html
+ #
+ cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
+ cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
+ "The properties used for this CSR come from `provider.ca.server_certificates`, "+
+ "but may be overridden here."
+ cert.command :csr do |csr|
+ csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
+ csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
+ csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name."
+ csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name."
+ csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name."
+ csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name."
+ csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name."
+ csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
+ csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
+ csr.action do |global_options,options,args|
+ assert_config! 'provider.domain'
+ assert_config! 'provider.name'
+ assert_config! 'provider.default_language'
+ assert_config! 'provider.ca.server_certificates.bit_size'
+ assert_config! 'provider.ca.server_certificates.digest'
+ domain = options[:domain] || provider.domain
+
+ unless global_options[:force]
+ assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain],
+ :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
+ end
+
+ server_certificates = provider.ca.server_certificates
+
+ # RSA key
+ keypair = CertificateAuthority::MemoryKeyMaterial.new
+ bit_size = (options[:bits] || server_certificates.bit_size).to_i
+ log :generating, "%s bit RSA key" % bit_size do
+ keypair.generate_key(bit_size)
+ write_file! [:commercial_key, domain], keypair.private_key.to_pem
+ end
+
+ # CSR
+ dn = CertificateAuthority::DistinguishedName.new
+ dn.common_name = domain
+ dn.organization = options[:organization] || provider.name[provider.default_language]
+ dn.ou = options[:organizational_unit] # optional
+ dn.email_address = options[:email] # optional
+ dn.country = options[:country] || server_certificates['country'] # optional
+ dn.state = options[:state] || server_certificates['state'] # optional
+ dn.locality = options[:locality] || server_certificates['locality'] # optional
+
+ digest = options[:digest] || server_certificates.digest
+ log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
+ csr = create_csr(dn, keypair, digest)
+ request = csr.to_x509_csr
+ write_file! [:commercial_csr, domain], csr.to_pem
+ end
+
+ # Sign using our own CA, for use in testing but hopefully not production.
+ # It is not that commerical CAs are so secure, it is just that signing your own certs is
+ # a total drag for the user because they must click through dire warnings.
+ #if options[:sign]
+ log :generating, "self-signed x509 server certificate for testing purposes" do
+ cert = csr.to_cert
+ cert.serial_number.number = cert_serial_number(domain)
+ cert.not_before = yesterday
+ cert.not_after = yesterday.advance(:years => 1)
+ cert.parent = ca_root
+ cert.sign! domain_test_signing_profile
+ write_file! [:commercial_cert, domain], cert.to_pem
+ log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, domain])}"
+ end
+ #end
+
+ # FAKE CA
+ unless file_exists? :commercial_ca_cert
+ log :using, "generated CA in place of commercial CA for testing purposes" do
+ write_file! :commercial_ca_cert, read_file!(:ca_cert)
+ log "please also replace this file with the CA cert from the commercial authority you use."
+ end
+ end
+ end
+ end
+ end
+
+ protected
+
+ #
+ # will generate new certificates for the specified nodes, if needed.
+ #
+ def update_certificates(nodes, options={})
+ assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them'
+ assert_config! 'provider.ca.server_certificates.bit_size'
+ assert_config! 'provider.ca.server_certificates.digest'
+ assert_config! 'provider.ca.server_certificates.life_span'
+ assert_config! 'common.x509.use'
+
+ nodes.each_node do |node|
+ warn_if_commercial_cert_will_soon_expire(node)
+ if !node.x509.use
+ remove_file!([:node_x509_key, node.name])
+ remove_file!([:node_x509_cert, node.name])
+ elsif options[:force] || cert_needs_updating?(node)
+ generate_cert_for_node(node)
+ end
+ end
+ end
+
+ private
+
+ def generate_new_certificate_authority(key_file, cert_file, common_name)
+ assert_files_missing! key_file, cert_file
+ assert_config! 'provider.ca.name'
+ assert_config! 'provider.ca.bit_size'
+ assert_config! 'provider.ca.life_span'
+
+ root = CertificateAuthority::Certificate.new
+
+ # set subject
+ root.subject.common_name = common_name
+ possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address']
+ provider.ca.keys.each do |key|
+ if possible.include?(key)
+ root.subject.send(key + '=', provider.ca[key])
+ end
+ end
+
+ # set expiration
+ root.not_before = yesterday
+ root.not_after = yesterday_advance(provider.ca.life_span)
+
+ # generate private key
+ root.serial_number.number = 1
+ root.key_material.generate_key(provider.ca.bit_size)
+
+ # sign self
+ root.signing_entity = true
+ root.parent = root
+ root.sign!(ca_root_signing_profile)
+
+ # save
+ write_file!(key_file, root.key_material.private_key.to_pem)
+ write_file!(cert_file, root.to_pem)
+ end
+
+ #
+ # returns true if the certs associated with +node+ need to be regenerated.
+ #
+ def cert_needs_updating?(node)
+ if !file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name])
+ return true
+ else
+ cert = load_certificate_file([:node_x509_cert, node.name])
+ if !created_by_authority?(cert, ca_root)
+ log :updating, "cert for node '#{node.name}' because it was signed by an old CA root cert."
+ return true
+ end
+ if cert.not_after < Time.now.advance(:months => 2)
+ log :updating, "cert for node '#{node.name}' because it will expire soon"
+ return true
+ end
+ if cert.subject.common_name != node.domain.full
+ log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})"
+ return true
+ end
+ cert.openssl_body.extensions.each do |ext|
+ if ext.oid == "subjectAltName"
+ ips = []
+ dns_names = []
+ ext.value.split(",").each do |value|
+ value.strip!
+ ips << $1 if value =~ /^IP Address:(.*)$/
+ dns_names << $1 if value =~ /^DNS:(.*)$/
+ end
+ dns_names.sort!
+ if ips.first != node.ip_address
+ log :updating, "cert for node '#{node.name}' because ip_address has changed (from #{ips.first} to #{node.ip_address})"
+ return true
+ elsif dns_names != dns_names_for_node(node)
+ log :updating, "cert for node '#{node.name}' because domain name aliases have changed\n from: #{dns_names.inspect}\n to: #{dns_names_for_node(node).inspect})"
+ return true
+ end
+ end
+ end
+ end
+ return false
+ end
+
+ def created_by_authority?(cert, ca)
+ authority_key_id = cert.extensions["authorityKeyIdentifier"].identifier.sub(/^keyid:/, '')
+ authority_key_id == public_key_id_for_ca(ca)
+ end
+
+ # calculate the "key id" for a root CA, that matches the value
+ # Authority Key Identifier in the x509 extensions of a cert.
+ def public_key_id_for_ca(ca_cert)
+ @ca_key_ids ||= {}
+ @ca_key_ids[ca_cert.object_id] ||= begin
+ pubkey = ca_cert.key_material.public_key
+ seq = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer.new(pubkey.n),
+ OpenSSL::ASN1::Integer.new(pubkey.e)
+ ])
+ Digest::SHA1.hexdigest(seq.to_der).upcase.scan(/../).join(':')
+ end
+ end
+
+ def warn_if_commercial_cert_will_soon_expire(node)
+ dns_names_for_node(node).each do |domain|
+ if file_exists?([:commercial_cert, domain])
+ cert = load_certificate_file([:commercial_cert, domain])
+ path = Path.relative_path([:commercial_cert, domain])
+ if cert.not_after < Time.now.utc
+ log :error, "the commercial certificate '#{path}' has EXPIRED! " +
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ elsif cert.not_after < Time.now.advance(:months => 2)
+ log :warning, "the commercial certificate '#{path}' will expire soon (#{cert.not_after}). "+
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ end
+ end
+ end
+ end
+
+ def generate_cert_for_node(node)
+ return if node.x509.use == false
+
+ cert = CertificateAuthority::Certificate.new
+
+ # set subject
+ cert.subject.common_name = node.domain.full
+ cert.serial_number.number = cert_serial_number(node.domain.full)
+
+ # set expiration
+ cert.not_before = yesterday
+ cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span)
+
+ # generate key
+ cert.key_material.generate_key(provider.ca.server_certificates.bit_size)
+
+ # sign
+ cert.parent = ca_root
+ cert.sign!(server_signing_profile(node))
+
+ # save
+ write_file!([:node_x509_key, node.name], cert.key_material.private_key.to_pem)
+ write_file!([:node_x509_cert, node.name], cert.to_pem)
+ end
+
+ #
+ # yields client key and cert suitable for testing
+ #
+ def generate_test_client_cert(prefix=nil)
+ cert = CertificateAuthority::Certificate.new
+ cert.serial_number.number = cert_serial_number(provider.domain)
+ cert.subject.common_name = [prefix, random_common_name(provider.domain)].join
+ cert.not_before = yesterday
+ cert.not_after = yesterday.advance(:years => 1)
+ cert.key_material.generate_key(1024) # just for testing, remember!
+ cert.parent = client_ca_root
+ cert.sign! client_test_signing_profile
+ yield cert.key_material.private_key.to_pem, cert.to_pem
+ end
+
+ #
+ # creates a CSR and returns it.
+ # with the correct extReq attribute so that the CA
+ # doens't generate certs with extensions we don't want.
+ #
+ def create_csr(dn, keypair, digest)
+ csr = CertificateAuthority::SigningRequest.new
+ csr.distinguished_name = dn
+ csr.key_material = keypair
+ csr.digest = digest
+
+ # define extensions manually (library doesn't support setting these on CSRs)
+ extensions = []
+ extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
+ basic.ca = false
+ }
+ extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
+ keyusage.usage = ["digitalSignature", "keyEncipherment"]
+ }
+ extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
+ extkeyusage.usage = [ "serverAuth"]
+ }
+
+ # convert extensions to attribute 'extReq'
+ # aka "Requested Extensions"
+ factory = OpenSSL::X509::ExtensionFactory.new
+ attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
+ extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
+ )])
+ attrs = [
+ OpenSSL::X509::Attribute.new("extReq", attrval),
+ ]
+ csr.attributes = attrs
+
+ return csr
+ end
+
+ def ca_root
+ @ca_root ||= begin
+ load_certificate_file(:ca_cert, :ca_key)
+ end
+ end
+
+ def client_ca_root
+ @client_ca_root ||= begin
+ load_certificate_file(:client_ca_cert, :client_ca_key)
+ end
+ end
+
+ def load_certificate_file(crt_file, key_file=nil, password=nil)
+ crt = read_file!(crt_file)
+ openssl_cert = OpenSSL::X509::Certificate.new(crt)
+ cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
+ if key_file
+ key = read_file!(key_file)
+ cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, password)
+ end
+ return cert
+ end
+
+ def ca_root_signing_profile
+ {
+ "extensions" => {
+ "basicConstraints" => {"ca" => true},
+ "keyUsage" => {
+ "usage" => ["critical", "keyCertSign"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => []
+ }
+ }
+ }
+ end
+
+ #
+ # For keyusage, openvpn server certs can have keyEncipherment or keyAgreement.
+ # Web browsers seem to break without keyEncipherment.
+ # For now, I am using digitalSignature + keyEncipherment
+ #
+ # * digitalSignature -- for (EC)DHE cipher suites
+ # "The digitalSignature bit is asserted when the subject public key is used
+ # with a digital signature mechanism to support security services other
+ # than certificate signing (bit 5), or CRL signing (bit 6). Digital
+ # signature mechanisms are often used for entity authentication and data
+ # origin authentication with integrity."
+ #
+ # * keyEncipherment ==> for plain RSA cipher suites
+ # "The keyEncipherment bit is asserted when the subject public key is used for
+ # key transport. For example, when an RSA key is to be used for key management,
+ # then this bit is set."
+ #
+ # * keyAgreement ==> for used with DH, not RSA.
+ # "The keyAgreement bit is asserted when the subject public key is used for key
+ # agreement. For example, when a Diffie-Hellman key is to be used for key
+ # management, then this bit is set."
+ #
+ # digest options: SHA512, SHA256, SHA1
+ #
+ def server_signing_profile(node)
+ {
+ "digest" => provider.ca.server_certificates.digest,
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature", "keyEncipherment"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["serverAuth", "clientAuth"]
+ },
+ "subjectAltName" => {
+ "ips" => [node.ip_address],
+ "dns_names" => dns_names_for_node(node)
+ }
+ }
+ }
+ end
+
+ #
+ # This is used when signing the main cert for the provider's domain
+ # with our own CA (for testing purposes). Typically, this cert would
+ # be purchased from a commercial CA, and not signed this way.
+ #
+ def domain_test_signing_profile
+ {
+ "digest" => "SHA256",
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature", "keyEncipherment"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["serverAuth"]
+ }
+ }
+ }
+ end
+
+ #
+ # This is used when signing a dummy client certificate that is only to be
+ # used for testing.
+ #
+ def client_test_signing_profile
+ {
+ "digest" => "SHA256",
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["clientAuth"]
+ }
+ }
+ }
+ end
+
+ def dns_names_for_node(node)
+ names = [node.domain.internal, node.domain.full]
+ if node['dns'] && node.dns['aliases'] && node.dns.aliases.any?
+ names += node.dns.aliases
+ end
+ names.compact!
+ names.sort!
+ names.uniq!
+ return names
+ end
+
+ #
+ # For cert serial numbers, we need a non-colliding number less than 160 bits.
+ # md5 will do nicely, since there is no need for a secure hash, just a short one.
+ # (md5 is 128 bits)
+ #
+ def cert_serial_number(domain_name)
+ Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16)
+ end
+
+ #
+ # for the random common name, we need a text string that will be unique across all certs.
+ # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid
+ #
+ def random_common_name(domain_name)
+ cert_serial_number(domain_name).to_s(36)
+ end
+
+ # prints CertificateAuthority::DistinguishedName fields
+ def print_dn(dn)
+ fields = {}
+ [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
+ fields[attr] = dn.send(attr) if dn.send(attr)
+ end
+ fields.inspect
+ end
+
+ ##
+ ## TIME HELPERS
+ ##
+ ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet
+ ## are behind UTC.
+ ##
+
+ def yesterday
+ t = Time.now - 24*24*60
+ Time.utc t.year, t.month, t.day
+ end
+
+ def yesterday_advance(string)
+ number, unit = string.split(' ')
+ unless ['years', 'months', 'days', 'hours', 'minutes'].include? unit
+ bail!("The time property '#{string}' is missing a unit (one of: years, months, days, hours, minutes).")
+ end
+ unless number.to_i.to_s == number
+ bail!("The time property '#{string}' is missing a number.")
+ end
+ yesterday.advance(unit.to_sym => number.to_i)
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/clean.rb b/lib/leap_cli/commands/clean.rb
new file mode 100644
index 00000000..a9afff53
--- /dev/null
+++ b/lib/leap_cli/commands/clean.rb
@@ -0,0 +1,16 @@
+module LeapCli
+ module Commands
+
+ desc 'Removes all files generated with the "compile" command.'
+ command :clean do |c|
+ c.action do |global_options,options,args|
+ Dir.glob(path([:hiera, '*'])).each do |file|
+ remove_file! file
+ end
+ remove_file! path(:authorized_keys)
+ remove_file! path(:known_hosts)
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
new file mode 100644
index 00000000..f9079279
--- /dev/null
+++ b/lib/leap_cli/commands/compile.rb
@@ -0,0 +1,531 @@
+require 'socket'
+
+module LeapCli
+ module Commands
+
+ desc "Compile generated files."
+ command [:compile, :c] do |c|
+ c.desc 'Compiles node configuration files into hiera files used for deployment.'
+ c.arg_name 'ENVIRONMENT', :optional => true
+ c.command :all do |all|
+ all.action do |global_options,options,args|
+ environment = args.first
+ compile_command(environment)
+ end
+ end
+
+ c.desc "Prints a DNS zone file for your provider."
+ c.command :zone do |zone|
+ zone.action do |global_options, options, args|
+ compile_command(nil)
+ compile_zone_file(global_options[:yes] || global_options[:force])
+ end
+ end
+
+ c.desc "Print entries suitable for an /etc/hosts file, useful for testing your provider."
+ c.command :hosts do |hosts|
+ hosts.action do |global_options, options, args|
+ compile_command(nil)
+ compile_hosts_file
+ end
+ end
+
+ c.desc "Compile provider.json bootstrap files for your provider."
+ c.command 'provider.json' do |provider|
+ provider.action do |global_options, options, args|
+ compile_command(nil)
+ compile_provider_json
+ end
+ end
+
+ c.desc "Prints a list of firewall rules. These rules are already "+
+ "implemented on each node, but you might want the list of all "+
+ "rules in case you also have a restrictive network firewall."
+ c.command :firewall do |zone|
+ zone.action do |global_options, options, args|
+ compile_command(nil)
+ compile_firewall
+ end
+ end
+
+ c.default_command :all
+ end
+
+ protected
+
+ def compile_command(environment)
+ if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment
+ bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned."
+ end
+ if environment
+ if manager.environment_names.include?(environment)
+ compile_hiera_files(manager.filter([environment]), false)
+ else
+ bail! "There is no environment named `#{environment}`."
+ end
+ else
+ clean_export = LeapCli.leapfile.environment.nil?
+ compile_hiera_files(manager.filter, clean_export)
+ end
+ if file_exists?(:static_web_readme)
+ compile_provider_json(environment)
+ end
+ end
+
+ #
+ # a "clean" export of secrets will also remove keys that are no longer used,
+ # but this should not be done if we are not examining all possible nodes.
+ #
+ def compile_hiera_files(nodes, clean_export)
+ update_certificates(nodes) # \ must come first so that output will
+ update_compiled_ssh_configs # / get included in compiled hiera files.
+ sanity_check(nodes)
+ manager.export_nodes(nodes)
+ manager.export_secrets(clean_export)
+ end
+
+ def update_compiled_ssh_configs
+ generate_monitor_ssh_keys
+ update_authorized_keys
+ update_known_hosts
+ end
+
+ def sanity_check(nodes)
+ # confirm that every node has a unique ip address
+ ips = {}
+ nodes.pick_fields('ip_address').each do |name, ip_address|
+ if ips.key?(ip_address)
+ bail! {
+ log(:fatal_error, "Every node must have its own IP address.") {
+ log "Nodes `#{name}` and `#{ips[ip_address]}` are both configured with `#{ip_address}`."
+ }
+ }
+ else
+ ips[ip_address] = name
+ end
+ end
+ # confirm that the IP address of this machine is not also used for a node.
+ Socket.ip_address_list.each do |addrinfo|
+ if !addrinfo.ipv4_private? && ips.key?(addrinfo.ip_address)
+ ip = addrinfo.ip_address
+ name = ips[ip]
+ bail! {
+ log(:fatal_error, "Something is very wrong. The `leap` command must only be run on your sysadmin machine, not on a provider node.") {
+ log "This machine has the same IP address (#{ip}) as node `#{name}`."
+ }
+ }
+ end
+ end
+ end
+
+ ##
+ ## SSH
+ ##
+
+ #
+ # generates a ssh key pair that is used only by remote monitors
+ # to connect to nodes and run certain allowed commands.
+ #
+ # every node has the public monitor key added to their authorized
+ # keys, and every monitor node has a copy of the private monitor key.
+ #
+ def generate_monitor_ssh_keys
+ priv_key_file = path(:monitor_priv_key)
+ pub_key_file = path(:monitor_pub_key)
+ unless file_exists?(priv_key_file, pub_key_file)
+ ensure_dir(File.dirname(priv_key_file))
+ ensure_dir(File.dirname(pub_key_file))
+ cmd = %(ssh-keygen -N '' -C 'monitor' -t rsa -b 4096 -f '%s') % priv_key_file
+ assert_run! cmd
+ if file_exists?(priv_key_file, pub_key_file)
+ log :created, priv_key_file
+ log :created, pub_key_file
+ else
+ log :failed, 'to create monitor ssh keys'
+ end
+ end
+ end
+
+ #
+ # Compiles the authorized keys file, which gets installed on every during init.
+ # Afterwards, puppet installs an authorized keys file that is generated differently
+ # (see authorized_keys() in macros.rb)
+ #
+ def update_authorized_keys
+ buffer = StringIO.new
+ keys = Dir.glob(path([:user_ssh, '*']))
+ if keys.empty?
+ bail! "You must have at least one public SSH user key configured in order to proceed. See `leap help add-user`."
+ end
+ if file_exists?(path(:monitor_pub_key))
+ keys << path(:monitor_pub_key)
+ end
+ keys.sort.each do |keyfile|
+ ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
+ buffer << ssh_type
+ buffer << " "
+ buffer << ssh_key
+ buffer << " "
+ buffer << Path.relative_path(keyfile)
+ buffer << "\n"
+ end
+ write_file!(:authorized_keys, buffer.string)
+ end
+
+ #
+ # generates the known_hosts file.
+ #
+ # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
+ # for the possibility that the hostnames or ip has changed in the node configuration.
+ #
+ def update_known_hosts
+ buffer = StringIO.new
+ buffer << "#\n"
+ buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n"
+ buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n"
+ buffer << "#\n"
+ manager.nodes.keys.sort.each do |node_name|
+ node = manager.nodes[node_name]
+ hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
+ pub_key = read_file([:node_ssh_pub_key,node.name])
+ if pub_key
+ buffer << [hostnames, pub_key].join(' ')
+ buffer << "\n"
+ end
+ end
+ write_file!(:known_hosts, buffer.string)
+ end
+
+ ##
+ ## provider.json
+ ##
+
+ #
+ # generates static provider.json files that can put into place
+ # (e.g. https://domain/provider.json) for the cases where the
+ # webapp domain does not match the provider's domain.
+ #
+ def compile_provider_json(environments=nil)
+ webapp_nodes = manager.nodes[:services => 'webapp']
+ write_file!(:static_web_readme, STATIC_WEB_README)
+ environments ||= manager.environment_names
+ environments.each do |env|
+ node = webapp_nodes[:environment => env].values.first
+ if node
+ env ||= 'default'
+ write_file!(
+ [:static_web_provider_json, env],
+ node['definition_files']['provider']
+ )
+ write_file!(
+ [:static_web_htaccess, env],
+ HTACCESS_FILE % {:min_version => manager.env(env).provider.client_version['min']}
+ )
+ end
+ end
+ end
+
+ HTACCESS_FILE = %[
+<Files provider.json>
+ Header set X-Minimum-Client-Version %{min_version}
+</Files>
+]
+
+ STATIC_WEB_README = %[
+This directory contains statically rendered copies of the `provider.json` file
+used by the client to "bootstrap" configure itself for use with your service
+provider.
+
+There is a separate provider.json file for each environment, although you
+should only need 'production/provider.json' or, if you have no environments
+configured, 'default/provider.json'.
+
+To clarify, this is the public `provider.json` file used by the client, not the
+`provider.json` file that is used to configure the provider.
+
+The provider.json file must be available at `https://domain/provider.json`
+(unless this provider is included in the list of providers which are pre-
+seeded in client).
+
+This provider.json file can be served correctly in one of three ways:
+
+(1) If the property webapp.domain is not configured, then the web app will be
+ installed at https://domain/ and it will handle serving the provider.json file.
+
+(2) If one or more nodes have the 'static' service configured for the provider's
+ domain, then these 'static' nodes will correctly serve provider.json.
+
+(3) Otherwise, you must copy the provider.json file to your web
+ server and make it available at '/provider.json'. The example htaccess
+ file shows what header options should be sent by the web server
+ with the response.
+
+This directory is needed for method (3), but not for methods (1) or (2).
+
+This directory has been created by the command `leap compile provider.json`.
+Once created, it will be kept up to date everytime you compile. You may safely
+remove this directory if you don't use it.
+]
+
+ ##
+ ##
+ ## ZONE FILE
+ ##
+
+ def relative_hostname(fqdn, provider)
+ @domain_regexp ||= /\.?#{Regexp.escape(provider.domain)}$/
+ fqdn.sub(@domain_regexp, '')
+ end
+
+ #
+ # serial is any number less than 2^32 (4294967296)
+ #
+ def compile_zone_file(force=false)
+ # note: we use the default provider for all nodes, because we use it
+ # to generate hostnames that are relative to the default domain.
+ provider = manager.env('default').provider
+ hosts_seen = {}
+ lines = []
+
+ #
+ # header
+ #
+ lines << ZONE_HEADER % {
+ :domain => provider.domain,
+ :ns => provider.domain,
+ :contact => provider.contacts.default.first.sub('@','.'),
+ :serial => generate_zone_serial
+ }
+
+ #
+ # common records
+ #
+ lines << ORIGIN_HEADER
+ # 'A' records for primary domain
+ manager.nodes[:environment => '!local'].each_node do |node|
+ if node.dns['aliases'] && node.dns.aliases.include?(provider.domain)
+ lines << ["@", "IN A #{node.ip_address}"]
+ end
+ end
+
+ # NS records
+ if provider['dns'] && provider.dns['nameservers']
+ unless provider.dns.nameservers.is_a?(Array)
+ # TODO: remove me once we have JSON schema working
+ bail! {log :error, 'dns.nameservers must be an array' }
+ end
+ provider.dns.nameservers.each do |ns|
+ lines << ["@", "IN NS #{ns}."]
+ end
+ elsif !force
+ log :warning, "Property dns.nameservers is not configured in provider.json." do
+ log "This will produce a zone file without any NS records."
+ log "Use --force to skip this warning."
+ end
+ return unless agree("Continue? ")
+ end
+
+ # environment records
+ manager.environment_names.each do |env|
+ next if env == 'local'
+ nodes = manager.nodes[:environment => env]
+ next unless nodes.any?
+ spf = nil
+ dkim = nil
+ lines << ENV_HEADER % (env.nil? ? 'default' : env)
+ nodes.each_node do |node|
+ if node.dns.public
+ lines << [relative_hostname(node.domain.full, provider), "IN A #{node.ip_address}"]
+ end
+ if node.dns['aliases']
+ node.dns.aliases.each do |host_alias|
+ if host_alias != node.domain.full && host_alias != provider.domain
+ lines << [relative_hostname(host_alias, provider), "IN A #{node.ip_address}"]
+ end
+ end
+ end
+ if node.services.include? 'mx'
+ mx_domain = relative_hostname(node.domain.full_suffix, provider)
+ lines << [mx_domain, "IN MX 10 #{relative_hostname(node.domain.full, provider)}"]
+ spf ||= [mx_domain, spf_record(node)]
+ dkim ||= dkim_record(node, provider)
+ end
+ end
+ lines << spf if spf
+ lines << dkim if dkim
+ end
+
+ # print the lines
+ max_width = lines.inject(0) {|max, line| line.is_a?(Array) ? [max, line[0].length].max : max}
+ max_width = [max_width, 24].min
+ lines.each do |host, line|
+ if line.nil?
+ puts(host)
+ else
+ host = '@' if host == ''
+ puts("%-#{max_width}s %s" % [host, line])
+ end
+ end
+ end
+
+ #
+ # outputs entries suitable for an /etc/hosts file
+ #
+ def compile_hosts_file
+ manager.environment_names.each do |env|
+ nodes = manager.nodes[:environment => env]
+ next unless nodes.any?
+ puts
+ puts "## environment '#{env || 'default'}'"
+ nodes.each do |name, node|
+ puts "%s %s" % [
+ node.ip_address,
+ [name, node.get('domain.full'), node.get('dns.aliases')].compact.join(' ')
+ ]
+ end
+ end
+ end
+
+ private
+
+ #
+ # allow mail from any mx node, plus the webapp nodes.
+ #
+ # TODO: ipv6
+ #
+ def spf_record(node)
+ ips = node.nodes_like_me['services' => 'webapp'].values.collect {|n|
+ "ip4:" + n.ip_address
+ }
+ # TXT strings may not be longer than 255 characters, although
+ # you can chain multiple strings together.
+ strings = "v=spf1 MX #{ips.join(' ')} -all".scan(/.{1,255}/).join('" "')
+ %(IN TXT "#{strings}")
+ end
+
+ #
+ # for example:
+ #
+ # selector._domainkey IN TXT "v=DKIM1;h=sha256;k=rsa;s=email;p=MIGfMA0GCSq...GSIb3DQ"
+ #
+ # specification: http://dkim.org/specs/rfc4871-dkimbase.html#rfc.section.7.4
+ #
+ def dkim_record(node, provider)
+ # PEM encoded public key (base64), without the ---PUBLIC KEY--- armor parts.
+ assert_files_exist! :dkim_pub_key
+ dkim_pub_key = Path.named_path(:dkim_pub_key)
+ public_key = File.readlines(dkim_pub_key).grep(/^[^\-]+/).join
+
+ host = relative_hostname(
+ node.mx.dkim.selector + "._domainkey." + node.domain.full_suffix,
+ provider)
+
+ attrs = [
+ "v=DKIM1",
+ "h=sha256",
+ "k=rsa",
+ "s=email",
+ "p=" + public_key
+ ]
+
+ return [host, "IN TXT " + txt_wrap(attrs.join(';'))]
+ end
+
+ #
+ # DNS TXT records cannot be longer than 255 characters.
+ #
+ # However, multiple responses will be concatenated together.
+ # It looks like this:
+ #
+ # IN TXT "v=spf1 .... first" "second string..."
+ #
+ def txt_wrap(str)
+ '"' + str.scan(/.{1,255}/).join('" "') + '"'
+ end
+
+ #
+ # For zone serial number, we want something that will be
+ # different each time you deploy but also will be greater
+ # than any prior likely serial that was prefixed by the
+ # year, such as 2016040600.
+ #
+ # so, we use time_t of right now, modified with first
+ # digit incremented by one.
+ #
+ # this will work until Time.at(2**32 - 1_000_000_000)
+ # aka 2074-05-31 04:41:36 UTC.
+ #
+ def generate_zone_serial
+ Time.now.utc.to_i + 1_000_000_000
+ end
+
+ ENV_HEADER = %[
+;;
+;; ENVIRONMENT %s
+;;
+
+]
+
+ ZONE_HEADER = %[
+;;
+;; BIND data file for %{domain}
+;;
+
+$TTL 600
+$ORIGIN %{domain}.
+
+@ IN SOA %{ns}. %{contact}. (
+ %{serial} ; serial
+ 7200 ; refresh ( 24 hours)
+ 3600 ; retry ( 2 hours)
+ 1209600 ; expire (1000 hours)
+ 600 ) ; minimum ( 2 days)
+;
+]
+
+ ORIGIN_HEADER = %[
+;;
+;; ZONE ORIGIN
+;;
+
+]
+
+ ##
+ ## FIREWALL
+ ##
+
+ public
+
+ def compile_firewall
+ manager.nodes.each_node(&:evaluate)
+
+ rules = [["ALLOW TO", "PORTS", "ALLOW FROM"]]
+ manager.nodes[:environment => '!local'].values.each do |node|
+ next unless node['firewall']
+ node.firewall.each do |name, rule|
+ if rule.is_a? Hash
+ rules << add_rule(rule)
+ elsif rule.is_a? Array
+ rule.each do |r|
+ rules << add_rule(r)
+ end
+ end
+ end
+ end
+
+ max_to = rules.inject(0) {|max, r| [max, r[0].length].max}
+ max_port = rules.inject(0) {|max, r| [max, r[1].length].max}
+ max_from = rules.inject(0) {|max, r| [max, r[2].length].max}
+ rules.each do |rule|
+ puts "%-#{max_to}s %-#{max_port}s %-#{max_from}s" % rule
+ end
+ end
+
+ private
+
+ def add_rule(rule)
+ [rule["to"], [rule["port"]].compact.join(','), rule["from"]]
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/db.rb b/lib/leap_cli/commands/db.rb
new file mode 100644
index 00000000..5307ac4d
--- /dev/null
+++ b/lib/leap_cli/commands/db.rb
@@ -0,0 +1,86 @@
+module LeapCli; module Commands
+
+ desc 'Database commands.'
+ command :db do |db|
+ db.desc 'Destroy one or more databases. If present, limit to FILTER nodes. For example `leap db destroy --db sessions,tokens testing`.'
+ db.arg_name 'FILTER', :optional => true
+ db.command :destroy do |destroy|
+ destroy.flag :db, :arg_name => "DATABASES", :desc => 'Comma separated list of databases to destroy (no space). Use "--db all" to destroy all databases.', :optional => true
+ destroy.flag :user, :arg_name => "USERS", :desc => 'Comma separated list of usernames. The storage databases for these user(s) will be destroyed.', :optional => true
+ destroy.action do |global_options,options,args|
+ dbs = (options[:db]||"").split(',')
+ users = (options[:user]||"").split(',')
+ if dbs.empty? && users.empty?
+ bail!('Either --db or --user is required.')
+ end
+ nodes = manager.filter(args)
+ if nodes.any?
+ nodes = nodes[:services => 'couchdb']
+ end
+ unless nodes.any?
+ bail! 'No db nodes selected.'
+ end
+ if users.any?
+ unless global_options[:yes]
+ say 'You are about to permanently destroy user databases for [%s] for nodes [%s].' % [users.join(', '), nodes.keys.join(', ')]
+ bail! unless agree("Continue? ")
+ end
+ destroy_user_dbs(nodes, users)
+ elsif dbs.any?
+ unless global_options[:yes]
+ if dbs.include?('all')
+ say 'You are about to permanently destroy all database data for nodes [%s].' % nodes.keys.join(', ')
+ else
+ say 'You are about to permanently destroy databases [%s] for nodes [%s].' % [dbs.join(', '), nodes.keys.join(', ')]
+ end
+ bail! unless agree("Continue? ")
+ end
+ if dbs.include?('all')
+ destroy_all_dbs(nodes)
+ else
+ destroy_dbs(nodes, dbs)
+ end
+ say 'You must run `leap deploy` in order to create the databases again.'
+ end
+ end
+ end
+ end
+
+ private
+
+ def destroy_all_dbs(nodes)
+ ssh_connect(nodes) do |ssh|
+ ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "All DBs destroyed" || echo "DBs already destroyed"')
+ end
+ end
+
+ def destroy_dbs(nodes, dbs)
+ nodes.each_node do |node|
+ ssh_connect(node) do |ssh|
+ dbs.each do |db|
+ ssh.run(DESTROY_DB_COMMAND % {:db => db})
+ end
+ end
+ end
+ end
+
+ def destroy_user_dbs(nodes, users)
+ nodes.each_node do |node|
+ ssh_connect(node) do |ssh|
+ users.each do |user|
+ ssh.run(DESTROY_USER_DB_COMMAND % {:user => user})
+ end
+ end
+ end
+ end
+
+ DESTROY_DB_COMMAND = %{
+if [ 200 = `curl -ns -w "%%{http_code}" -X GET "127.0.0.1:5984/%{db}" -o /dev/null` ]; then
+ echo "Result from DELETE /%{db}:" `curl -ns -X DELETE "127.0.0.1:5984/%{db}"`;
+else
+ echo "Skipping db '%{db}': it does not exist or has already been deleted.";
+fi
+}
+
+ DESTROY_USER_DB_COMMAND = %{/srv/leap/couchdb/scripts/destroy-user-db --username %{user}}
+end; end
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
new file mode 100644
index 00000000..9dd190ab
--- /dev/null
+++ b/lib/leap_cli/commands/deploy.rb
@@ -0,0 +1,374 @@
+require 'etc'
+
+module LeapCli
+ module Commands
+
+ desc 'Apply recipes to a node or set of nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:deploy, :d] do |c|
+
+ c.switch :fast, :desc => 'Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.',
+ :negatable => false
+
+ c.switch :sync, :desc => "Sync files, but don't actually apply recipes.", :negatable => false
+
+ c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
+
+ c.switch :downgrade, :desc => 'Allows deploy to run with an older platform version.', :negatable => false
+
+ c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false
+
+ c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).',
+ :arg_name => 'TAG[,TAG]'
+
+ c.flag :port, :desc => 'Override the default SSH port.',
+ :arg_name => 'PORT'
+
+ c.flag :ip, :desc => 'Override the default SSH IP address.',
+ :arg_name => 'IPADDRESS'
+
+ c.action do |global,options,args|
+
+ if options[:dev] != true
+ init_submodules
+ end
+
+ nodes = manager.filter!(args, :disabled => false)
+ if nodes.size > 1
+ say "Deploying to these nodes: #{nodes.keys.join(', ')}"
+ if !global[:yes] && !agree("Continue? ")
+ quit! "OK. Bye."
+ end
+ end
+
+ environments = nodes.field('environment').uniq
+ if environments.empty?
+ environments = [nil]
+ end
+ environments.each do |env|
+ check_platform_pinning(env, global)
+ end
+
+ # compile hiera files for all the nodes in every environment that is
+ # being deployed and only those environments.
+ compile_hiera_files(manager.filter(environments), false)
+
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.log :checking, 'node' do
+ ssh.leap.check_for_no_deploy
+ ssh.leap.assert_initialized
+ end
+ ssh.leap.log :synching, "configuration files" do
+ sync_hiera_config(ssh)
+ sync_support_files(ssh)
+ end
+ ssh.leap.log :synching, "puppet manifests" do
+ sync_puppet_files(ssh)
+ end
+ unless options[:sync]
+ ssh.leap.log :applying, "puppet" do
+ ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min,
+ :tags => tags(options),
+ :force => options[:force],
+ :info => deploy_info,
+ :downgrade => options[:downgrade]
+ )
+ end
+ end
+ end
+ if !Util.exit_status.nil? && Util.exit_status != 0
+ log :warning, "puppet did not finish successfully."
+ end
+ end
+ end
+
+ desc 'Display recent deployment history for a set of nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:history, :h] do |c|
+ c.flag :port, :desc => 'Override the default SSH port.',
+ :arg_name => 'PORT'
+ c.flag :ip, :desc => 'Override the default SSH IP address.',
+ :arg_name => 'IPADDRESS'
+ c.switch :last, :desc => 'Show last deploy only',
+ :negatable => false
+ c.action do |global,options,args|
+ if options[:last] == true
+ lines = 1
+ else
+ lines = 10
+ end
+ nodes = manager.filter!(args)
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.history(lines)
+ end
+ end
+ end
+
+ private
+
+ def forcible_prompt(forced, msg, prompt)
+ say(msg)
+ if forced
+ log :warning, "continuing anyway because of --force"
+ else
+ say "hint: use --force to skip this prompt."
+ quit!("OK. Bye.") unless agree(prompt)
+ end
+ end
+
+ #
+ # The currently activated provider.json could have loaded some pinning
+ # information for the platform. If this is the case, refuse to deploy
+ # if there is a mismatch.
+ #
+ # For example:
+ #
+ # "platform": {
+ # "branch": "develop"
+ # "version": "1.0..99"
+ # "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD"
+ # }
+ #
+ def check_platform_pinning(environment, global_options)
+ provider = manager.env(environment).provider
+ return unless provider['platform']
+
+ if environment.nil? || environment == 'default'
+ provider_json = 'provider.json'
+ else
+ provider_json = 'provider.' + environment + '.json'
+ end
+
+ # can we have json schema verification already?
+ unless provider.platform.is_a? Hash
+ bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect)
+ end
+
+ # check version
+ if provider.platform['version']
+ if !Leap::Platform.version_in_range?(provider.platform.version)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a version range of '#{provider.platform.version}' "+
+ "by the `platform.version` property in #{provider_json}, but the platform "+
+ "(#{Path.platform}) has version #{Leap::Platform.version}.",
+ "Do you really want to deploy from the wrong version? "
+ )
+ end
+ end
+
+ # check branch
+ if provider.platform['branch']
+ if !is_git_directory?(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a particular branch by the `platform.branch` property "+
+ "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
+ "Do you really want to deploy anyway? "
+ )
+ end
+ unless provider.platform.branch == current_git_branch(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+
+ "in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " +
+ "(for directory '#{Path.platform}')",
+ "Do you really want to deploy from the wrong branch? "
+ )
+ end
+ end
+
+ # check commit
+ if provider.platform['commit']
+ if !is_git_directory?(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a particular commit range by the `platform.commit` property "+
+ "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
+ "Do you really want to deploy anyway? "
+ )
+ end
+ current_commit = current_git_commit(Path.platform)
+ Dir.chdir(Path.platform) do
+ commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'",
+ "The platform is pinned to a particular commit range by the `platform.commit` property "+
+ "in #{provider_json}, but git was not able to find commits in the range specified "+
+ "(#{provider.platform.commit}).")
+ commit_range = commit_range.split("\n")
+ if !commit_range.include?(current_commit) &&
+ provider.platform.commit.split('..').first != current_commit
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned via the `platform.commit` property in #{provider_json} " +
+ "to a commit in the range #{provider.platform.commit}, but the current HEAD " +
+ "(#{current_commit}) is not in that range.",
+ "Do you really want to deploy from the wrong commit? "
+ )
+ end
+ end
+ end
+ end
+
+ def sync_hiera_config(ssh)
+ ssh.rsync.update do |server|
+ node = manager.node(server.host)
+ hiera_file = Path.relative_path([:hiera, node.name])
+ ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
+ {
+ :source => hiera_file,
+ :dest => Leap::Platform.hiera_path,
+ :flags => "-rltp --chmod=u+rX,go-rwx"
+ }
+ end
+ end
+
+ #
+ # sync various support files.
+ #
+ def sync_support_files(ssh)
+ dest_dir = Leap::Platform.files_dir
+ custom_files = build_custom_file_list
+ ssh.rsync.update do |server|
+ node = manager.node(server.host)
+ files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
+ files_to_sync += custom_files
+ if files_to_sync.any?
+ ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
+ {
+ :chdir => Path.named_path(:files_dir),
+ :source => ".",
+ :dest => dest_dir,
+ :excludes => "*",
+ :includes => calculate_includes_from_files(files_to_sync, '/files'),
+ :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
+ }
+ else
+ nil
+ end
+ end
+ end
+
+ def sync_puppet_files(ssh)
+ ssh.rsync.update do |server|
+ ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
+ {
+ :dest => Leap::Platform.leap_dir,
+ :source => '.',
+ :chdir => Path.platform,
+ :excludes => '*',
+ :includes => ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**'],
+ :flags => "-rlt --relative --delete --copy-links"
+ }
+ end
+ end
+
+ #
+ # ensure submodules are up to date, if the platform is a git
+ # repository.
+ #
+ def init_submodules
+ return unless is_git_directory?(Path.platform)
+ Dir.chdir Path.platform do
+ assert_run! "git submodule sync"
+ statuses = assert_run! "git submodule status"
+ statuses.strip.split("\n").each do |status_line|
+ if status_line =~ /^[\+-]/
+ submodule = status_line.split(' ')[1]
+ log "Updating submodule #{submodule}"
+ assert_run! "git submodule update --init #{submodule}"
+ end
+ end
+ end
+ end
+
+ #
+ # converts an array of file paths into an array
+ # suitable for --include of rsync
+ #
+ # if set, `prefix` is stripped off.
+ #
+ def calculate_includes_from_files(files, prefix=nil)
+ return nil unless files and files.any?
+
+ # prepend '/' (kind of like ^ for rsync)
+ includes = files.collect {|file| file =~ /^\// ? file : '/' + file }
+
+ # include all sub files of specified directories
+ includes.size.times do |i|
+ if includes[i] =~ /\/$/
+ includes << includes[i] + '**'
+ end
+ end
+
+ # include all parent directories (required because of --exclude '*')
+ includes.size.times do |i|
+ path = File.dirname(includes[i])
+ while(path != '/')
+ includes << path unless includes.include?(path)
+ path = File.dirname(path)
+ end
+ end
+
+ if prefix
+ includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')}
+ end
+
+ return includes
+ end
+
+ def tags(options)
+ if options[:tags]
+ tags = options[:tags].split(',')
+ else
+ tags = Leap::Platform.default_puppet_tags.dup
+ end
+ tags << 'leap_slow' unless options[:fast]
+ tags.join(',')
+ end
+
+ #
+ # a provider might have various customization files that should be sync'ed to the server.
+ # this method builds that list of files to sync.
+ #
+ def build_custom_file_list
+ custom_files = []
+ Leap::Platform.paths.keys.grep(/^custom_/).each do |path|
+ if file_exists?(path)
+ relative_path = Path.relative_path(path, Path.provider)
+ if dir_exists?(path)
+ custom_files << relative_path + '/' # rsync needs trailing slash
+ else
+ custom_files << relative_path
+ end
+ end
+ end
+ return custom_files
+ end
+
+ def deploy_info
+ info = []
+ info << "user: %s" % Etc.getpwuid(Process.euid).name
+ if is_git_directory?(Path.platform) && current_git_branch(Path.platform) != 'master'
+ info << "platform: %s (%s %s)" % [
+ Leap::Platform.version,
+ current_git_branch(Path.platform),
+ current_git_commit(Path.platform)[0..4]
+ ]
+ else
+ info << "platform: %s" % Leap::Platform.version
+ end
+ if is_git_directory?(LEAP_CLI_BASE_DIR)
+ info << "leap_cli: %s (%s %s)" % [
+ LeapCli::VERSION,
+ current_git_branch(LEAP_CLI_BASE_DIR),
+ current_git_commit(LEAP_CLI_BASE_DIR)[0..4]
+ ]
+ else
+ info << "leap_cli: %s" % LeapCli::VERSION
+ end
+ info.join(', ')
+ end
+ end
+end
diff --git a/lib/leap_cli/commands/env.rb b/lib/leap_cli/commands/env.rb
new file mode 100644
index 00000000..80be2174
--- /dev/null
+++ b/lib/leap_cli/commands/env.rb
@@ -0,0 +1,76 @@
+module LeapCli
+ module Commands
+
+ desc "Manipulate and query environment information."
+ long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+
+ "A node in one environment will never interact with a node from another environment. "+
+ "Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+
+ "absolute file path of your provider directory (pins don't apply if you move the directory)"
+ command [:env, :e] do |c|
+ c.desc "List the available environments. The pinned environment, if any, will be marked with '*'. Will also set the pin if run with an environment argument."
+ c.arg_name 'ENVIRONMENT', :optional => true
+ c.command :ls do |ls|
+ ls.action do |global_options, options, args|
+ environment = get_env_from_args(args)
+ if environment
+ pin(environment)
+ LeapCli.leapfile.load
+ end
+ print_envs
+ end
+ end
+
+ c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.'
+ c.arg_name 'ENVIRONMENT'
+ c.command :pin do |pin|
+ pin.action do |global_options,options,args|
+ environment = get_env_from_args(args)
+ if environment
+ pin(environment)
+ else
+ bail! "There is no environment `#{environment}`"
+ end
+ end
+ end
+
+ c.desc "Unpin the environment. All subsequent commands will apply to all nodes."
+ c.command :unpin do |unpin|
+ unpin.action do |global_options, options, args|
+ LeapCli.leapfile.unset('environment')
+ log 0, :saved, "~/.leaprc, removing environment property."
+ end
+ end
+
+ c.default_command :ls
+ end
+
+ protected
+
+ def get_env_from_args(args)
+ environment = args.first
+ if environment == 'default' || (environment && manager.environment_names.include?(environment))
+ return environment
+ else
+ return nil
+ end
+ end
+
+ def pin(environment)
+ LeapCli.leapfile.set('environment', environment)
+ log 0, :saved, "~/.leaprc with environment set to #{environment}."
+ end
+
+ def print_envs
+ envs = ["default"] + manager.environment_names.compact.sort
+ envs.each do |env|
+ if env
+ if LeapCli.leapfile.environment == env
+ puts "* #{env}"
+ else
+ puts " #{env}"
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb
new file mode 100644
index 00000000..11329ccc
--- /dev/null
+++ b/lib/leap_cli/commands/facts.rb
@@ -0,0 +1,100 @@
+#
+# Gather facter facts
+#
+
+module LeapCli; module Commands
+
+ desc 'Gather information on nodes.'
+ command :facts do |facts|
+ facts.desc 'Query servers to update facts.json.'
+ facts.long_desc "Queries every node included in FILTER and saves the important information to facts.json"
+ facts.arg_name 'FILTER'
+ facts.command :update do |update|
+ update.action do |global_options,options,args|
+ update_facts(global_options, options, args)
+ end
+ end
+ end
+
+ protected
+
+ def facter_cmd
+ 'facter --json ' + Leap::Platform.facts.join(' ')
+ end
+
+ def remove_node_facts(name)
+ if file_exists?(:facts)
+ update_facts_file({name => nil})
+ end
+ end
+
+ def update_node_facts(name, facts)
+ update_facts_file({name => facts})
+ end
+
+ def rename_node_facts(old_name, new_name)
+ if file_exists?(:facts)
+ facts = JSON.parse(read_file(:facts) || {})
+ facts[new_name] = facts[old_name]
+ facts[old_name] = nil
+ update_facts_file(facts, true)
+ end
+ end
+
+ #
+ # if overwrite = true, then ignore existing facts.json.
+ #
+ def update_facts_file(new_facts, overwrite=false)
+ replace_file!(:facts) do |content|
+ if overwrite || content.nil? || content.empty?
+ old_facts = {}
+ else
+ old_facts = manager.facts
+ end
+ facts = old_facts.merge(new_facts)
+ facts.each do |name, value|
+ if value.is_a? String
+ if value == ""
+ value = nil
+ else
+ value = JSON.parse(value) rescue JSON::ParserError
+ end
+ end
+ if value.is_a? Hash
+ value.delete_if {|key,v| v.nil?}
+ end
+ facts[name] = value
+ end
+ facts.delete_if do |name, value|
+ value.nil? || value.empty?
+ end
+ if facts.empty?
+ "{}\n"
+ else
+ JSON.sorted_generate(facts) + "\n"
+ end
+ end
+ end
+
+ private
+
+ def update_facts(global_options, options, args)
+ nodes = manager.filter(args, :local => false, :disabled => false)
+ new_facts = {}
+ ssh_connect(nodes) do |ssh|
+ ssh.leap.run_with_progress(facter_cmd) do |response|
+ node = manager.node(response[:host])
+ if node
+ new_facts[node.name] = response[:data].strip
+ else
+ log :warning, 'Could not find node for hostname %s' % response[:host]
+ end
+ end
+ end
+ # only overwrite the entire facts file if and only if we are gathering facts
+ # for all nodes in all environments.
+ overwrite_existing = args.empty? && LeapCli.leapfile.environment.nil?
+ update_facts_file(new_facts, overwrite_existing)
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/info.rb b/lib/leap_cli/commands/info.rb
new file mode 100644
index 00000000..52225a94
--- /dev/null
+++ b/lib/leap_cli/commands/info.rb
@@ -0,0 +1,15 @@
+module LeapCli; module Commands
+
+ desc 'Prints information regarding facts, history, and running processes for a node or nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:info] do |c|
+ c.action do |global,options,args|
+ nodes = manager.filter!(args)
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.debug
+ end
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/inspect.rb b/lib/leap_cli/commands/inspect.rb
new file mode 100644
index 00000000..20654fa7
--- /dev/null
+++ b/lib/leap_cli/commands/inspect.rb
@@ -0,0 +1,144 @@
+module LeapCli; module Commands
+
+ desc 'Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.'
+ arg_name 'FILE'
+ command [:inspect, :i] do |c|
+ c.switch 'base', :desc => 'Inspect the FILE from the provider_base (i.e. without local inheritance).', :negatable => false
+ c.action do |global_options,options,args|
+ object = args.first
+ assert! object, 'A file path or node/service/tag name is required'
+ method = inspection_method(object)
+ if method && defined?(method)
+ self.send(method, object, options)
+ else
+ log "Sorry, I don't know how to inspect that."
+ end
+ end
+ end
+
+ private
+
+ FTYPE_MAP = {
+ "PEM certificate" => :inspect_x509_cert,
+ "PEM RSA private key" => :inspect_x509_key,
+ "OpenSSH RSA public key" => :inspect_ssh_pub_key,
+ "PEM certificate request" => :inspect_x509_csr
+ }
+
+ def inspection_method(object)
+ if File.exists?(object)
+ ftype = `file #{object}`.split(':').last.strip
+ log 2, "file is of type '#{ftype}'"
+ if FTYPE_MAP[ftype]
+ FTYPE_MAP[ftype]
+ elsif File.extname(object) == ".json"
+ full_path = File.expand_path(object, Dir.pwd)
+ if path_match?(:node_config, full_path)
+ :inspect_node
+ elsif path_match?(:service_config, full_path)
+ :inspect_service
+ elsif path_match?(:tag_config, full_path)
+ :inspect_tag
+ elsif path_match?(:provider_config, full_path) || path_match?(:provider_env_config, full_path)
+ :inspect_provider
+ elsif path_match?(:common_config, full_path)
+ :inspect_common
+ else
+ nil
+ end
+ end
+ elsif manager.nodes[object]
+ :inspect_node
+ elsif manager.services[object]
+ :inspect_service
+ elsif manager.tags[object]
+ :inspect_tag
+ elsif object == "common"
+ :inspect_common
+ elsif object == "provider"
+ :inspect_provider
+ else
+ nil
+ end
+ end
+
+ #
+ # inspectors
+ #
+
+ def inspect_x509_key(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl rsa -in %s -text -check' % file_path
+ end
+
+ def inspect_x509_cert(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl x509 -in %s -text -noout' % file_path
+ log 0, :"SHA256 fingerprint", X509.fingerprint("SHA256", file_path)
+ end
+
+ def inspect_x509_csr(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl req -text -noout -verify -in %s' % file_path
+ end
+
+ #def inspect_ssh_pub_key(file_path)
+ #end
+
+ def inspect_node(arg, options)
+ inspect_json manager.nodes[name(arg)]
+ end
+
+ def inspect_service(arg, options)
+ if options[:base]
+ inspect_json manager.base_services[name(arg)]
+ else
+ inspect_json manager.services[name(arg)]
+ end
+ end
+
+ def inspect_tag(arg, options)
+ if options[:base]
+ inspect_json manager.base_tags[name(arg)]
+ else
+ inspect_json manager.tags[name(arg)]
+ end
+ end
+
+ def inspect_provider(arg, options)
+ if options[:base]
+ inspect_json manager.base_provider
+ elsif arg =~ /provider\.(.*)\.json/
+ inspect_json manager.env($1).provider
+ else
+ inspect_json manager.provider
+ end
+ end
+
+ def inspect_common(arg, options)
+ if options[:base]
+ inspect_json manager.base_common
+ else
+ inspect_json manager.common
+ end
+ end
+
+ #
+ # helpers
+ #
+
+ def name(arg)
+ File.basename(arg).sub(/\.json$/, '')
+ end
+
+ def inspect_json(config)
+ if config
+ puts JSON.sorted_generate(config)
+ end
+ end
+
+ def path_match?(path_symbol, path)
+ Dir.glob(Path.named_path([path_symbol, '*'])).include?(path)
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb
new file mode 100644
index 00000000..aa425432
--- /dev/null
+++ b/lib/leap_cli/commands/list.rb
@@ -0,0 +1,132 @@
+require 'command_line_reporter'
+
+module LeapCli; module Commands
+
+ desc 'List nodes and their classifications'
+ long_desc 'Prints out a listing of nodes, services, or tags. ' +
+ 'If present, the FILTER can be a list of names of nodes, services, or tags. ' +
+ 'If the name is prefixed with +, this acts like an AND condition. ' +
+ "For example:\n\n" +
+ "`leap list node1 node2` matches all nodes named \"node1\" OR \"node2\"\n\n" +
+ "`leap list openvpn +local` matches all nodes with service \"openvpn\" AND tag \"local\""
+
+ arg_name 'FILTER', :optional => true
+ command [:list,:ls] do |c|
+ c.flag 'print', :desc => 'What attributes to print (optional)'
+ c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
+ c.action do |global_options,options,args|
+ # don't rely on default manager(), because we want to pass custom options to load()
+ manager = LeapCli::Config::Manager.new
+ if global_options[:color]
+ colors = ['cyan', 'white']
+ else
+ colors = [nil, nil]
+ end
+ puts
+ manager.load(:include_disabled => options['disabled'], :continue_on_error => true)
+ if options['print']
+ print_node_properties(manager.filter(args), options['print'])
+ else
+ if args.any?
+ NodeTable.new(manager.filter(args), colors).run
+ else
+ environment = LeapCli.leapfile.environment || '_all_'
+ TagTable.new('SERVICES', manager.env(environment).services, colors).run
+ TagTable.new('TAGS', manager.env(environment).tags, colors).run
+ NodeTable.new(manager.filter(), colors).run
+ end
+ end
+ end
+ end
+
+ private
+
+ def self.print_node_properties(nodes, properties)
+ properties = properties.split(',')
+ max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max}
+ nodes.each_node do |node|
+ value = properties.collect{|prop|
+ prop_value = node[prop]
+ if prop_value.nil?
+ "null"
+ elsif prop_value == ""
+ "empty"
+ elsif prop_value.is_a? LeapCli::Config::Object
+ node[prop].dump_json(:format => :compact) # TODO: add option of getting pre-evaluation values.
+ else
+ prop_value.to_s
+ end
+ }.join(', ')
+ printf("%#{max_width}s %s\n", node.name, value)
+ end
+ puts
+ end
+
+ class TagTable
+ include CommandLineReporter
+ def initialize(heading, tag_list, colors)
+ @heading = heading
+ @tag_list = tag_list
+ @colors = colors
+ end
+ def run
+ tags = @tag_list.keys.select{|tag| tag !~ /^_/}.sort # sorted list of tags, excluding _partials
+ max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
+ table :border => false do
+ row :color => @colors[0] do
+ column @heading, :align => 'right', :width => max_width
+ column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
+ end
+ tags.each do |tag|
+ next if @tag_list[tag].node_list.empty?
+ row :color => @colors[1] do
+ column tag
+ column @tag_list[tag].node_list.keys.sort.join(', ')
+ end
+ end
+ end
+ vertical_spacing
+ end
+ end
+
+ #
+ # might be handy: HighLine::SystemExtensions.terminal_size.first
+ #
+ class NodeTable
+ include CommandLineReporter
+ def initialize(node_list, colors)
+ @node_list = node_list
+ @colors = colors
+ end
+ def run
+ rows = @node_list.keys.sort.collect do |node_name|
+ [node_name, @node_list[node_name].services.sort.join(', '), @node_list[node_name].tags.sort.join(', ')]
+ end
+ unless rows.any?
+ puts Paint["no results", :red]
+ puts
+ return
+ end
+ padding = 2
+ max_node_width = [20, (rows.map{|i|i[0]} + ["NODES"] ).inject(0) {|max,i| [i.size,max].max}].max
+ max_service_width = (rows.map{|i|i[1]} + ["SERVICES"]).inject(0) {|max,i| [i.size+padding+padding,max].max}
+ max_tag_width = (rows.map{|i|i[2]} + ["TAGS"] ).inject(0) {|max,i| [i.size,max].max}
+ table :border => false do
+ row :color => @colors[0] do
+ column "NODES", :align => 'right', :width => max_node_width
+ column "SERVICES", :width => max_service_width, :padding => 2
+ column "TAGS", :width => max_tag_width
+ end
+ rows.each do |r|
+ row :color => @colors[1] do
+ column r[0]
+ column r[1]
+ column r[2]
+ end
+ end
+ end
+ vertical_spacing
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb
new file mode 100644
index 00000000..a23661b3
--- /dev/null
+++ b/lib/leap_cli/commands/node.rb
@@ -0,0 +1,188 @@
+#
+# fyi: the `node init` command lives in node_init.rb,
+# but all other `node x` commands live here.
+#
+
+autoload :IPAddr, 'ipaddr'
+
+module LeapCli; module Commands
+
+ ##
+ ## COMMANDS
+ ##
+
+ desc 'Node management'
+ command [:node, :n] do |node|
+ node.desc 'Create a new configuration file for a node named NAME.'
+ node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
+ "The format is property_name:value.",
+ "For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.",
+ "To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`",
+ "Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`"].join("\n\n")
+ node.arg_name 'NAME [SEED]' # , :optional => false, :multiple => false
+ node.command :add do |add|
+ add.switch :local, :desc => 'Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false
+ add.action do |global_options,options,args|
+ # argument sanity checks
+ name = args.first
+ assert_valid_node_name!(name, options[:local])
+ assert_files_missing! [:node_config, name]
+
+ # create and seed new node
+ node = Config::Node.new(manager.env)
+ if options[:local]
+ node['ip_address'] = pick_next_vagrant_ip_address
+ end
+ seed_node_data_from_cmd_line(node, args[1..-1])
+ seed_node_data_from_template(node)
+ validate_ip_address(node)
+ begin
+ node['name'] = name
+ json = node.dump_json(:exclude => ['name'])
+ write_file!([:node_config, name], json + "\n")
+ if file_exists? :ca_cert, :ca_key
+ generate_cert_for_node(manager.reload_node!(node))
+ end
+ rescue LeapCli::ConfigError => exc
+ remove_node_files(name)
+ end
+ end
+ end
+
+ node.desc 'Renames a node file, and all its related files.'
+ node.arg_name 'OLD_NAME NEW_NAME'
+ node.command :mv do |mv|
+ mv.action do |global_options,options,args|
+ node = get_node_from_args(args, include_disabled: true)
+ new_name = args.last
+ assert_valid_node_name!(new_name, node.vagrant?)
+ ensure_dir [:node_files_dir, new_name]
+ Leap::Platform.node_files.each do |path|
+ rename_file! [path, node.name], [path, new_name]
+ end
+ remove_directory! [:node_files_dir, node.name]
+ rename_node_facts(node.name, new_name)
+ end
+ end
+
+ node.desc 'Removes all the files related to the node named NAME.'
+ node.arg_name 'NAME' #:optional => false #, :multiple => false
+ node.command :rm do |rm|
+ rm.action do |global_options,options,args|
+ node = get_node_from_args(args, include_disabled: true)
+ remove_node_files(node.name)
+ if node.vagrant?
+ vagrant_command("destroy --force", [node.name])
+ end
+ remove_node_facts(node.name)
+ end
+ end
+ end
+
+ ##
+ ## PUBLIC HELPERS
+ ##
+
+ def get_node_from_args(args, options={})
+ node_name = args.first
+ node = manager.node(node_name)
+ if node.nil? && options[:include_disabled]
+ node = manager.disabled_node(node_name)
+ end
+ assert!(node, "Node '#{node_name}' not found.")
+ node
+ end
+
+ def seed_node_data_from_cmd_line(node, args)
+ args.each do |seed|
+ key, value = seed.split(':', 2)
+ value = format_seed_value(value)
+ assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
+ if key =~ /\./
+ key_parts = key.split('.')
+ final_key = key_parts.pop
+ current_object = node
+ key_parts.each do |key_part|
+ current_object[key_part] ||= Config::Object.new
+ current_object = current_object[key_part]
+ end
+ current_object[final_key] = value
+ else
+ node[key] = value
+ end
+ end
+ end
+
+ #
+ # load "new node template" information into the `node`, modifying `node`.
+ # values in the template will not override existing node values.
+ #
+ def seed_node_data_from_template(node)
+ node.inherit_from!(manager.template('common'))
+ [node['services']].flatten.each do |service|
+ if service
+ template = manager.template(service)
+ if template
+ node.inherit_from!(template)
+ end
+ end
+ end
+ end
+
+ def remove_node_files(node_name)
+ (Leap::Platform.node_files + [:node_files_dir]).each do |path|
+ remove_file! [path, node_name]
+ end
+ end
+
+ #
+ # conversions:
+ #
+ # "x,y,z" => ["x","y","z"]
+ #
+ # "22" => 22
+ #
+ # "5.1" => 5.1
+ #
+ def format_seed_value(v)
+ if v =~ /,/
+ v = v.split(',')
+ v.map! do |i|
+ i = i.to_i if i.to_i.to_s == i
+ i = i.to_f if i.to_f.to_s == i
+ i
+ end
+ else
+ v = v.to_i if v.to_i.to_s == v
+ v = v.to_f if v.to_f.to_s == v
+ end
+ return v
+ end
+
+ def validate_ip_address(node)
+ if node['ip_address'] == "REQUIRED"
+ bail! do
+ log :error, "ip_address is not set. Specify with `leap node add NAME ip_address:ADDRESS`."
+ end
+ end
+ IPAddr.new(node['ip_address'])
+ rescue ArgumentError
+ bail! do
+ if node['ip_address']
+ log :invalid, "ip_address #{node['ip_address'].inspect}"
+ else
+ log :missing, "ip_address"
+ end
+ end
+ end
+
+ def assert_valid_node_name!(name, local=false)
+ assert! name, 'No <node-name> specified.'
+ if local
+ assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
+ else
+ assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/node_init.rb b/lib/leap_cli/commands/node_init.rb
new file mode 100644
index 00000000..33f6288d
--- /dev/null
+++ b/lib/leap_cli/commands/node_init.rb
@@ -0,0 +1,169 @@
+#
+# Node initialization.
+# Most of the fun stuff is in tasks.rb.
+#
+
+module LeapCli; module Commands
+
+ desc 'Node management'
+ command :node do |node|
+ node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
+ node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
+ "copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
+ "Node init must be run before deploying to a server, and the server must be running and available via the network. " +
+ "This command only needs to be run once, but there is no harm in running it multiple times."
+ node.arg_name 'FILTER'
+ node.command :init do |init|
+ init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
+ init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
+ init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
+
+ init.action do |global,options,args|
+ assert! args.any?, 'You must specify a FILTER'
+ finished = []
+ manager.filter!(args).each_node do |node|
+ is_node_alive(node, options)
+ save_public_host_key(node, global, options) unless node.vagrant?
+ update_compiled_ssh_configs
+ ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
+ ssh_connect(node, ssh_connect_options) do |ssh|
+ if node.vagrant?
+ ssh.install_insecure_vagrant_key
+ end
+ ssh.install_authorized_keys
+ ssh.install_prerequisites
+ unless node.vagrant?
+ ssh.leap.log(:checking, "SSH host keys") do
+ ssh.leap.capture(get_ssh_keys_cmd) do |response|
+ update_local_ssh_host_keys(node, response[:data]) if response[:exitcode] == 0
+ end
+ end
+ end
+ ssh.leap.log(:updating, "facts") do
+ ssh.leap.capture(facter_cmd) do |response|
+ if response[:exitcode] == 0
+ update_node_facts(node.name, response[:data])
+ else
+ log :failed, "to run facter on #{node.name}"
+ end
+ end
+ end
+ end
+ finished << node.name
+ end
+ log :completed, "initialization of nodes #{finished.join(', ')}"
+ end
+ end
+ end
+
+ private
+
+ ##
+ ## PRIVATE HELPERS
+ ##
+
+ def is_node_alive(node, options)
+ address = options[:ip] || node.ip_address
+ port = options[:port] || node.ssh.port
+ log :connecting, "to node #{node.name}"
+ assert_run! "nc -zw3 #{address} #{port}",
+ "Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
+ end
+
+ #
+ # saves the public ssh host key for node into the provider directory.
+ #
+ # see `man sshd` for the format of known_hosts
+ #
+ def save_public_host_key(node, global, options)
+ log :fetching, "public SSH host key for #{node.name}"
+ address = options[:ip] || node.ip_address
+ port = options[:port] || node.ssh.port
+ host_keys = get_public_keys_for_ip(address, port)
+ pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
+
+ if Path.exists?(pub_key_path)
+ if host_keys.include? SshKey.load(pub_key_path)
+ log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
+ else
+ bail! do
+ log :error, "The public SSH host keys we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
+ log "Delete the file #{pub_key_path} if you really want to remove the trusted SSH host key.", :indent => 2
+ end
+ end
+ else
+ known_key = host_keys.detect{|k|k.in_known_hosts?(node.name, node.ip_address, node.domain.name)}
+ if known_key
+ log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
+ else
+ public_key = SshKey.pick_best_key(host_keys)
+ if public_key.nil?
+ bail!("We got back #{host_keys.size} host keys from #{node.name}, but we can't support any of them.")
+ else
+ say(" This is the SSH host key you got back from node \"#{node.name}\"")
+ say(" Type -- #{public_key.bits} bit #{public_key.type.upcase}")
+ say(" Fingerprint -- " + public_key.fingerprint)
+ say(" Public Key -- " + public_key.key)
+ if !global[:yes] && !agree(" Is this correct? ")
+ bail!
+ else
+ known_key = public_key
+ end
+ end
+ end
+ puts
+ write_file! [:node_ssh_pub_key, node.name], known_key.to_s
+ end
+ end
+
+ #
+ # Get the public host keys for a host using ssh-keyscan.
+ # Return an array of SshKey objects, one for each key.
+ #
+ def get_public_keys_for_ip(address, port=22)
+ assert_bin!('ssh-keyscan')
+ output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
+ if output.empty?
+ bail! :failed, "ssh-keyscan returned empty output."
+ end
+
+ if output =~ /No route to host/
+ bail! :failed, 'ssh-keyscan: no route to %s' % address
+ else
+ keys = SshKey.parse_keys(output)
+ if keys.empty?
+ bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
+ else
+ return keys
+ end
+ end
+ end
+
+ # run on the server to generate a string suitable for passing to SshKey.parse_keys()
+ def get_ssh_keys_cmd
+ "/bin/grep ^HostKey /etc/ssh/sshd_config | /usr/bin/awk '{print $2 \".pub\"}' | /usr/bin/xargs /bin/cat"
+ end
+
+ #
+ # Sometimes the ssh host keys on the server will be better than what we have
+ # stored locally. In these cases, ask the user if they want to upgrade.
+ #
+ def update_local_ssh_host_keys(node, remote_keys_string)
+ remote_keys = SshKey.parse_keys(remote_keys_string)
+ return unless remote_keys.any?
+ current_key = SshKey.load(Path.named_path([:node_ssh_pub_key, node.name]))
+ best_key = SshKey.pick_best_key(remote_keys)
+ return unless best_key && current_key
+ if current_key != best_key
+ say(" One of the SSH host keys for node '#{node.name}' is better than what you currently have trusted.")
+ say(" Current key: #{current_key.summary}")
+ say(" Better key: #{best_key.summary}")
+ if agree(" Do you want to use the better key? ")
+ write_file! [:node_ssh_pub_key, node.name], best_key.to_s
+ end
+ else
+ log(3, "current host key does not need updating")
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/ssh.rb b/lib/leap_cli/commands/ssh.rb
new file mode 100644
index 00000000..3887618e
--- /dev/null
+++ b/lib/leap_cli/commands/ssh.rb
@@ -0,0 +1,225 @@
+module LeapCli; module Commands
+
+ desc 'Log in to the specified node with an interactive shell.'
+ arg_name 'NAME' #, :optional => false, :multiple => false
+ command :ssh do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ exec_ssh(:ssh, options, args)
+ end
+ end
+
+ desc 'Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).'
+ arg_name 'NAME'
+ command :mosh do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ exec_ssh(:mosh, options, args)
+ end
+ end
+
+ desc 'Creates an SSH port forward (tunnel) to the node NAME. REMOTE_PORT is the port on the remote node that the tunnel will connect to. LOCAL_PORT is the optional port on your local machine. For example: `leap tunnel couch1:5984`.'
+ arg_name '[LOCAL_PORT:]NAME:REMOTE_PORT'
+ command :tunnel do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ local_port, node, remote_port = parse_tunnel_arg(args.first)
+ unless node.ssh.config.AllowTcpForwarding == "yes"
+ log :warning, "It looks like TCP forwarding is not enabled. "+
+ "The tunnel command requires that the node property ssh.config.AllowTcpForwarding "+
+ "be set to 'yes'. Add this property to #{node.name}.json, deploy, and then try tunnel again."
+ end
+ options[:ssh] = [options[:ssh], "-N -L 127.0.0.1:#{local_port}:0.0.0.0:#{remote_port}"].join(' ')
+ log("Forward port localhost:#{local_port} to #{node.name}:#{remote_port}")
+ if is_port_available?(local_port)
+ exec_ssh(:ssh, options, [node.name])
+ end
+ end
+ end
+
+ desc 'Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".'
+ arg_name 'FILE1 FILE2'
+ command :scp do |c|
+ c.switch :r, :desc => 'Copy recursively'
+ c.action do |global_options, options, args|
+ if args.size != 2
+ bail!('You must specificy both FILE1 and FILE2')
+ end
+ from, to = args
+ if (from !~ /:/ && to !~ /:/) || (from =~ /:/ && to =~ /:/)
+ bail!('One FILE must be remote and the other local.')
+ end
+ src_node_name = src_file_path = src_node = nil
+ dst_node_name = dst_file_path = dst_node = nil
+ if from =~ /:/
+ src_node_name, src_file_path = from.split(':')
+ src_node = get_node_from_args([src_node_name], :include_disabled => true)
+ dst_file_path = to
+ else
+ dst_node_name, dst_file_path = to.split(':')
+ dst_node = get_node_from_args([dst_node_name], :include_disabled => true)
+ src_file_path = from
+ end
+ exec_scp(options, src_node, src_file_path, dst_node, dst_file_path)
+ end
+ end
+
+ protected
+
+ #
+ # allow for ssh overrides of all commands that use ssh_connect
+ #
+ def connect_options(options)
+ connect_options = {:ssh_options=>{}}
+ if options[:port]
+ connect_options[:ssh_options][:port] = options[:port]
+ end
+ if options[:ip]
+ connect_options[:ssh_options][:host_name] = options[:ip]
+ end
+ return connect_options
+ end
+
+ def ssh_config_help_message
+ puts ""
+ puts "Are 'too many authentication failures' getting you down?"
+ puts "Then we have the solution for you! Add something like this to your ~/.ssh/config file:"
+ puts " Host *.#{manager.provider.domain}"
+ puts " IdentityFile ~/.ssh/id_rsa"
+ puts " IdentitiesOnly=yes"
+ puts "(replace `id_rsa` with the actual private key filename that you use for this provider)"
+ end
+
+ require 'socket'
+ def is_port_available?(port)
+ TCPServer.open('127.0.0.1', port) {}
+ true
+ rescue Errno::EACCES
+ bail!("You don't have permission to bind to port #{port}.")
+ rescue Errno::EADDRINUSE
+ bail!("Local port #{port} is already in use. Specify LOCAL_PORT to pick another.")
+ rescue Exception => exc
+ bail!(exc.to_s)
+ end
+
+ private
+
+ def exec_ssh(cmd, cli_options, args)
+ node = get_node_from_args(args, :include_disabled => true)
+ port = node.ssh.port
+ options = ssh_config(node)
+ username = 'root'
+ if LeapCli.log_level >= 3
+ options << "-vv"
+ elsif LeapCli.log_level >= 2
+ options << "-v"
+ end
+ if cli_options[:port]
+ port = cli_options[:port]
+ end
+ if cli_options[:ssh]
+ options << cli_options[:ssh]
+ end
+ ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}"
+ if cmd == :ssh
+ command = "#{ssh} #{node.domain.full}"
+ elsif cmd == :mosh
+ command = "MOSH_TITLE_NOPREFIX=1 mosh --ssh \"#{ssh}\" #{node.domain.full}"
+ end
+ log 2, command
+
+ # exec the shell command in a subprocess
+ pid = fork { exec "#{command}" }
+
+ Signal.trap("SIGINT") do
+ Process.kill("KILL", pid)
+ Process.wait(pid)
+ exit(0)
+ end
+
+ # wait for shell to exit so we can grab the exit status
+ _, status = Process.waitpid2(pid)
+
+ if status.exitstatus == 255
+ ssh_config_help_message
+ elsif status.exitstatus != 0
+ exit(status.exitstatus)
+ end
+ end
+
+ def exec_scp(cli_options, src_node, src_file_path, dst_node, dst_file_path)
+ node = src_node || dst_node
+ options = ssh_config(node)
+ port = node.ssh.port
+ username = 'root'
+ options << "-r" if cli_options[:r]
+ scp = "scp -P #{port} #{options.join(' ')}"
+ if src_node
+ command = "#{scp} #{username}@#{src_node.domain.full}:#{src_file_path} #{dst_file_path}"
+ elsif dst_node
+ command = "#{scp} #{src_file_path} #{username}@#{dst_node.domain.full}:#{dst_file_path}"
+ end
+ log 2, command
+
+ # exec the shell command in a subprocess
+ pid = fork { exec "#{command}" }
+
+ Signal.trap("SIGINT") do
+ Process.kill("KILL", pid)
+ Process.wait(pid)
+ exit(0)
+ end
+
+ # wait for shell to exit so we can grab the exit status
+ _, status = Process.waitpid2(pid)
+ exit(status.exitstatus)
+ end
+
+ #
+ # SSH command line -o options. See `man ssh_config`
+ #
+ # NOTES:
+ #
+ # The option 'HostKeyAlias=#{node.name}' is oddly incompatible with ports in
+ # known_hosts file, so we must not use this or non-standard ports break.
+ #
+ def ssh_config(node)
+ options = [
+ "-o 'HostName=#{node.ip_address}'",
+ "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
+ "-o 'UserKnownHostsFile=/dev/null'"
+ ]
+ if node.vagrant?
+ options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
+ options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
+ options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it
+ # (since userknownhostsfile is /dev/null)
+ else
+ options << "-o 'StrictHostKeyChecking=yes'"
+ end
+ if !node.supported_ssh_host_key_algorithms.empty?
+ options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
+ end
+ return options
+ end
+
+ def parse_tunnel_arg(arg)
+ if arg.count(':') == 1
+ node_name, remote = arg.split(':')
+ local = nil
+ elsif arg.count(':') == 2
+ local, node_name, remote = arg.split(':')
+ else
+ bail!('Argument NAME:REMOTE_PORT required.')
+ end
+ node = get_node_from_args([node_name], :include_disabled => true)
+ remote = remote.to_i
+ local = local || remote
+ local = local.to_i
+ return [local, node, remote]
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb
new file mode 100644
index 00000000..73207b31
--- /dev/null
+++ b/lib/leap_cli/commands/test.rb
@@ -0,0 +1,74 @@
+module LeapCli; module Commands
+
+ desc 'Run tests.'
+ command [:test, :t] do |test|
+ test.desc 'Run the test suit on FILTER nodes.'
+ test.arg_name 'FILTER', :optional => true
+ test.command :run do |run|
+ run.switch 'continue', :desc => 'Continue over errors and failures (default is --no-continue).', :negatable => true
+ run.action do |global_options,options,args|
+ test_order = File.join(Path.platform, 'tests/order.rb')
+ if File.exists?(test_order)
+ require test_order
+ end
+ manager.filter!(args).names_in_test_dependency_order.each do |node_name|
+ node = manager.nodes[node_name]
+ begin
+ ssh_connect(node) do |ssh|
+ ssh.run(test_cmd(options))
+ end
+ rescue Capistrano::CommandError => exc
+ if options[:continue]
+ exit_status(1)
+ else
+ bail!
+ end
+ end
+ end
+ end
+ end
+
+ test.desc 'Creates files needed to run tests.'
+ test.command :init do |init|
+ init.action do |global_options,options,args|
+ generate_test_client_openvpn_configs
+ end
+ end
+
+ test.default_command :run
+ end
+
+ private
+
+ def test_cmd(options)
+ if options[:continue]
+ "#{Leap::Platform.leap_dir}/bin/run_tests --continue"
+ else
+ "#{Leap::Platform.leap_dir}/bin/run_tests"
+ end
+ end
+
+ #
+ # generates a whole bunch of openvpn configs that can be used to connect to different openvpn gateways
+ #
+ def generate_test_client_openvpn_configs
+ assert_config! 'provider.ca.client_certificates.unlimited_prefix'
+ assert_config! 'provider.ca.client_certificates.limited_prefix'
+ template = read_file! Path.find_file(:test_client_openvpn_template)
+ manager.environment_names.each do |env|
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_limited' => true]
+ if vpn_nodes.any?
+ generate_test_client_cert(provider.ca.client_certificates.limited_prefix) do |key, cert|
+ write_file! [:test_openvpn_config, [env, 'limited'].compact.join('_')], Util.erb_eval(template, binding)
+ end
+ end
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_unlimited' => true]
+ if vpn_nodes.any?
+ generate_test_client_cert(provider.ca.client_certificates.unlimited_prefix) do |key, cert|
+ write_file! [:test_openvpn_config, [env, 'unlimited'].compact.join('_')], Util.erb_eval(template, binding)
+ end
+ end
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
new file mode 100644
index 00000000..b842e854
--- /dev/null
+++ b/lib/leap_cli/commands/user.rb
@@ -0,0 +1,136 @@
+
+#
+# perhaps we want to verify that the key files are actually the key files we expect.
+# we could use 'file' for this:
+#
+# > file ~/.gnupg/00440025.asc
+# ~/.gnupg/00440025.asc: PGP public key block
+#
+# > file ~/.ssh/id_rsa.pub
+# ~/.ssh/id_rsa.pub: OpenSSH RSA public key
+#
+
+module LeapCli
+ module Commands
+
+ desc 'Adds a new trusted sysadmin by adding public keys to the "users" directory.'
+ arg_name 'USERNAME' #, :optional => false, :multiple => false
+ command :'add-user' do |c|
+
+ c.switch 'self', :desc => 'Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.', :negatable => false
+ c.flag 'ssh-pub-key', :desc => 'SSH public key file for this new user'
+ c.flag 'pgp-pub-key', :desc => 'OpenPGP public key file for this new user'
+
+ c.action do |global_options,options,args|
+ username = args.first
+ if !username.any?
+ if options[:self]
+ username ||= `whoami`.strip
+ else
+ help! "Either USERNAME argument or --self flag is required."
+ end
+ end
+ if Leap::Platform.reserved_usernames.include? username
+ bail! %(The username "#{username}" is reserved. Sorry, pick another.)
+ end
+
+ ssh_pub_key = nil
+ pgp_pub_key = nil
+
+ if options['ssh-pub-key']
+ ssh_pub_key = read_file!(options['ssh-pub-key'])
+ end
+ if options['pgp-pub-key']
+ pgp_pub_key = read_file!(options['pgp-pub-key'])
+ end
+
+ if options[:self]
+ ssh_pub_key ||= pick_ssh_key.to_s
+ pgp_pub_key ||= pick_pgp_key
+ end
+
+ assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+
+ if ssh_pub_key
+ write_file!([:user_ssh, username], ssh_pub_key)
+ end
+ if pgp_pub_key
+ write_file!([:user_pgp, username], pgp_pub_key)
+ end
+
+ update_authorized_keys
+ end
+ end
+
+ #
+ # let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_ssh_key
+ ssh_keys = []
+ Dir.glob("#{ENV['HOME']}/.ssh/*.pub").each do |keyfile|
+ ssh_keys << SshKey.load(keyfile)
+ end
+
+ if `which ssh-add`.strip.any?
+ `ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
+ key = SshKey.load(line)
+ if key
+ key.comment = 'ssh-agent'
+ ssh_keys << key unless ssh_keys.include?(key)
+ end
+ end
+ end
+ ssh_keys.compact!
+
+ assert! ssh_keys.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?'
+
+ if ssh_keys.length > 1
+ key_index = numbered_choice_menu('Choose your SSH public key', ssh_keys.collect(&:summary)) do |line, i|
+ say("#{i+1}. #{line}")
+ end
+ else
+ key_index = 0
+ end
+
+ return ssh_keys[key_index]
+ end
+
+ #
+ # let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_pgp_key
+ begin
+ require 'gpgme'
+ rescue LoadError
+ log "Skipping OpenPGP setup because gpgme is not installed."
+ return
+ end
+
+ secret_keys = GPGME::Key.find(:secret)
+ if secret_keys.empty?
+ log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you"
+ return nil
+ end
+
+ secret_keys.select!{|key| !key.expired}
+
+ if secret_keys.length > 1
+ key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
+ key_info = key.to_s.split("\n")[0..1].map{|line| line.sub(/^\s*(sec|uid)\s*/,'')}.join(' -- ')
+ say("#{i+1}. #{key_info}")
+ end
+ else
+ key_index = 0
+ end
+
+ key_id = secret_keys[key_index].sha
+
+ # can't use this, it includes signatures:
+ #puts GPGME::Key.export(key_id, :armor => true, :export_options => :export_minimal)
+
+ # export with signatures removed:
+ return `gpg --armor --export-options export-minimal --export #{key_id}`.strip
+ end
+
+ end
+end
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
new file mode 100644
index 00000000..c1da570e
--- /dev/null
+++ b/lib/leap_cli/commands/util.rb
@@ -0,0 +1,50 @@
+module LeapCli; module Commands
+
+ extend self
+ extend LeapCli::Util
+ extend LeapCli::Util::RemoteCommand
+
+ def path(name)
+ Path.named_path(name)
+ end
+
+ #
+ # keeps prompting the user for a numbered choice, until they pick a good one or bail out.
+ #
+ # block is yielded and is responsible for rendering the choices.
+ #
+ def numbered_choice_menu(msg, items, &block)
+ while true
+ say("\n" + msg + ':')
+ items.each_with_index &block
+ say("q. quit")
+ index = ask("number 1-#{items.length}> ")
+ if index.empty?
+ next
+ elsif index =~ /q/
+ bail!
+ else
+ i = index.to_i - 1
+ if i < 0 || i >= items.length
+ bail!
+ else
+ return i
+ end
+ end
+ end
+ end
+
+
+ def parse_node_list(nodes)
+ if nodes.is_a? Config::Object
+ Config::ObjectList.new(nodes)
+ elsif nodes.is_a? Config::ObjectList
+ nodes
+ elsif nodes.is_a? String
+ manager.filter!(nodes)
+ else
+ bail! "argument error"
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb
new file mode 100644
index 00000000..9fdd48e3
--- /dev/null
+++ b/lib/leap_cli/commands/vagrant.rb
@@ -0,0 +1,180 @@
+autoload :IPAddr, 'ipaddr'
+require 'fileutils'
+
+module LeapCli; module Commands
+
+ desc "Manage local virtual machines."
+ long_desc "This command provides a convient way to manage Vagrant-based virtual machines. If FILTER argument is missing, the command runs on all local virtual machines. The Vagrantfile is automatically generated in 'test/Vagrantfile'. If you want to run vagrant commands manually, cd to 'test'."
+ command [:local, :l] do |local|
+ local.desc 'Starts up the virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :start do |start|
+ start.flag(:basebox,
+ :desc => "The basebox to use. This value is passed to vagrant as the "+
+ "`config.vm.box` option. The value here should be the name of an installed box or a "+
+ "shorthand name of a box in HashiCorp's Atlas.",
+ :arg_name => 'BASEBOX',
+ :default_value => 'LEAP/jessie'
+ )
+ start.action do |global_options,options,args|
+ vagrant_command(["up", "sandbox on"], args, options)
+ end
+ end
+
+ local.desc 'Shuts down the virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :stop do |stop|
+ stop.action do |global_options,options,args|
+ if global_options[:yes]
+ vagrant_command("halt --force", args)
+ else
+ vagrant_command("halt", args)
+ end
+ end
+ end
+
+ local.desc 'Destroys the virtual machine(s), reclaiming the disk space'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :destroy do |destroy|
+ destroy.action do |global_options,options,args|
+ if global_options[:yes]
+ vagrant_command("destroy --force", args)
+ else
+ vagrant_command("destroy", args)
+ end
+ end
+ end
+
+ local.desc 'Print the status of local virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :status do |status|
+ status.action do |global_options,options,args|
+ vagrant_command("status", args)
+ end
+ end
+
+ local.desc 'Saves the current state of the virtual machine as a new snapshot'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :save do |status|
+ status.action do |global_options,options,args|
+ vagrant_command("sandbox commit", args)
+ end
+ end
+
+ local.desc 'Resets virtual machine(s) to the last saved snapshot'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :reset do |reset|
+ reset.action do |global_options,options,args|
+ vagrant_command("sandbox rollback", args)
+ end
+ end
+ end
+
+ public
+
+ #
+ # returns the path to a vagrant ssh private key file.
+ #
+ # if the vagrant.key file is owned by root or ourselves, then
+ # we need to make sure that it owned by us and not world readable.
+ #
+ def vagrant_ssh_key_file
+ file_path = Path.vagrant_ssh_priv_key_file
+ Util.assert_files_exist! file_path
+ uid = File.new(file_path).stat.uid
+ if uid == 0 || uid == Process.euid
+ FileUtils.install file_path, '/tmp/vagrant.key', :mode => 0600
+ file_path = '/tmp/vagrant.key'
+ end
+ return file_path
+ end
+
+ protected
+
+ def vagrant_command(cmds, args, options={})
+ vagrant_setup(options)
+ cmds = cmds.to_a
+ if args.empty?
+ nodes = [""]
+ else
+ nodes = manager.filter(args)[:environment => "local"].field(:name)
+ end
+ if nodes.any?
+ vagrant_dir = File.dirname(Path.named_path(:vagrantfile))
+ exec = ["cd #{vagrant_dir}"]
+ cmds.each do |cmd|
+ nodes.each do |node|
+ exec << "vagrant #{cmd} #{node}"
+ end
+ end
+ execute exec.join('; ')
+ else
+ bail! "No nodes found. This command only works on nodes with ip_address in the network #{LeapCli.leapfile.vagrant_network}"
+ end
+ end
+
+ private
+
+ def vagrant_setup(options)
+ assert_bin! 'vagrant', 'Vagrant is required for running local virtual machines. Run "sudo apt-get install vagrant".'
+ assert! (vagrant_version >= Gem::Version.new('1.1')), 'Vagrant version >= 1.1 is required for running local virtual machines. Please upgrade.'
+
+ unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any?
+ log :installing, "vagrant plugin 'sahara'"
+ assert_run! 'vagrant plugin install sahara'
+ end
+ create_vagrant_file(options)
+ end
+
+ def vagrant_version
+ @vagrant_version ||= Gem::Version.new(assert_run!('vagrant --version').split(' ')[1])
+ end
+
+ def execute(cmd)
+ log 2, :run, cmd
+ exec cmd
+ end
+
+ def create_vagrant_file(options)
+ lines = []
+
+ basebox = options[:basebox] || 'LEAP/jessie'
+ # override basebox with custom setting from Leapfile or ~/.leaprc
+ basebox = leapfile.vagrant_basebox || basebox
+
+ lines << %[Vagrant.configure("2") do |config|]
+ manager.each_node do |node|
+ if node.vagrant?
+ lines << %[ config.vm.define :#{node.name} do |config|]
+ lines << %[ config.vm.box = "#{basebox}"]
+ lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
+ lines << %[ config.vm.provider "virtualbox" do |v|]
+ lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
+ lines << %[ v.name = "#{node.name}"]
+ lines << %[ v.memory = 1536]
+ lines << %[ end]
+ lines << %[ config.vm.provider "libvirt" do |v|]
+ lines << %[ v.memory = 1536]
+ lines << %[ end]
+ lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
+ lines << %[ end]
+ end
+ end
+
+ lines << %[end]
+ lines << ""
+ write_file! :vagrantfile, lines.join("\n")
+ end
+
+ def pick_next_vagrant_ip_address
+ taken_ips = manager.nodes[:environment => "local"].field(:ip_address)
+ if taken_ips.any?
+ highest_ip = taken_ips.map{|ip| IPAddr.new(ip)}.max
+ new_ip = highest_ip.succ
+ else
+ new_ip = IPAddr.new(LeapCli.leapfile.vagrant_network).succ.succ
+ end
+ return new_ip.to_s
+ end
+
+end; end
diff --git a/lib/leap_cli/macros.rb b/lib/leap_cli/macros.rb
new file mode 100644
index 00000000..fdb9a94e
--- /dev/null
+++ b/lib/leap_cli/macros.rb
@@ -0,0 +1,16 @@
+#
+# MACROS
+#
+# The methods in these files are available in the context of a .json configuration file.
+# (The module LeapCli::Macro is included in Config::Object)
+#
+
+require_relative 'macros/core'
+require_relative 'macros/files'
+require_relative 'macros/haproxy'
+require_relative 'macros/hosts'
+require_relative 'macros/keys'
+require_relative 'macros/nodes'
+require_relative 'macros/secrets'
+require_relative 'macros/stunnel'
+require_relative 'macros/provider'
diff --git a/lib/leap_cli/macros/core.rb b/lib/leap_cli/macros/core.rb
new file mode 100644
index 00000000..873da358
--- /dev/null
+++ b/lib/leap_cli/macros/core.rb
@@ -0,0 +1,92 @@
+# encoding: utf-8
+
+module LeapCli
+ module Macro
+
+ #
+ # Creates a hash from the ssh key info in users directory, for use in
+ # updating authorized_keys file. Additionally, the 'monitor' public key is
+ # included, which is used by the monitor nodes to run particular commands
+ # remotely.
+ #
+ def authorized_keys
+ hash = {}
+ keys = Dir.glob(Path.named_path([:user_ssh, '*']))
+ keys.sort.each do |keyfile|
+ ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
+ name = File.basename(File.dirname(keyfile))
+ until hash[name].nil?
+ i ||= 1; name = "#{name}#{i+=1}"
+ end
+ hash[name] = {
+ "type" => ssh_type,
+ "key" => ssh_key
+ }
+ end
+ ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
+ hash[Leap::Platform.monitor_username] = {
+ "type" => ssh_type,
+ "key" => ssh_key
+ }
+ hash
+ end
+
+ def assert(assertion)
+ if instance_eval(assertion)
+ true
+ else
+ raise AssertionFailed.new(assertion), assertion, caller
+ end
+ end
+
+ def error(msg)
+ raise ConfigError.new(@node, msg), msg, caller
+ end
+
+ #
+ # applies a JSON partial to this node
+ #
+ def apply_partial(partial_path)
+ if env.partials[partial_path]
+ self.deep_merge!(env.partials[partial_path])
+ else
+ raise ArgumentError.new(
+ "No such partial `%s`. Available partials include:\n%s" %
+ [partial_path, env.partials.keys.join(", ")]
+ )
+ end
+ end
+
+ #
+ # If at first you don't succeed, then it is time to give up.
+ #
+ # try{} returns nil if anything in the block throws an exception.
+ #
+ # You can wrap something that might fail in `try`, like so.
+ #
+ # "= try{ nodes[:services => 'tor'].first.ip_address } "
+ #
+ def try(&block)
+ yield
+ rescue NoMethodError
+ rescue ArgumentError
+ nil
+ end
+
+ protected
+
+ #
+ # returns a node list, if argument is not already one
+ #
+ def listify(node_list)
+ if node_list.is_a? Config::ObjectList
+ node_list
+ elsif node_list.is_a? Config::Object
+ Config::ObjectList.new(node_list)
+ else
+ raise ArgumentError, 'argument must be a node or node list, not a `%s`' % node_list.class, caller
+ end
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/files.rb b/lib/leap_cli/macros/files.rb
new file mode 100644
index 00000000..04c94edf
--- /dev/null
+++ b/lib/leap_cli/macros/files.rb
@@ -0,0 +1,124 @@
+# encoding: utf-8
+
+##
+## FILES
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # inserts the contents of a file
+ #
+ def file(filename, options={})
+ if filename.is_a? Symbol
+ filename = [filename, @node.name]
+ end
+ filepath = Path.find_file(filename)
+ if filepath
+ if filepath =~ /\.erb$/
+ return ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
+ else
+ return File.read(filepath, :encoding => 'UTF-8')
+ end
+ else
+ raise FileMissing.new(Path.named_path(filename), options)
+ end
+ end
+
+ #
+ # like #file, but allow missing files
+ #
+ def try_file(filename)
+ return file(filename)
+ rescue FileMissing
+ return nil
+ end
+
+ #
+ # returns the location of a file that is stored on the local
+ # host, under PROVIDER_DIR/files.
+ #
+ def local_file_path(path, options={})
+ if path.is_a? Symbol
+ path = [path, @node.name]
+ elsif path.is_a? String
+ # ensure it prefixed with files/
+ unless path =~ /^files\//
+ path = "files/" + path
+ end
+ end
+ local_path = Path.find_file(path)
+ if local_path.nil?
+ if options[:missing]
+ raise FileMissing.new(Path.named_path(path), options)
+ elsif block_given?
+ yield
+ return local_file_path(path, options) # try again.
+ else
+ Util::log 2, :skipping, "local_file_path(\"#{path}\") because there is no such file."
+ return nil
+ end
+ else
+ return local_path
+ end
+ end
+
+ #
+ # Returns the location of a file once it is deployed via rsync to the a
+ # remote server. An internal list of discovered file paths is saved, in
+ # order to rsync these files when needed.
+ #
+ # If the file does not exist, nil is returned.
+ #
+ # If there is a block given and the file does not actually exist, the
+ # block will be yielded to give an opportunity for some code to create the
+ # file.
+ #
+ # For example:
+ #
+ # file_path(:dkim_priv_key) {generate_dkim_key}
+ #
+ # notes:
+ #
+ # * argument 'path' is relative to Path.provider/files or
+ # Path.provider_base/files
+ # * the path returned by this method is absolute
+ # * the path stored for use later by rsync is relative to Path.provider
+ # * if the path does not exist locally, but exists in provider_base,
+ # then the default file from provider_base is copied locally. this
+ # is required for rsync to work correctly.
+ #
+ def remote_file_path(path, options={}, &block)
+ local_path = local_file_path(path, options, &block)
+
+ return nil if local_path.nil?
+
+ # if file is under Path.provider_base, we must copy the default file to
+ # to Path.provider in order for rsync to be able to sync the file.
+ if local_path =~ /^#{Regexp.escape(Path.provider_base)}/
+ local_provider_path = local_path.sub(/^#{Regexp.escape(Path.provider_base)}/, Path.provider)
+ FileUtils.mkdir_p File.dirname(local_provider_path), :mode => 0700
+ FileUtils.install local_path, local_provider_path, :mode => 0600
+ Util.log :created, Path.relative_path(local_provider_path)
+ local_path = local_provider_path
+ end
+
+ # ensure directories end with /, important for building rsync command
+ if File.directory?(local_path) && local_path !~ /\/$/
+ local_path += '/'
+ end
+
+ relative_path = Path.relative_path(local_path)
+ relative_path.sub!(/^files\//, '') # remove "files/" prefix
+ @node.file_paths << relative_path
+ return File.join(Leap::Platform.files_dir, relative_path)
+ end
+
+ # deprecated
+ def file_path(path, options={})
+ return remote_file_path(path, options)
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/haproxy.rb b/lib/leap_cli/macros/haproxy.rb
new file mode 100644
index 00000000..602ae726
--- /dev/null
+++ b/lib/leap_cli/macros/haproxy.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+##
+## HAPROXY
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # creates a hash suitable for configuring haproxy. the key is the node name of the server we are proxying to.
+ #
+ # * node_list - a hash of nodes for the haproxy servers
+ # * stunnel_client - contains the mappings to local ports for each server node.
+ # * non_stunnel_port - in case self is included in node_list, the port to connect to.
+ #
+ # 1000 weight is used for nodes in the same location.
+ # 100 otherwise.
+ #
+ def haproxy_servers(node_list, stunnel_clients, non_stunnel_port=nil)
+ default_weight = 10
+ local_weight = 100
+
+ # record the hosts_file
+ hostnames(node_list)
+
+ # create a simple map for node name -> local stunnel accept port
+ accept_ports = stunnel_clients.inject({}) do |hsh, stunnel_entry|
+ name = stunnel_entry.first.sub /_[0-9]+$/, ''
+ hsh[name] = stunnel_entry.last['accept_port']
+ hsh
+ end
+
+ # if one the nodes in the node list is ourself, then there will not be a stunnel to it,
+ # but we need to include it anyway in the haproxy config.
+ if node_list[self.name] && non_stunnel_port
+ accept_ports[self.name] = non_stunnel_port
+ end
+
+ # create the first pass of the servers hash
+ servers = node_list.values.inject(Config::ObjectList.new) do |hsh, node|
+ # make sure we have a port to talk to
+ unless accept_ports[node.name]
+ error "haproxy needs a local port to talk to when connecting to #{node.name}"
+ end
+ weight = default_weight
+ try {
+ weight = local_weight if self.location.name == node.location.name
+ }
+ hsh[node.name] = Config::Object[
+ 'backup', false,
+ 'host', 'localhost',
+ 'port', accept_ports[node.name],
+ 'weight', weight
+ ]
+ if node.services.include?('couchdb')
+ hsh[node.name]['writable'] = node.couch.mode != 'mirror'
+ end
+ hsh
+ end
+
+ # if there are some local servers, make the others backup
+ if servers.detect{|k,v| v.weight == local_weight}
+ servers.each do |k,server|
+ server['backup'] = server['weight'] == default_weight
+ end
+ end
+
+ return servers
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/hosts.rb b/lib/leap_cli/macros/hosts.rb
new file mode 100644
index 00000000..963857ae
--- /dev/null
+++ b/lib/leap_cli/macros/hosts.rb
@@ -0,0 +1,90 @@
+# encoding: utf-8
+
+module LeapCli
+ module Macro
+
+ ##
+ ## IPs
+ ##
+
+ #
+ # returns a simple array of all the IPs for the specified node list
+ #
+ def host_ips(node_list)
+ if self.vagrant?
+ node_list = node_list['environment' => 'local']
+ else
+ node_list = node_list['environment' => '!local']
+ end
+ node_list.map {|name, n|
+ [n.ip_address, (manager.facts[name]||{})['ec2_public_ipv4']]
+ }.flatten.compact.uniq
+ end
+
+ ##
+ ## HOSTS
+ ##
+
+ #
+ # records the list of hosts that are encountered for this node
+ #
+ def hostnames(nodes)
+ @referenced_nodes ||= Config::ObjectList.new
+ nodes = listify(nodes)
+ nodes.each_node do |node|
+ @referenced_nodes[node.name] ||= node
+ end
+ return nodes.values.collect {|node| node.domain.name}
+ end
+
+ #
+ # Generates entries needed for updating /etc/hosts on a node (as a hash).
+ #
+ # Argument `nodes` can be nil or a list of nodes. If nil, only include the
+ # IPs of the other nodes this @node as has encountered (plus all mx nodes).
+ #
+ # Also, for virtual machines, we use the local address if this @node is in
+ # the same location as the node in question.
+ #
+ # We include the ssh public key for each host, so that the hash can also
+ # be used to generate the /etc/ssh/known_hosts
+ #
+ def hosts_file(nodes=nil)
+ if nodes.nil?
+ if @referenced_nodes && @referenced_nodes.any?
+ nodes = @referenced_nodes
+ nodes = nodes.merge(nodes_like_me[:services => 'mx']) # all nodes always need to communicate with mx nodes.
+ end
+ end
+ return {} unless nodes
+ hosts = {}
+ my_location = @node['location'] ? @node['location']['name'] : nil
+ nodes.each_node do |node|
+ hosts[node.name] = {
+ 'ip_address' => node.ip_address,
+ 'domain_internal' => node.domain.internal,
+ 'domain_full' => node.domain.full,
+ 'port' => node.ssh.port
+ }
+ if node.dns['aliases'] && node.dns['aliases'].any?
+ # include aliases, but without domain.full
+ hosts[node.name]['aliases'] = node.dns['aliases'] - [node.domain.full]
+ end
+ node_location = node['location'] ? node['location']['name'] : nil
+ if my_location == node_location
+ if facts = @node.manager.facts[node.name]
+ if facts['ec2_public_ipv4']
+ hosts[node.name]['ip_address'] = facts['ec2_public_ipv4']
+ end
+ end
+ end
+ host_pub_key = Util::read_file([:node_ssh_pub_key,node.name])
+ if host_pub_key
+ hosts[node.name]['host_pub_key'] = host_pub_key
+ end
+ end
+ hosts
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/keys.rb b/lib/leap_cli/macros/keys.rb
new file mode 100644
index 00000000..e7a75cfb
--- /dev/null
+++ b/lib/leap_cli/macros/keys.rb
@@ -0,0 +1,97 @@
+# encoding: utf-8
+
+#
+# Macro for dealing with cryptographic keys
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # return a fingerprint for a key or certificate
+ #
+ def fingerprint(filename, options={})
+ options[:mode] ||= :x509
+ if options[:mode] == :x509
+ "SHA256: " + X509.fingerprint("SHA256", Path.named_path(filename))
+ elsif options[:mode] == :rsa
+ key = OpenSSL::PKey::RSA.new(File.read(filename))
+ Digest::SHA1.new.hexdigest(key.to_der)
+ end
+ end
+
+ ##
+ ## TOR
+ ##
+
+ #
+ # return the path to the tor public key
+ # generating key if it is missing
+ #
+ def tor_public_key_path(path_name, key_type)
+ file_path(path_name) { generate_tor_key(key_type) }
+ end
+
+ #
+ # return the path to the tor private key
+ # generating key if it is missing
+ #
+ def tor_private_key_path(path_name, key_type)
+ file_path(path_name) { generate_tor_key(key_type) }
+ end
+
+ #
+ # Generates a onion_address from a public RSA key file.
+ #
+ # path_name is the named path of the Tor public key.
+ #
+ # Basically, an onion address is nothing more than a base32 encoding
+ # of the first 10 bytes of a sha1 digest of the public key.
+ #
+ # Additionally, Tor ignores the 22 byte header of the public key
+ # before taking the sha1 digest.
+ #
+ def onion_address(path_name)
+ require 'base32'
+ require 'base64'
+ require 'openssl'
+ path = Path.find_file([path_name, self.name])
+ if path && File.exists?(path)
+ public_key_str = File.readlines(path).grep(/^[^-]/).join
+ public_key = Base64.decode64(public_key_str)
+ public_key = public_key.slice(22..-1) # Tor ignores the 22 byte SPKI header
+ sha1sum = Digest::SHA1.new.digest(public_key)
+ Base32.encode(sha1sum.slice(0,10)).downcase
+ else
+ LeapCli.log :warning, 'Tor public key file "%s" does not exist' % tor_public_key_path
+ end
+ end
+
+ def generate_dkim_key(bit_size=2048)
+ LeapCli.log :generating, "%s bit RSA DKIM key" % bit_size do
+ private_key = OpenSSL::PKey::RSA.new(bit_size)
+ public_key = private_key.public_key
+ LeapCli::Util.write_file! :dkim_priv_key, private_key.to_pem
+ LeapCli::Util.write_file! :dkim_pub_key, public_key.to_pem
+ end
+ end
+
+ private
+
+ def generate_tor_key(key_type)
+ if key_type == 'RSA'
+ require 'certificate_authority'
+ keypair = CertificateAuthority::MemoryKeyMaterial.new
+ bit_size = 1024
+ LeapCli.log :generating, "%s bit RSA Tor key" % bit_size do
+ keypair.generate_key(bit_size)
+ LeapCli::Util.write_file! [:node_tor_priv_key, self.name], keypair.private_key.to_pem
+ LeapCli::Util.write_file! [:node_tor_pub_key, self.name], keypair.public_key.to_pem
+ end
+ else
+ LeapCli.bail! 'tor.key.type of %s is not yet supported' % key_type
+ end
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/nodes.rb b/lib/leap_cli/macros/nodes.rb
new file mode 100644
index 00000000..0e23831d
--- /dev/null
+++ b/lib/leap_cli/macros/nodes.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+
+##
+## node related macros
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # the list of all the nodes
+ #
+ def nodes
+ env.nodes
+ end
+
+ #
+ # simple alias for global.provider
+ #
+ def provider
+ env.provider
+ end
+
+ #
+ # returns a list of nodes that match the same environment
+ #
+ # if @node.environment is not set, we return other nodes
+ # where environment is not set.
+ #
+ def nodes_like_me
+ nodes[:environment => @node.environment]
+ end
+
+ #
+ # returns a list of nodes that match the location name
+ # and environment of @node.
+ #
+ def nodes_near_me
+ if @node['location'] && @node['location']['name']
+ nodes_like_me['location.name' => @node.location.name]
+ else
+ nodes_like_me['location' => nil]
+ end
+ end
+
+ #
+ #
+ # picks a node out from the node list in such a way that:
+ #
+ # (1) which nodes picked which nodes is saved in secrets.json
+ # (2) when other nodes call this macro with the same node list, they are guaranteed to get a different node
+ # (3) if all the nodes in the pick_node list have been picked, remaining nodes are distributed randomly.
+ #
+ # if the node_list is empty, an exception is raised.
+ # if node_list size is 1, then that node is returned and nothing is
+ # memorized via the secrets.json file.
+ #
+ # `label` is needed to distinguish between pools of nodes for different purposes.
+ #
+ # TODO: more evenly balance after all the nodes have been picked.
+ #
+ def pick_node(label, node_list)
+ if node_list.any?
+ if node_list.size == 1
+ return node_list.values.first
+ else
+ secrets_key = "pick_node(:#{label},#{node_list.keys.sort.join(',')})"
+ secrets_value = @manager.secrets.retrieve(secrets_key, @node.environment) || {}
+ secrets_value[@node.name] ||= begin
+ node_to_pick = nil
+ node_list.each_node do |node|
+ next if secrets_value.values.include?(node.name)
+ node_to_pick = node.name
+ end
+ node_to_pick ||= secrets_value.values.shuffle.first # all picked already, so pick a random one.
+ node_to_pick
+ end
+ picked_node_name = secrets_value[@node.name]
+ @manager.secrets.set(secrets_key, secrets_value, @node.environment)
+ return node_list[picked_node_name]
+ end
+ else
+ raise ArgumentError.new('pick_node(node_list): node_list cannot be empty')
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/provider.rb b/lib/leap_cli/macros/provider.rb
new file mode 100644
index 00000000..4e74da01
--- /dev/null
+++ b/lib/leap_cli/macros/provider.rb
@@ -0,0 +1,90 @@
+#
+# These macros are intended only for use in provider.json, although they are
+# currently loaded in all .json contexts.
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # returns an array of the service names, including only those services that
+ # are enabled for this environment.
+ #
+ def enabled_services
+ manager.env(self.environment).services[:service_type => :user_service].field(:name).select { |service|
+ manager.nodes[:environment => self.environment][:services => service].any?
+ }
+ end
+
+ #
+ # The webapp will not work unless the service level configuration is precisely defined.
+ # Here, we take what the sysadmin has specified in provider.json and clean it up to
+ # ensure it is OK.
+ #
+ # It would be better to add support for JSON schema.
+ #
+ def service_levels()
+ levels = {}
+ provider.service.levels.each do |name, level|
+ if name =~ /^[0-9]+$/
+ name = name.to_i
+ end
+ levels[name] = level_cleanup(name, level.clone)
+ end
+ levels
+ end
+
+ private
+
+ def print_warning(name, msg)
+ if self.environment
+ provider_str = "provider.json or %s" % ['provider', self.environment, 'json'].join('.')
+ else
+ provider_str = "provider.json"
+ end
+ LeapCli::log :warning, "In #{provider_str}, you have an incorrect definition for service level '#{name}':" do
+ LeapCli::log msg
+ end
+ end
+
+ def level_cleanup(name, level)
+ unless level['name']
+ print_warning(name, 'required field "name" is missing')
+ end
+ unless level['description']
+ print_warning(name, 'required field "description" is missing')
+ end
+ unless level['bandwidth'].nil? || level['bandwidth'] == 'limited'
+ print_warning(name, 'field "bandwidth" must be nil or "limited"')
+ end
+ unless level['rate'].nil? || level['rate'].is_a?(Hash)
+ print_warning(name, 'field "rate" must be nil or a hash (e.g. {"USD":10, "EUR":10})')
+ end
+ possible_services = enabled_services
+ if level['services']
+ level['services'].each do |service|
+ unless possible_services.include? service
+ print_warning(name, "the service '#{service}' does not exist or there are no nodes that provide this service.")
+ LeapCli::Util::bail!
+ end
+ end
+ else
+ level['services'] = possible_services
+ end
+ level['services'] = remap_services(level['services'])
+ level
+ end
+
+ #
+ # the service names that the webapp uses and that leap_platform uses are different. ugh.
+ #
+ SERVICE_MAP = {
+ "mx" => "email",
+ "openvpn" => "eip"
+ }
+ def remap_services(services)
+ services.map {|srv| SERVICE_MAP[srv]}
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/secrets.rb b/lib/leap_cli/macros/secrets.rb
new file mode 100644
index 00000000..8d1feb55
--- /dev/null
+++ b/lib/leap_cli/macros/secrets.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+require 'base32'
+
+module LeapCli
+ module Macro
+
+ #
+ # inserts a named secret, generating it if needed.
+ #
+ # manager.export_secrets should be called later to capture any newly generated secrets.
+ #
+ # +length+ is the character length of the generated password.
+ #
+ def secret(name, length=32)
+ manager.secrets.set(name, @node.environment) { Util::Secret.generate(length) }
+ end
+
+ # inserts a base32 encoded secret
+ def base32_secret(name, length=20)
+ manager.secrets.set(name, @node.environment) { Base32.encode(Util::Secret.generate(length)) }
+ end
+
+ # Picks a random obfsproxy port from given range
+ def rand_range(name, range)
+ manager.secrets.set(name, @node.environment) { rand(range) }
+ end
+
+ #
+ # inserts an hexidecimal secret string, generating it if needed.
+ #
+ # +bit_length+ is the bits in the secret, (ie length of resulting hex string will be bit_length/4)
+ #
+ def hex_secret(name, bit_length=128)
+ manager.secrets.set(name, @node.environment) { Util::Secret.generate_hex(bit_length) }
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/stunnel.rb b/lib/leap_cli/macros/stunnel.rb
new file mode 100644
index 00000000..821bda38
--- /dev/null
+++ b/lib/leap_cli/macros/stunnel.rb
@@ -0,0 +1,106 @@
+##
+## STUNNEL
+##
+
+#
+# About stunnel
+# --------------------------
+#
+# The network looks like this:
+#
+# From the client's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# consumer app -> localhost:accept_port -> connect:connect_port -> ??
+#
+# From the server's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# ?? -> *:accept_port -> localhost:connect_port -> service
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # stunnel configuration for the client side.
+ #
+ # +node_list+ is a ObjectList of nodes running stunnel servers.
+ #
+ # +port+ is the real port of the ultimate service running on the servers
+ # that the client wants to connect to.
+ #
+ # * accept_port is the port on localhost to which local clients
+ # can connect. it is auto generated serially.
+ #
+ # * connect_port is the port on the stunnel server to connect to.
+ # it is auto generated from the +port+ argument.
+ #
+ # generates an entry appropriate to be passed directly to
+ # create_resources(stunnel::service, hiera('..'), defaults)
+ #
+ # local ports are automatically generated, starting at 4000
+ # and incrementing in sorted order (by node name).
+ #
+ def stunnel_client(node_list, port, options={})
+ @next_stunnel_port ||= 4000
+ node_list = listify(node_list)
+ hostnames(node_list) # record the hosts
+ result = Config::ObjectList.new
+ node_list.each_node do |node|
+ if node.name != self.name || options[:include_self]
+ s_port = stunnel_port(port)
+ result["#{node.name}_#{port}"] = Config::Object[
+ 'accept_port', @next_stunnel_port,
+ 'connect', node.domain.internal,
+ 'connect_port', s_port,
+ 'original_port', port
+ ]
+ manager.connections.add(:from => @node.ip_address, :to => node.ip_address, :port => s_port)
+ @next_stunnel_port += 1
+ end
+ end
+ result
+ end
+
+ #
+ # generates a stunnel server entry.
+ #
+ # +port+ is the real port targeted service.
+ #
+ # * `accept_port` is the publicly bound port
+ # * `connect_port` is the port that the local service is running on.
+ #
+ def stunnel_server(port)
+ {
+ "accept_port" => stunnel_port(port),
+ "connect_port" => port
+ }
+ end
+
+ #
+ # lists the ips that connect to this node, on particular ports.
+ #
+ def stunnel_firewall
+ manager.connections.select {|connection|
+ connection['to'] == @node.ip_address
+ }
+ end
+
+ private
+
+ #
+ # maps a real port to a stunnel port (used as the connect_port in the client config
+ # and the accept_port in the server config)
+ #
+ def stunnel_port(port)
+ port = port.to_i
+ if port < 50000
+ return port + 10000
+ else
+ return port - 10000
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/functions/type_of.rb b/lib/puppet/functions/type_of.rb
deleted file mode 100644
index 02cdd4db..00000000
--- a/lib/puppet/functions/type_of.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# Returns the type when passed a value.
-#
-# @example how to compare values' types
-# # compare the types of two values
-# if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") }
-# @example how to compare against an abstract type
-# unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") }
-# unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") }
-#
-# See the documentation for "The Puppet Type System" for more information about types.
-# See the `assert_type()` function for flexible ways to assert the type of a value.
-#
-Puppet::Functions.create_function(:type_of) do
- def type_of(value)
- Puppet::Pops::Types::TypeCalculator.infer_set(value)
- end
-end
diff --git a/lib/puppet/parser/functions/abs.rb b/lib/puppet/parser/functions/abs.rb
deleted file mode 100644
index 11d2d7fe..00000000
--- a/lib/puppet/parser/functions/abs.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# abs.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:abs, :type => :rvalue, :doc => <<-EOS
- Returns the absolute value of a number, for example -34.56 becomes
- 34.56. Takes a single integer and float value as an argument.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "abs(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- # Numbers in Puppet are often string-encoded which is troublesome ...
- if value.is_a?(String)
- if value.match(/^-?(?:\d+)(?:\.\d+){1}$/)
- value = value.to_f
- elsif value.match(/^-?\d+$/)
- value = value.to_i
- else
- raise(Puppet::ParseError, 'abs(): Requires float or ' +
- 'integer to work with')
- end
- end
-
- # We have numeric value to handle ...
- result = value.abs
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/any2array.rb b/lib/puppet/parser/functions/any2array.rb
deleted file mode 100644
index e71407e8..00000000
--- a/lib/puppet/parser/functions/any2array.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# any2array.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:any2array, :type => :rvalue, :doc => <<-EOS
-This converts any object to an array containing that object. Empty argument
-lists are converted to an empty array. Arrays are left untouched. Hashes are
-converted to arrays of alternating keys and values.
- EOS
- ) do |arguments|
-
- if arguments.empty?
- return []
- end
-
- if arguments.length == 1
- if arguments[0].kind_of?(Array)
- return arguments[0]
- elsif arguments[0].kind_of?(Hash)
- result = []
- arguments[0].each do |key, value|
- result << key << value
- end
- return result
- end
- end
-
- return arguments
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/base64.rb b/lib/puppet/parser/functions/base64.rb
deleted file mode 100644
index 617ba31b..00000000
--- a/lib/puppet/parser/functions/base64.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
-
- Base64 encode or decode a string based on the command and the string submitted
-
- Usage:
-
- $encodestring = base64('encode','thestring')
- $decodestring = base64('decode','dGhlc3RyaW5n')
-
- ENDHEREDOC
-
- require 'base64'
-
- raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be = 2)") unless args.length == 2
-
- actions = ['encode','decode']
-
- unless actions.include?(args[0])
- raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'")
- end
-
- unless args[1].is_a?(String)
- raise Puppet::ParseError, ("base64(): the second argument must be a string to base64")
- end
-
- case args[0]
- when 'encode'
- result = Base64.encode64(args[1])
- when 'decode'
- result = Base64.decode64(args[1])
- end
-
- return result
- end
-end
diff --git a/lib/puppet/parser/functions/basename.rb b/lib/puppet/parser/functions/basename.rb
deleted file mode 100644
index f7e44384..00000000
--- a/lib/puppet/parser/functions/basename.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:basename, :type => :rvalue, :doc => <<-EOS
- Strips directory (and optional suffix) from a filename
- EOS
- ) do |arguments|
-
- if arguments.size < 1 then
- raise(Puppet::ParseError, "basename(): No arguments given")
- elsif arguments.size > 2 then
- raise(Puppet::ParseError, "basename(): Too many arguments given (#{arguments.size})")
- else
-
- unless arguments[0].is_a?(String)
- raise(Puppet::ParseError, 'basename(): Requires string as first argument')
- end
-
- if arguments.size == 1 then
- rv = File.basename(arguments[0])
- elsif arguments.size == 2 then
-
- unless arguments[1].is_a?(String)
- raise(Puppet::ParseError, 'basename(): Requires string as second argument')
- end
-
- rv = File.basename(arguments[0], arguments[1])
- end
-
- end
-
- return rv
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/bool2num.rb b/lib/puppet/parser/functions/bool2num.rb
deleted file mode 100644
index 6ad6cf4e..00000000
--- a/lib/puppet/parser/functions/bool2num.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# bool2num.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:bool2num, :type => :rvalue, :doc => <<-EOS
- Converts a boolean to a number. Converts the values:
- false, f, 0, n, and no to 0
- true, t, 1, y, and yes to 1
- Requires a single boolean or string as an input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "bool2num(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = function_str2bool([arguments[0]])
-
- # We have real boolean values as well ...
- result = value ? 1 : 0
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/bool2str.rb b/lib/puppet/parser/functions/bool2str.rb
deleted file mode 100644
index fcd37917..00000000
--- a/lib/puppet/parser/functions/bool2str.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# bool2str.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:bool2str, :type => :rvalue, :doc => <<-EOS
- Converts a boolean to a string.
- Requires a single boolean as an input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "bool2str(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
- klass = value.class
-
- # We can have either true or false, and nothing else
- unless [FalseClass, TrueClass].include?(klass)
- raise(Puppet::ParseError, 'bool2str(): Requires a boolean to work with')
- end
-
- return value.to_s
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/camelcase.rb b/lib/puppet/parser/functions/camelcase.rb
deleted file mode 100644
index d7f43f7a..00000000
--- a/lib/puppet/parser/functions/camelcase.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# camelcase.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:camelcase, :type => :rvalue, :doc => <<-EOS
-Converts the case of a string or all strings in an array to camel case.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "camelcase(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
- klass = value.class
-
- unless [Array, String].include?(klass)
- raise(Puppet::ParseError, 'camelcase(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.split('_').map{|e| e.capitalize}.join : i }
- else
- result = value.split('_').map{|e| e.capitalize}.join
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/capitalize.rb b/lib/puppet/parser/functions/capitalize.rb
deleted file mode 100644
index 98b2d16c..00000000
--- a/lib/puppet/parser/functions/capitalize.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# capitalize.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:capitalize, :type => :rvalue, :doc => <<-EOS
- Capitalizes the first letter of a string or array of strings.
- Requires either a single string or an array as an input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "capitalize(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'capitalize(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.capitalize : i }
- else
- result = value.capitalize
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/chomp.rb b/lib/puppet/parser/functions/chomp.rb
deleted file mode 100644
index c55841e3..00000000
--- a/lib/puppet/parser/functions/chomp.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# chomp.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS'
- Removes the record separator from the end of a string or an array of
- strings, for example `hello\n` becomes `hello`.
- Requires a single string or array as an input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "chomp(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'chomp(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.chomp : i }
- else
- result = value.chomp
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/chop.rb b/lib/puppet/parser/functions/chop.rb
deleted file mode 100644
index b24ab785..00000000
--- a/lib/puppet/parser/functions/chop.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# chop.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:chop, :type => :rvalue, :doc => <<-'EOS'
- Returns a new string with the last character removed. If the string ends
- with `\r\n`, both characters are removed. Applying chop to an empty
- string returns an empty string. If you wish to merely remove record
- separators then you should use the `chomp` function.
- Requires a string or array of strings as input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "chop(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'chop(): Requires either an ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.chop : i }
- else
- result = value.chop
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/concat.rb b/lib/puppet/parser/functions/concat.rb
deleted file mode 100644
index 618e62d4..00000000
--- a/lib/puppet/parser/functions/concat.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# concat.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:concat, :type => :rvalue, :doc => <<-EOS
-Appends the contents of multiple arrays into array 1.
-
-*Example:*
-
- concat(['1','2','3'],['4','5','6'],['7','8','9'])
-
-Would result in:
-
- ['1','2','3','4','5','6','7','8','9']
- EOS
- ) do |arguments|
-
- # Check that more than 2 arguments have been given ...
- raise(Puppet::ParseError, "concat(): Wrong number of arguments " +
- "given (#{arguments.size} for < 2)") if arguments.size < 2
-
- a = arguments[0]
-
- # Check that the first parameter is an array
- unless a.is_a?(Array)
- raise(Puppet::ParseError, 'concat(): Requires array to work with')
- end
-
- result = a
- arguments.shift
-
- arguments.each do |x|
- result = result + Array(x)
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/count.rb b/lib/puppet/parser/functions/count.rb
deleted file mode 100644
index 52de1b8a..00000000
--- a/lib/puppet/parser/functions/count.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS
-Takes an array as first argument and an optional second argument.
-Count the number of elements in array that matches second argument.
-If called with only an array it counts the number of elements that are not nil/undef.
- EOS
- ) do |args|
-
- if (args.size > 2) then
- raise(ArgumentError, "count(): Wrong number of arguments "+
- "given #{args.size} for 1 or 2.")
- end
-
- collection, item = args
-
- if item then
- collection.count item
- else
- collection.count { |obj| obj != nil && obj != :undef && obj != '' }
- end
- end
-end
diff --git a/lib/puppet/parser/functions/deep_merge.rb b/lib/puppet/parser/functions/deep_merge.rb
deleted file mode 100644
index 6df32e9c..00000000
--- a/lib/puppet/parser/functions/deep_merge.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
- Recursively merges two or more hashes together and returns the resulting hash.
-
- For example:
-
- $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
- $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } }
- $merged_hash = deep_merge($hash1, $hash2)
- # The resulting hash is equivalent to:
- # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } }
-
- When there is a duplicate key that is a hash, they are recursively merged.
- When there is a duplicate key that is not a hash, the key in the rightmost hash will "win."
-
- ENDHEREDOC
-
- if args.length < 2
- raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)")
- end
-
- deep_merge = Proc.new do |hash1,hash2|
- hash1.merge(hash2) do |key,old_value,new_value|
- if old_value.is_a?(Hash) && new_value.is_a?(Hash)
- deep_merge.call(old_value, new_value)
- else
- new_value
- end
- end
- end
-
- result = Hash.new
- args.each do |arg|
- next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef
- # If the argument was not a hash, skip it.
- unless arg.is_a?(Hash)
- raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments"
- end
-
- result = deep_merge.call(result, arg)
- end
- return( result )
- end
-end
diff --git a/lib/puppet/parser/functions/defined_with_params.rb b/lib/puppet/parser/functions/defined_with_params.rb
deleted file mode 100644
index d7df306c..00000000
--- a/lib/puppet/parser/functions/defined_with_params.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# Test whether a given class or definition is defined
-require 'puppet/parser/functions'
-
-Puppet::Parser::Functions.newfunction(:defined_with_params,
- :type => :rvalue,
- :doc => <<-'ENDOFDOC'
-Takes a resource reference and an optional hash of attributes.
-
-Returns true if a resource with the specified attributes has already been added
-to the catalog, and false otherwise.
-
- user { 'dan':
- ensure => present,
- }
-
- if ! defined_with_params(User[dan], {'ensure' => 'present' }) {
- user { 'dan': ensure => present, }
- }
-ENDOFDOC
-) do |vals|
- reference, params = vals
- raise(ArgumentError, 'Must specify a reference') unless reference
- if (! params) || params == ''
- params = {}
- end
- ret = false
- if resource = findresource(reference.to_s)
- matches = params.collect do |key, value|
- resource[key] == value
- end
- ret = params.empty? || !matches.include?(false)
- end
- Puppet.debug("Resource #{reference} was not determined to be defined")
- ret
-end
diff --git a/lib/puppet/parser/functions/delete.rb b/lib/puppet/parser/functions/delete.rb
deleted file mode 100644
index f548b444..00000000
--- a/lib/puppet/parser/functions/delete.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# delete.rb
-#
-
-# TODO(Krzysztof Wilczynski): We need to add support for regular expression ...
-
-module Puppet::Parser::Functions
- newfunction(:delete, :type => :rvalue, :doc => <<-EOS
-Deletes all instances of a given element from an array, substring from a
-string, or key from a hash.
-
-*Examples:*
-
- delete(['a','b','c','b'], 'b')
- Would return: ['a','c']
-
- delete({'a'=>1,'b'=>2,'c'=>3}, 'b')
- Would return: {'a'=>1,'c'=>3}
-
- delete({'a'=>1,'b'=>2,'c'=>3}, ['b','c'])
- Would return: {'a'=>1}
-
- delete('abracadabra', 'bra')
- Would return: 'acada'
- EOS
- ) do |arguments|
-
- if (arguments.size != 2) then
- raise(Puppet::ParseError, "delete(): Wrong number of arguments "+
- "given #{arguments.size} for 2.")
- end
-
- collection = arguments[0].dup
- Array(arguments[1]).each do |item|
- case collection
- when Array, Hash
- collection.delete item
- when String
- collection.gsub! item, ''
- else
- raise(TypeError, "delete(): First argument must be an Array, " +
- "String, or Hash. Given an argument of class #{collection.class}.")
- end
- end
- collection
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/delete_at.rb b/lib/puppet/parser/functions/delete_at.rb
deleted file mode 100644
index 3eb4b537..00000000
--- a/lib/puppet/parser/functions/delete_at.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# delete_at.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:delete_at, :type => :rvalue, :doc => <<-EOS
-Deletes a determined indexed value from an array.
-
-*Examples:*
-
- delete_at(['a','b','c'], 1)
-
-Would return: ['a','c']
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "delete_at(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size < 2
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'delete_at(): Requires array to work with')
- end
-
- index = arguments[1]
-
- if index.is_a?(String) and not index.match(/^\d+$/)
- raise(Puppet::ParseError, 'delete_at(): You must provide ' +
- 'non-negative numeric index')
- end
-
- result = array.clone
-
- # Numbers in Puppet are often string-encoded which is troublesome ...
- index = index.to_i
-
- if index > result.size - 1 # First element is at index 0 is it not?
- raise(Puppet::ParseError, 'delete_at(): Given index ' +
- 'exceeds size of array given')
- end
-
- result.delete_at(index) # We ignore the element that got deleted ...
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/delete_undef_values.rb b/lib/puppet/parser/functions/delete_undef_values.rb
deleted file mode 100644
index f94d4da8..00000000
--- a/lib/puppet/parser/functions/delete_undef_values.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS
-Returns a copy of input hash or array with all undefs deleted.
-
-*Examples:*
-
- $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})
-
-Would return: {a => 'A', b => '', d => false}
-
- $array = delete_undef_values(['A','',undef,false])
-
-Would return: ['A','',false]
-
- EOS
- ) do |args|
-
- raise(Puppet::ParseError,
- "delete_undef_values(): Wrong number of arguments given " +
- "(#{args.size})") if args.size < 1
-
- unless args[0].is_a? Array or args[0].is_a? Hash
- raise(Puppet::ParseError,
- "delete_undef_values(): expected an array or hash, got #{args[0]} type #{args[0].class} ")
- end
- result = args[0].dup
- if result.is_a?(Hash)
- result.delete_if {|key, val| val.equal? :undef}
- elsif result.is_a?(Array)
- result.delete :undef
- end
- result
- end
-end
diff --git a/lib/puppet/parser/functions/delete_values.rb b/lib/puppet/parser/functions/delete_values.rb
deleted file mode 100644
index f6c8c0e6..00000000
--- a/lib/puppet/parser/functions/delete_values.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS
-Deletes all instances of a given value from a hash.
-
-*Examples:*
-
- delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B')
-
-Would return: {'a'=>'A','c'=>'C','B'=>'D'}
-
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError,
- "delete_values(): Wrong number of arguments given " +
- "(#{arguments.size} of 2)") if arguments.size != 2
-
- hash, item = arguments
-
- if not hash.is_a?(Hash)
- raise(TypeError, "delete_values(): First argument must be a Hash. " + \
- "Given an argument of class #{hash.class}.")
- end
- hash.dup.delete_if { |key, val| item == val }
- end
-end
diff --git a/lib/puppet/parser/functions/difference.rb b/lib/puppet/parser/functions/difference.rb
deleted file mode 100644
index cd258f75..00000000
--- a/lib/puppet/parser/functions/difference.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# difference.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:difference, :type => :rvalue, :doc => <<-EOS
-This function returns the difference between two arrays.
-The returned array is a copy of the original array, removing any items that
-also appear in the second array.
-
-*Examples:*
-
- difference(["a","b","c"],["b","c","d"])
-
-Would return: ["a"]
- EOS
- ) do |arguments|
-
- # Two arguments are required
- raise(Puppet::ParseError, "difference(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size != 2
-
- first = arguments[0]
- second = arguments[1]
-
- unless first.is_a?(Array) && second.is_a?(Array)
- raise(Puppet::ParseError, 'difference(): Requires 2 arrays')
- end
-
- result = first - second
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/dirname.rb b/lib/puppet/parser/functions/dirname.rb
deleted file mode 100644
index ea8cc1e0..00000000
--- a/lib/puppet/parser/functions/dirname.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:dirname, :type => :rvalue, :doc => <<-EOS
- Returns the dirname of a path.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "dirname(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- path = arguments[0]
- return File.dirname(path)
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/downcase.rb b/lib/puppet/parser/functions/downcase.rb
deleted file mode 100644
index 040b84f5..00000000
--- a/lib/puppet/parser/functions/downcase.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# downcase.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:downcase, :type => :rvalue, :doc => <<-EOS
-Converts the case of a string or all strings in an array to lower case.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "downcase(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'downcase(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.downcase : i }
- else
- result = value.downcase
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/empty.rb b/lib/puppet/parser/functions/empty.rb
deleted file mode 100644
index cca620fa..00000000
--- a/lib/puppet/parser/functions/empty.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# empty.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:empty, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable is empty.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "empty(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(String)
- raise(Puppet::ParseError, 'empty(): Requires either ' +
- 'array, hash or string to work with')
- end
-
- result = value.empty?
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/ensure_packages.rb b/lib/puppet/parser/functions/ensure_packages.rb
deleted file mode 100644
index f1da4aaa..00000000
--- a/lib/puppet/parser/functions/ensure_packages.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# ensure_packages.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS
-Takes a list of packages and only installs them if they don't already exist.
-It optionally takes a hash as a second parameter that will be passed as the
-third argument to the ensure_resource() function.
- EOS
- ) do |arguments|
-
- if arguments.size > 2 or arguments.size == 0
- raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments " +
- "given (#{arguments.size} for 1 or 2)")
- elsif arguments.size == 2 and !arguments[1].is_a?(Hash)
- raise(Puppet::ParseError, 'ensure_packages(): Requires second argument to be a Hash')
- end
-
- packages = Array(arguments[0])
-
- if arguments[1]
- defaults = { 'ensure' => 'present' }.merge(arguments[1])
- else
- defaults = { 'ensure' => 'present' }
- end
-
- Puppet::Parser::Functions.function(:ensure_resource)
- packages.each { |package_name|
- function_ensure_resource(['package', package_name, defaults ])
- }
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/ensure_resource.rb b/lib/puppet/parser/functions/ensure_resource.rb
deleted file mode 100644
index 1ba6a447..00000000
--- a/lib/puppet/parser/functions/ensure_resource.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# Test whether a given class or definition is defined
-require 'puppet/parser/functions'
-
-Puppet::Parser::Functions.newfunction(:ensure_resource,
- :type => :statement,
- :doc => <<-'ENDOFDOC'
-Takes a resource type, title, and a list of attributes that describe a
-resource.
-
- user { 'dan':
- ensure => present,
- }
-
-This example only creates the resource if it does not already exist:
-
- ensure_resource('user', 'dan', {'ensure' => 'present' })
-
-If the resource already exists but does not match the specified parameters,
-this function will attempt to recreate the resource leading to a duplicate
-resource definition error.
-
-An array of resources can also be passed in and each will be created with
-the type and parameters specified if it doesn't already exist.
-
- ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})
-
-ENDOFDOC
-) do |vals|
- type, title, params = vals
- raise(ArgumentError, 'Must specify a type') unless type
- raise(ArgumentError, 'Must specify a title') unless title
- params ||= {}
-
- items = [title].flatten
-
- items.each do |item|
- Puppet::Parser::Functions.function(:defined_with_params)
- if function_defined_with_params(["#{type}[#{item}]", params])
- Puppet.debug("Resource #{type}[#{item}] with params #{params} not created because it already exists")
- else
- Puppet.debug("Create new resource #{type}[#{item}] with params #{params}")
- Puppet::Parser::Functions.function(:create_resources)
- function_create_resources([type.capitalize, { item => params }])
- end
- end
-end
diff --git a/lib/puppet/parser/functions/flatten.rb b/lib/puppet/parser/functions/flatten.rb
deleted file mode 100644
index a1ed1832..00000000
--- a/lib/puppet/parser/functions/flatten.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# flatten.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:flatten, :type => :rvalue, :doc => <<-EOS
-This function flattens any deeply nested arrays and returns a single flat array
-as a result.
-
-*Examples:*
-
- flatten(['a', ['b', ['c']]])
-
-Would return: ['a','b','c']
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "flatten(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'flatten(): Requires array to work with')
- end
-
- result = array.flatten
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/floor.rb b/lib/puppet/parser/functions/floor.rb
deleted file mode 100644
index 9a6f014d..00000000
--- a/lib/puppet/parser/functions/floor.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:floor, :type => :rvalue, :doc => <<-EOS
- Returns the largest integer less or equal to the argument.
- Takes a single numeric value as an argument.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "floor(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- begin
- arg = Float(arguments[0])
- rescue TypeError, ArgumentError => e
- raise(Puppet::ParseError, "floor(): Wrong argument type " +
- "given (#{arguments[0]} for Numeric)")
- end
-
- raise(Puppet::ParseError, "floor(): Wrong argument type " +
- "given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false
-
- arg.floor
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/fqdn_rotate.rb b/lib/puppet/parser/functions/fqdn_rotate.rb
deleted file mode 100644
index 7f4d37d0..00000000
--- a/lib/puppet/parser/functions/fqdn_rotate.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# fqdn_rotate.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:fqdn_rotate, :type => :rvalue, :doc => <<-EOS
-Rotates an array a random number of times based on a nodes fqdn.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
- require 'digest/md5'
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'fqdn_rotate(): Requires either ' +
- 'array or string to work with')
- end
-
- result = value.clone
-
- string = value.is_a?(String) ? true : false
-
- # Check whether it makes sense to rotate ...
- return result if result.size <= 1
-
- # We turn any string value into an array to be able to rotate ...
- result = string ? result.split('') : result
-
- elements = result.size
-
- srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),arguments].join(':')).hex)
- rand(elements).times {
- result.push result.shift
- }
-
- result = string ? result.join : result
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/get_module_path.rb b/lib/puppet/parser/functions/get_module_path.rb
deleted file mode 100644
index 1421b91f..00000000
--- a/lib/puppet/parser/functions/get_module_path.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:get_module_path, :type =>:rvalue, :doc => <<-EOT
- Returns the absolute path of the specified module for the current
- environment.
-
- Example:
- $module_path = get_module_path('stdlib')
- EOT
- ) do |args|
- raise(Puppet::ParseError, "get_module_path(): Wrong number of arguments, expects one") unless args.size == 1
- if module_path = Puppet::Module.find(args[0], compiler.environment.to_s)
- module_path.path
- else
- raise(Puppet::ParseError, "Could not find module #{args[0]} in environment #{compiler.environment}")
- end
- end
-end
diff --git a/lib/puppet/parser/functions/getparam.rb b/lib/puppet/parser/functions/getparam.rb
deleted file mode 100644
index 6d510069..00000000
--- a/lib/puppet/parser/functions/getparam.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# Test whether a given class or definition is defined
-require 'puppet/parser/functions'
-
-Puppet::Parser::Functions.newfunction(:getparam,
- :type => :rvalue,
- :doc => <<-'ENDOFDOC'
-Takes a resource reference and name of the parameter and
-returns value of resource's parameter.
-
-*Examples:*
-
- define example_resource($param) {
- }
-
- example_resource { "example_resource_instance":
- param => "param_value"
- }
-
- getparam(Example_resource["example_resource_instance"], "param")
-
-Would return: param_value
-ENDOFDOC
-) do |vals|
- reference, param = vals
- raise(ArgumentError, 'Must specify a reference') unless reference
- raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String
-
- return '' if param.empty?
-
- if resource = findresource(reference.to_s)
- return resource[param] if resource[param]
- end
-
- return ''
-end
diff --git a/lib/puppet/parser/functions/getvar.rb b/lib/puppet/parser/functions/getvar.rb
deleted file mode 100644
index fb336b6a..00000000
--- a/lib/puppet/parser/functions/getvar.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:getvar, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
- Lookup a variable in a remote namespace.
-
- For example:
-
- $foo = getvar('site::data::foo')
- # Equivalent to $foo = $site::data::foo
-
- This is useful if the namespace itself is stored in a string:
-
- $datalocation = 'site::data'
- $bar = getvar("${datalocation}::bar")
- # Equivalent to $bar = $site::data::bar
- ENDHEREDOC
-
- unless args.length == 1
- raise Puppet::ParseError, ("getvar(): wrong number of arguments (#{args.length}; must be 1)")
- end
-
- begin
- self.lookupvar("#{args[0]}")
- rescue Puppet::ParseError # Eat the exception if strict_variables = true is set
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/grep.rb b/lib/puppet/parser/functions/grep.rb
deleted file mode 100644
index ceba9ecc..00000000
--- a/lib/puppet/parser/functions/grep.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# grep.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:grep, :type => :rvalue, :doc => <<-EOS
-This function searches through an array and returns any elements that match
-the provided regular expression.
-
-*Examples:*
-
- grep(['aaa','bbb','ccc','aaaddd'], 'aaa')
-
-Would return:
-
- ['aaa','aaaddd']
- EOS
- ) do |arguments|
-
- if (arguments.size != 2) then
- raise(Puppet::ParseError, "grep(): Wrong number of arguments "+
- "given #{arguments.size} for 2")
- end
-
- a = arguments[0]
- pattern = Regexp.new(arguments[1])
-
- a.grep(pattern)
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/has_interface_with.rb b/lib/puppet/parser/functions/has_interface_with.rb
deleted file mode 100644
index 36915246..00000000
--- a/lib/puppet/parser/functions/has_interface_with.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# has_interface_with
-#
-
-module Puppet::Parser::Functions
- newfunction(:has_interface_with, :type => :rvalue, :doc => <<-EOS
-Returns boolean based on kind and value:
- * macaddress
- * netmask
- * ipaddress
- * network
-
-has_interface_with("macaddress", "x:x:x:x:x:x")
-has_interface_with("ipaddress", "127.0.0.1") => true
-etc.
-
-If no "kind" is given, then the presence of the interface is checked:
-has_interface_with("lo") => true
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "has_interface_with(): Wrong number of arguments " +
- "given (#{args.size} for 1 or 2)") if args.size < 1 or args.size > 2
-
- interfaces = lookupvar('interfaces')
-
- # If we do not have any interfaces, then there are no requested attributes
- return false if (interfaces == :undefined || interfaces.nil?)
-
- interfaces = interfaces.split(',')
-
- if args.size == 1
- return interfaces.member?(args[0])
- end
-
- kind, value = args
-
- # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable
- # https://tickets.puppetlabs.com/browse/PUP-3597
- factval = nil
- catch :undefined_variable do
- factval = lookupvar(kind)
- end
- if factval == value
- return true
- end
-
- result = false
- interfaces.each do |iface|
- iface.downcase!
- factval = nil
- begin
- # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable
- # https://tickets.puppetlabs.com/browse/PUP-3597
- catch :undefined_variable do
- factval = lookupvar("#{kind}_#{iface}")
- end
- rescue Puppet::ParseError # Eat the exception if strict_variables = true is set
- end
- if value == factval
- result = true
- break
- end
- end
-
- result
- end
-end
diff --git a/lib/puppet/parser/functions/has_ip_address.rb b/lib/puppet/parser/functions/has_ip_address.rb
deleted file mode 100644
index 842c8ec6..00000000
--- a/lib/puppet/parser/functions/has_ip_address.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# has_ip_address
-#
-
-module Puppet::Parser::Functions
- newfunction(:has_ip_address, :type => :rvalue, :doc => <<-EOS
-Returns true if the client has the requested IP address on some interface.
-
-This function iterates through the 'interfaces' fact and checks the
-'ipaddress_IFACE' facts, performing a simple string comparison.
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "has_ip_address(): Wrong number of arguments " +
- "given (#{args.size} for 1)") if args.size != 1
-
- Puppet::Parser::Functions.autoloader.load(:has_interface_with) \
- unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with)
-
- function_has_interface_with(['ipaddress', args[0]])
-
- end
-end
-
-# vim:sts=2 sw=2
diff --git a/lib/puppet/parser/functions/has_ip_network.rb b/lib/puppet/parser/functions/has_ip_network.rb
deleted file mode 100644
index 9ccf9024..00000000
--- a/lib/puppet/parser/functions/has_ip_network.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# has_ip_network
-#
-
-module Puppet::Parser::Functions
- newfunction(:has_ip_network, :type => :rvalue, :doc => <<-EOS
-Returns true if the client has an IP address within the requested network.
-
-This function iterates through the 'interfaces' fact and checks the
-'network_IFACE' facts, performing a simple string comparision.
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "has_ip_network(): Wrong number of arguments " +
- "given (#{args.size} for 1)") if args.size != 1
-
- Puppet::Parser::Functions.autoloader.load(:has_interface_with) \
- unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with)
-
- function_has_interface_with(['network', args[0]])
-
- end
-end
-
-# vim:sts=2 sw=2
diff --git a/lib/puppet/parser/functions/has_key.rb b/lib/puppet/parser/functions/has_key.rb
deleted file mode 100644
index 4657cc29..00000000
--- a/lib/puppet/parser/functions/has_key.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:has_key, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
- Determine if a hash has a certain key value.
-
- Example:
-
- $my_hash = {'key_one' => 'value_one'}
- if has_key($my_hash, 'key_two') {
- notice('we will not reach here')
- }
- if has_key($my_hash, 'key_one') {
- notice('this will be printed')
- }
-
- ENDHEREDOC
-
- unless args.length == 2
- raise Puppet::ParseError, ("has_key(): wrong number of arguments (#{args.length}; must be 2)")
- end
- unless args[0].is_a?(Hash)
- raise Puppet::ParseError, "has_key(): expects the first argument to be a hash, got #{args[0].inspect} which is of type #{args[0].class}"
- end
- args[0].has_key?(args[1])
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/hash.rb b/lib/puppet/parser/functions/hash.rb
deleted file mode 100644
index 8cc4823b..00000000
--- a/lib/puppet/parser/functions/hash.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# hash.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:hash, :type => :rvalue, :doc => <<-EOS
-This function converts an array into a hash.
-
-*Examples:*
-
- hash(['a',1,'b',2,'c',3])
-
-Would return: {'a'=>1,'b'=>2,'c'=>3}
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "hash(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'hash(): Requires array to work with')
- end
-
- result = {}
-
- begin
- # This is to make it compatible with older version of Ruby ...
- array = array.flatten
- result = Hash[*array]
- rescue Exception
- raise(Puppet::ParseError, 'hash(): Unable to compute ' +
- 'hash from array given')
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/intersection.rb b/lib/puppet/parser/functions/intersection.rb
deleted file mode 100644
index 48f02e9d..00000000
--- a/lib/puppet/parser/functions/intersection.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# intersection.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:intersection, :type => :rvalue, :doc => <<-EOS
-This function returns an array an intersection of two.
-
-*Examples:*
-
- intersection(["a","b","c"],["b","c","d"])
-
-Would return: ["b","c"]
- EOS
- ) do |arguments|
-
- # Two arguments are required
- raise(Puppet::ParseError, "intersection(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size != 2
-
- first = arguments[0]
- second = arguments[1]
-
- unless first.is_a?(Array) && second.is_a?(Array)
- raise(Puppet::ParseError, 'intersection(): Requires 2 arrays')
- end
-
- result = first & second
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_array.rb b/lib/puppet/parser/functions/is_array.rb
deleted file mode 100644
index b39e184a..00000000
--- a/lib/puppet/parser/functions/is_array.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# is_array.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_array, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is an array.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "is_array(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- type = arguments[0]
-
- result = type.is_a?(Array)
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_bool.rb b/lib/puppet/parser/functions/is_bool.rb
deleted file mode 100644
index 8bbdbc8a..00000000
--- a/lib/puppet/parser/functions/is_bool.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# is_bool.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is a boolean.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "is_bool(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- type = arguments[0]
-
- result = type.is_a?(TrueClass) || type.is_a?(FalseClass)
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_domain_name.rb b/lib/puppet/parser/functions/is_domain_name.rb
deleted file mode 100644
index b3fee965..00000000
--- a/lib/puppet/parser/functions/is_domain_name.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# is_domain_name.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_domain_name, :type => :rvalue, :doc => <<-EOS
-Returns true if the string passed to this function is a syntactically correct domain name.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_domain_name(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- domain = arguments[0]
-
- # Limits (rfc1035, 3.1)
- domain_max_length=255
- label_min_length=1
- label_max_length=63
-
- # Only allow string types
- return false unless domain.is_a?(String)
-
- # Allow ".", it is the top level domain
- return true if domain == '.'
-
- # Remove the final dot, if present.
- domain.chomp!('.')
-
- # Check the whole domain
- return false if domain.empty?
- return false if domain.length > domain_max_length
-
- # Check each label in the domain
- labels = domain.split('.')
- vlabels = labels.each do |label|
- break if label.length < label_min_length
- break if label.length > label_max_length
- break if label[-1..-1] == '-'
- break if label[0..0] == '-'
- break unless /^[a-z\d-]+$/i.match(label)
- end
- return vlabels == labels
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_float.rb b/lib/puppet/parser/functions/is_float.rb
deleted file mode 100644
index a2da9438..00000000
--- a/lib/puppet/parser/functions/is_float.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# is_float.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_float, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is a float.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_float(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- value = arguments[0]
-
- # Only allow Numeric or String types
- return false unless value.is_a?(Numeric) or value.is_a?(String)
-
- if value != value.to_f.to_s and !value.is_a? Float then
- return false
- else
- return true
- end
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_function_available.rb b/lib/puppet/parser/functions/is_function_available.rb
deleted file mode 100644
index 6da82c8c..00000000
--- a/lib/puppet/parser/functions/is_function_available.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# is_function_available.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_function_available, :type => :rvalue, :doc => <<-EOS
-This function accepts a string as an argument, determines whether the
-Puppet runtime has access to a function by that name. It returns a
-true if the function exists, false if not.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_function_available?(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- # Only allow String types
- return false unless arguments[0].is_a?(String)
-
- function = Puppet::Parser::Functions.function(arguments[0].to_sym)
- function.is_a?(String) and not function.empty?
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_hash.rb b/lib/puppet/parser/functions/is_hash.rb
deleted file mode 100644
index ad907f08..00000000
--- a/lib/puppet/parser/functions/is_hash.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# is_hash.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_hash, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is a hash.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "is_hash(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- type = arguments[0]
-
- result = type.is_a?(Hash)
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_integer.rb b/lib/puppet/parser/functions/is_integer.rb
deleted file mode 100644
index c03d28df..00000000
--- a/lib/puppet/parser/functions/is_integer.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# is_integer.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is an Integer or
-a decimal (base 10) integer in String form. The string may
-start with a '-' (minus). A value of '0' is allowed, but a leading '0' digit may not
-be followed by other digits as this indicates that the value is octal (base 8).
-
-If given any other argument `false` is returned.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_integer(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- value = arguments[0]
-
- # Regex is taken from the lexer of puppet
- # puppet/pops/parser/lexer.rb but modified to match also
- # negative values and disallow numbers prefixed with multiple
- # 0's
- #
- # TODO these parameter should be a constant but I'm not sure
- # if there is no risk to declare it inside of the module
- # Puppet::Parser::Functions
-
- # Integer numbers like
- # -1234568981273
- # 47291
- numeric = %r{^-?(?:(?:[1-9]\d*)|0)$}
-
- if value.is_a? Integer or (value.is_a? String and value.match numeric)
- return true
- else
- return false
- end
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_ip_address.rb b/lib/puppet/parser/functions/is_ip_address.rb
deleted file mode 100644
index a90adabe..00000000
--- a/lib/puppet/parser/functions/is_ip_address.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# is_ip_address.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_ip_address, :type => :rvalue, :doc => <<-EOS
-Returns true if the string passed to this function is a valid IP address.
- EOS
- ) do |arguments|
-
- require 'ipaddr'
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_ip_address(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- begin
- ip = IPAddr.new(arguments[0])
- rescue ArgumentError
- return false
- end
-
- if ip.ipv4? or ip.ipv6? then
- return true
- else
- return false
- end
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_mac_address.rb b/lib/puppet/parser/functions/is_mac_address.rb
deleted file mode 100644
index 1b3088a2..00000000
--- a/lib/puppet/parser/functions/is_mac_address.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# is_mac_address.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_mac_address, :type => :rvalue, :doc => <<-EOS
-Returns true if the string passed to this function is a valid mac address.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_mac_address(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- mac = arguments[0]
-
- if /^[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}$/.match(mac) then
- return true
- else
- return false
- end
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_numeric.rb b/lib/puppet/parser/functions/is_numeric.rb
deleted file mode 100644
index e7e1d2a7..00000000
--- a/lib/puppet/parser/functions/is_numeric.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# is_numeric.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS
-Returns true if the given argument is a Numeric (Integer or Float),
-or a String containing either a valid integer in decimal base 10 form, or
-a valid floating point string representation.
-
-The function recognizes only decimal (base 10) integers and float but not
-integers in hex (base 16) or octal (base 8) form.
-
-The string representation may start with a '-' (minus). If a decimal '.' is used,
-it must be followed by at least one digit.
-
-Valid examples:
-
- 77435
- 10e-12
- -8475
- 0.2343
- -23.561e3
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "is_numeric(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- value = arguments[0]
-
- # Regex is taken from the lexer of puppet
- # puppet/pops/parser/lexer.rb but modified to match also
- # negative values and disallow invalid octal numbers or
- # numbers prefixed with multiple 0's (except in hex numbers)
- #
- # TODO these parameters should be constants but I'm not sure
- # if there is no risk to declare them inside of the module
- # Puppet::Parser::Functions
-
- # TODO decide if this should be used
- # HEX numbers like
- # 0xaa230F
- # 0X1234009C
- # 0x0012
- # -12FcD
- #numeric_hex = %r{^-?0[xX][0-9A-Fa-f]+$}
-
- # TODO decide if this should be used
- # OCTAL numbers like
- # 01234567
- # -045372
- #numeric_oct = %r{^-?0[1-7][0-7]*$}
-
- # Integer/Float numbers like
- # -0.1234568981273
- # 47291
- # 42.12345e-12
- numeric = %r{^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$}
-
- if value.is_a? Numeric or (value.is_a? String and (
- value.match(numeric) #or
- # value.match(numeric_hex) or
- # value.match(numeric_oct)
- ))
- return true
- else
- return false
- end
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/is_string.rb b/lib/puppet/parser/functions/is_string.rb
deleted file mode 100644
index f5bef045..00000000
--- a/lib/puppet/parser/functions/is_string.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# is_string.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:is_string, :type => :rvalue, :doc => <<-EOS
-Returns true if the variable passed to this function is a string.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "is_string(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- type = arguments[0]
-
- result = type.is_a?(String)
-
- if result and (type == type.to_f.to_s or type == type.to_i.to_s) then
- return false
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/join.rb b/lib/puppet/parser/functions/join.rb
deleted file mode 100644
index 6c0a6ba0..00000000
--- a/lib/puppet/parser/functions/join.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# join.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:join, :type => :rvalue, :doc => <<-EOS
-This function joins an array into a string using a separator.
-
-*Examples:*
-
- join(['a','b','c'], ",")
-
-Would result in: "a,b,c"
- EOS
- ) do |arguments|
-
- # Technically we support two arguments but only first is mandatory ...
- raise(Puppet::ParseError, "join(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'join(): Requires array to work with')
- end
-
- suffix = arguments[1] if arguments[1]
-
- if suffix
- unless suffix.is_a?(String)
- raise(Puppet::ParseError, 'join(): Requires string to work with')
- end
- end
-
- result = suffix ? array.join(suffix) : array.join
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/join_keys_to_values.rb b/lib/puppet/parser/functions/join_keys_to_values.rb
deleted file mode 100644
index e9924fe2..00000000
--- a/lib/puppet/parser/functions/join_keys_to_values.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-#
-# join.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:join_keys_to_values, :type => :rvalue, :doc => <<-EOS
-This function joins each key of a hash to that key's corresponding value with a
-separator. Keys and values are cast to strings. The return value is an array in
-which each element is one joined key/value pair.
-
-*Examples:*
-
- join_keys_to_values({'a'=>1,'b'=>2}, " is ")
-
-Would result in: ["a is 1","b is 2"]
- EOS
- ) do |arguments|
-
- # Validate the number of arguments.
- if arguments.size != 2
- raise(Puppet::ParseError, "join_keys_to_values(): Takes exactly two " +
- "arguments, but #{arguments.size} given.")
- end
-
- # Validate the first argument.
- hash = arguments[0]
- if not hash.is_a?(Hash)
- raise(TypeError, "join_keys_to_values(): The first argument must be a " +
- "hash, but a #{hash.class} was given.")
- end
-
- # Validate the second argument.
- separator = arguments[1]
- if not separator.is_a?(String)
- raise(TypeError, "join_keys_to_values(): The second argument must be a " +
- "string, but a #{separator.class} was given.")
- end
-
- # Join the keys to their values.
- hash.map do |k,v|
- String(k) + separator + String(v)
- end
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/keys.rb b/lib/puppet/parser/functions/keys.rb
deleted file mode 100644
index f0d13b64..00000000
--- a/lib/puppet/parser/functions/keys.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# keys.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:keys, :type => :rvalue, :doc => <<-EOS
-Returns the keys of a hash as an array.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "keys(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- hash = arguments[0]
-
- unless hash.is_a?(Hash)
- raise(Puppet::ParseError, 'keys(): Requires hash to work with')
- end
-
- result = hash.keys
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/loadyaml.rb b/lib/puppet/parser/functions/loadyaml.rb
deleted file mode 100644
index 10c40050..00000000
--- a/lib/puppet/parser/functions/loadyaml.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:loadyaml, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
- Load a YAML file containing an array, string, or hash, and return the data
- in the corresponding native data type.
-
- For example:
-
- $myhash = loadyaml('/etc/puppet/data/myhash.yaml')
- ENDHEREDOC
-
- unless args.length == 1
- raise Puppet::ParseError, ("loadyaml(): wrong number of arguments (#{args.length}; must be 1)")
- end
-
- YAML.load_file(args[0])
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/lstrip.rb b/lib/puppet/parser/functions/lstrip.rb
deleted file mode 100644
index 624e4c84..00000000
--- a/lib/puppet/parser/functions/lstrip.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# lstrip.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:lstrip, :type => :rvalue, :doc => <<-EOS
-Strips leading spaces to the left of a string.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "lstrip(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'lstrip(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.lstrip : i }
- else
- result = value.lstrip
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/max.rb b/lib/puppet/parser/functions/max.rb
deleted file mode 100644
index 60fb94ac..00000000
--- a/lib/puppet/parser/functions/max.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:max, :type => :rvalue, :doc => <<-EOS
- Returns the highest value of all arguments.
- Requires at least one argument.
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "max(): Wrong number of arguments " +
- "need at least one") if args.size == 0
-
- # Sometimes we get numbers as numerics and sometimes as strings.
- # We try to compare them as numbers when possible
- return args.max do |a,b|
- if a.to_s =~ /\A-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then
- a.to_f <=> b.to_f
- else
- a.to_s <=> b.to_s
- end
- end
- end
-end
diff --git a/lib/puppet/parser/functions/member.rb b/lib/puppet/parser/functions/member.rb
deleted file mode 100644
index 88609ce5..00000000
--- a/lib/puppet/parser/functions/member.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-#
-# member.rb
-#
-
-# TODO(Krzysztof Wilczynski): We need to add support for regular expression ...
-# TODO(Krzysztof Wilczynski): Support for strings and hashes too ...
-
-module Puppet::Parser::Functions
- newfunction(:member, :type => :rvalue, :doc => <<-EOS
-This function determines if a variable is a member of an array.
-The variable can be a string, fixnum, or array.
-
-*Examples:*
-
- member(['a','b'], 'b')
-
-Would return: true
-
- member(['a', 'b', 'c'], ['a', 'b'])
-
-would return: true
-
- member(['a','b'], 'c')
-
-Would return: false
-
- member(['a', 'b', 'c'], ['d', 'b'])
-
-would return: false
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "member(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size < 2
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'member(): Requires array to work with')
- end
-
- unless arguments[1].is_a? String or arguments[1].is_a? Fixnum or arguments[1].is_a? Array
- raise(Puppet::ParseError, 'member(): Item to search for must be a string, fixnum, or array')
- end
-
- if arguments[1].is_a? String or arguments[1].is_a? Fixnum
- item = Array(arguments[1])
- else
- item = arguments[1]
- end
-
-
- raise(Puppet::ParseError, 'member(): You must provide item ' +
- 'to search for within array given') if item.respond_to?('empty?') && item.empty?
-
- result = (item - array).empty?
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/merge.rb b/lib/puppet/parser/functions/merge.rb
deleted file mode 100644
index 1b39f206..00000000
--- a/lib/puppet/parser/functions/merge.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
- Merges two or more hashes together and returns the resulting hash.
-
- For example:
-
- $hash1 = {'one' => 1, 'two', => 2}
- $hash2 = {'two' => 'dos', 'three', => 'tres'}
- $merged_hash = merge($hash1, $hash2)
- # The resulting hash is equivalent to:
- # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'}
-
- When there is a duplicate key, the key in the rightmost hash will "win."
-
- ENDHEREDOC
-
- if args.length < 2
- raise Puppet::ParseError, ("merge(): wrong number of arguments (#{args.length}; must be at least 2)")
- end
-
- # The hash we accumulate into
- accumulator = Hash.new
- # Merge into the accumulator hash
- args.each do |arg|
- next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef
- unless arg.is_a?(Hash)
- raise Puppet::ParseError, "merge: unexpected argument type #{arg.class}, only expects hash arguments"
- end
- accumulator.merge!(arg)
- end
- # Return the fully merged hash
- accumulator
- end
-end
diff --git a/lib/puppet/parser/functions/min.rb b/lib/puppet/parser/functions/min.rb
deleted file mode 100644
index 6bd6ebf2..00000000
--- a/lib/puppet/parser/functions/min.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:min, :type => :rvalue, :doc => <<-EOS
- Returns the lowest value of all arguments.
- Requires at least one argument.
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "min(): Wrong number of arguments " +
- "need at least one") if args.size == 0
-
- # Sometimes we get numbers as numerics and sometimes as strings.
- # We try to compare them as numbers when possible
- return args.min do |a,b|
- if a.to_s =~ /\A^-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then
- a.to_f <=> b.to_f
- else
- a.to_s <=> b.to_s
- end
- end
- end
-end
diff --git a/lib/puppet/parser/functions/num2bool.rb b/lib/puppet/parser/functions/num2bool.rb
deleted file mode 100644
index af0e6ed7..00000000
--- a/lib/puppet/parser/functions/num2bool.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# num2bool.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS
-This function converts a number or a string representation of a number into a
-true boolean. Zero or anything non-numeric becomes false. Numbers higher then 0
-become true.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "num2bool(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- number = arguments[0]
-
- case number
- when Numeric
- # Yay, it's a number
- when String
- begin
- number = Float(number)
- rescue ArgumentError => ex
- raise(Puppet::ParseError, "num2bool(): '#{number}' does not look like a number: #{ex.message}")
- end
- else
- begin
- number = number.to_s
- rescue NoMethodError => ex
- raise(Puppet::ParseError, "num2bool(): Unable to parse argument: #{ex.message}")
- end
- end
-
- # Truncate Floats
- number = number.to_i
-
- # Return true for any positive number and false otherwise
- return number > 0
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/obfuscate_email.rb b/lib/puppet/parser/functions/obfuscate_email.rb
deleted file mode 100644
index 4e4cb826..00000000
--- a/lib/puppet/parser/functions/obfuscate_email.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:obfuscate_email, :type => :rvalue, :doc => <<-EOS
-Given:
- a comma seperated email string in form of 'john@doe.com, doe@john.com'
-
-This function will return all emails obfuscated in form of 'john {at} doe {dot} com, doe {at} john {dot} com'
-Works with multiple email adresses as well as with a single email adress.
-
- EOS
- ) do |args|
- args[0].gsub('@', ' {at} ').gsub('.', ' {dot} ')
- end
-end
-
-# vim: set ts=2 sw=2 et :
-# encoding: utf-8
diff --git a/lib/puppet/parser/functions/parsejson.rb b/lib/puppet/parser/functions/parsejson.rb
deleted file mode 100644
index a9a16a45..00000000
--- a/lib/puppet/parser/functions/parsejson.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# parsejson.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:parsejson, :type => :rvalue, :doc => <<-EOS
-This function accepts JSON as a string and converts into the correct Puppet
-structure.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "parsejson(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- json = arguments[0]
-
- # PSON is natively available in puppet
- PSON.load(json)
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/parseyaml.rb b/lib/puppet/parser/functions/parseyaml.rb
deleted file mode 100644
index 53d54faf..00000000
--- a/lib/puppet/parser/functions/parseyaml.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# parseyaml.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS
-This function accepts YAML as a string and converts it into the correct
-Puppet structure.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "parseyaml(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- require 'yaml'
-
- YAML::load(arguments[0])
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/pick.rb b/lib/puppet/parser/functions/pick.rb
deleted file mode 100644
index fdd0aefd..00000000
--- a/lib/puppet/parser/functions/pick.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:pick, :type => :rvalue, :doc => <<-EOS
-
-This function is similar to a coalesce function in SQL in that it will return
-the first value in a list of values that is not undefined or an empty string
-(two things in Puppet that will return a boolean false value). Typically,
-this function is used to check for a value in the Puppet Dashboard/Enterprise
-Console, and failover to a default value like the following:
-
- $real_jenkins_version = pick($::jenkins_version, '1.449')
-
-The value of $real_jenkins_version will first look for a top-scope variable
-called 'jenkins_version' (note that parameters set in the Puppet Dashboard/
-Enterprise Console are brought into Puppet as top-scope variables), and,
-failing that, will use a default value of 1.449.
-
-EOS
-) do |args|
- args = args.compact
- args.delete(:undef)
- args.delete(:undefined)
- args.delete("")
- if args[0].to_s.empty? then
- fail Puppet::ParseError, "pick(): must receive at least one non empty value"
- else
- return args[0]
- end
- end
-end
diff --git a/lib/puppet/parser/functions/pick_default.rb b/lib/puppet/parser/functions/pick_default.rb
deleted file mode 100644
index 36e33abf..00000000
--- a/lib/puppet/parser/functions/pick_default.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:pick_default, :type => :rvalue, :doc => <<-EOS
-
-This function is similar to a coalesce function in SQL in that it will return
-the first value in a list of values that is not undefined or an empty string
-(two things in Puppet that will return a boolean false value). If no value is
-found, it will return the last argument.
-
-Typically, this function is used to check for a value in the Puppet
-Dashboard/Enterprise Console, and failover to a default value like the
-following:
-
- $real_jenkins_version = pick_default($::jenkins_version, '1.449')
-
-The value of $real_jenkins_version will first look for a top-scope variable
-called 'jenkins_version' (note that parameters set in the Puppet Dashboard/
-Enterprise Console are brought into Puppet as top-scope variables), and,
-failing that, will use a default value of 1.449.
-
-Note that, contrary to the pick() function, the pick_default does not fail if
-all arguments are empty. This allows pick_default to use an empty value as
-default.
-
-EOS
-) do |args|
- fail "Must receive at least one argument." if args.empty?
- default = args.last
- args = args[0..-2].compact
- args.delete(:undef)
- args.delete(:undefined)
- args.delete("")
- args << default
- return args[0]
- end
-end
diff --git a/lib/puppet/parser/functions/prefix.rb b/lib/puppet/parser/functions/prefix.rb
deleted file mode 100644
index d02286af..00000000
--- a/lib/puppet/parser/functions/prefix.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# prefix.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:prefix, :type => :rvalue, :doc => <<-EOS
-This function applies a prefix to all elements in an array.
-
-*Examples:*
-
- prefix(['a','b','c'], 'p')
-
-Will return: ['pa','pb','pc']
- EOS
- ) do |arguments|
-
- # Technically we support two arguments but only first is mandatory ...
- raise(Puppet::ParseError, "prefix(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise Puppet::ParseError, "prefix(): expected first argument to be an Array, got #{array.inspect}"
- end
-
- prefix = arguments[1] if arguments[1]
-
- if prefix
- unless prefix.is_a?(String)
- raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}"
- end
- end
-
- # Turn everything into string same as join would do ...
- result = array.collect do |i|
- i = i.to_s
- prefix ? prefix + i : i
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/private.rb b/lib/puppet/parser/functions/private.rb
deleted file mode 100644
index 60210d33..00000000
--- a/lib/puppet/parser/functions/private.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# private.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:private, :doc => <<-'EOS'
- Sets the current class or definition as private.
- Calling the class or definition from outside the current module will fail.
- EOS
- ) do |args|
-
- raise(Puppet::ParseError, "private(): Wrong number of arguments "+
- "given (#{args.size}}) for 0 or 1)") if args.size > 1
-
- scope = self
- if scope.lookupvar('module_name') != scope.lookupvar('caller_module_name')
- message = nil
- if args[0] and args[0].is_a? String
- message = args[0]
- else
- manifest_name = scope.source.name
- manifest_type = scope.source.type
- message = (manifest_type.to_s == 'hostclass') ? 'Class' : 'Definition'
- message += " #{manifest_name} is private"
- end
- raise(Puppet::ParseError, message)
- end
- end
-end
diff --git a/lib/puppet/parser/functions/range.rb b/lib/puppet/parser/functions/range.rb
deleted file mode 100644
index 49fba21c..00000000
--- a/lib/puppet/parser/functions/range.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# range.rb
-#
-
-# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ...
-
-module Puppet::Parser::Functions
- newfunction(:range, :type => :rvalue, :doc => <<-EOS
-When given range in the form of (start, stop) it will extrapolate a range as
-an array.
-
-*Examples:*
-
- range("0", "9")
-
-Will return: [0,1,2,3,4,5,6,7,8,9]
-
- range("00", "09")
-
-Will return: [0,1,2,3,4,5,6,7,8,9] (Zero padded strings are converted to
-integers automatically)
-
- range("a", "c")
-
-Will return: ["a","b","c"]
-
- range("host01", "host10")
-
-Will return: ["host01", "host02", ..., "host09", "host10"]
-
-Passing a third argument will cause the generated range to step by that
-interval, e.g.
-
- range("0", "9", "2")
-
-Will return: [0,2,4,6,8]
- EOS
- ) do |arguments|
-
- # We support more than one argument but at least one is mandatory ...
- raise(Puppet::ParseError, "range(): Wrong number of " +
- "arguments given (#{arguments.size} for 1)") if arguments.size < 1
-
- if arguments.size > 1
- start = arguments[0]
- stop = arguments[1]
- step = arguments[2].nil? ? 1 : arguments[2].to_i.abs
-
- type = '..' # We select simplest type for Range available in Ruby ...
-
- elsif arguments.size > 0
- value = arguments[0]
-
- if m = value.match(/^(\w+)(\.\.\.?|\-)(\w+)$/)
- start = m[1]
- stop = m[3]
-
- type = m[2]
-
- elsif value.match(/^.+$/)
- raise(Puppet::ParseError, 'range(): Unable to compute range ' +
- 'from the value given')
- else
- raise(Puppet::ParseError, 'range(): Unknown format of range given')
- end
- end
-
- # Check whether we have integer value if so then make it so ...
- if start.to_s.match(/^\d+$/)
- start = start.to_i
- stop = stop.to_i
- else
- start = start.to_s
- stop = stop.to_s
- end
-
- range = case type
- when /^(\.\.|\-)$/ then (start .. stop)
- when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ...
- end
-
- result = range.step(step).collect { |i| i } # Get them all ... Pokemon ...
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/reject.rb b/lib/puppet/parser/functions/reject.rb
deleted file mode 100644
index 1953ffcf..00000000
--- a/lib/puppet/parser/functions/reject.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# reject.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:reject, :type => :rvalue, :doc => <<-EOS) do |args|
-This function searches through an array and rejects all elements that match
-the provided regular expression.
-
-*Examples:*
-
- reject(['aaa','bbb','ccc','aaaddd'], 'aaa')
-
-Would return:
-
- ['bbb','ccc']
-EOS
-
- if (args.size != 2)
- raise Puppet::ParseError,
- "reject(): Wrong number of arguments given #{args.size} for 2"
- end
-
- ary = args[0]
- pattern = Regexp.new(args[1])
-
- ary.reject { |e| e =~ pattern }
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/reverse.rb b/lib/puppet/parser/functions/reverse.rb
deleted file mode 100644
index 7f1018f6..00000000
--- a/lib/puppet/parser/functions/reverse.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# reverse.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:reverse, :type => :rvalue, :doc => <<-EOS
-Reverses the order of a string or array.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "reverse(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'reverse(): Requires either ' +
- 'array or string to work with')
- end
-
- result = value.reverse
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/rstrip.rb b/lib/puppet/parser/functions/rstrip.rb
deleted file mode 100644
index 0cf8d222..00000000
--- a/lib/puppet/parser/functions/rstrip.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# rstrip.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:rstrip, :type => :rvalue, :doc => <<-EOS
-Strips leading spaces to the right of the string.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "rstrip(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'rstrip(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- result = value.collect { |i| i.is_a?(String) ? i.rstrip : i }
- else
- result = value.rstrip
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/shuffle.rb b/lib/puppet/parser/functions/shuffle.rb
deleted file mode 100644
index 30c663db..00000000
--- a/lib/puppet/parser/functions/shuffle.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# shuffle.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:shuffle, :type => :rvalue, :doc => <<-EOS
-Randomizes the order of a string or array elements.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "shuffle(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'shuffle(): Requires either ' +
- 'array or string to work with')
- end
-
- result = value.clone
-
- string = value.is_a?(String) ? true : false
-
- # Check whether it makes sense to shuffle ...
- return result if result.size <= 1
-
- # We turn any string value into an array to be able to shuffle ...
- result = string ? result.split('') : result
-
- elements = result.size
-
- # Simple implementation of Fisher–Yates in-place shuffle ...
- elements.times do |i|
- j = rand(elements - i) + i
- result[j], result[i] = result[i], result[j]
- end
-
- result = string ? result.join : result
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/size.rb b/lib/puppet/parser/functions/size.rb
deleted file mode 100644
index cc207e3f..00000000
--- a/lib/puppet/parser/functions/size.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-#
-# size.rb
-#
-
-# TODO(Krzysztof Wilczynski): Support for hashes would be nice too ...
-
-module Puppet::Parser::Functions
- newfunction(:size, :type => :rvalue, :doc => <<-EOS
-Returns the number of elements in a string or array.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "size(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- item = arguments[0]
-
- if item.is_a?(String)
-
- begin
- #
- # Check whether your item is a numeric value or not ...
- # This will take care about positive and/or negative numbers
- # for both integer and floating-point values ...
- #
- # Please note that Puppet has no notion of hexadecimal
- # nor octal numbers for its DSL at this point in time ...
- #
- Float(item)
-
- raise(Puppet::ParseError, 'size(): Requires either ' +
- 'string or array to work with')
-
- rescue ArgumentError
- result = item.size
- end
-
- elsif item.is_a?(Array)
- result = item.size
- else
- raise(Puppet::ParseError, 'size(): Unknown type given')
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/sort.rb b/lib/puppet/parser/functions/sort.rb
deleted file mode 100644
index cefbe546..00000000
--- a/lib/puppet/parser/functions/sort.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# sort.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:sort, :type => :rvalue, :doc => <<-EOS
-Sorts strings and arrays lexically.
- EOS
- ) do |arguments|
-
- if (arguments.size != 1) then
- raise(Puppet::ParseError, "sort(): Wrong number of arguments "+
- "given #{arguments.size} for 1")
- end
-
- value = arguments[0]
-
- if value.is_a?(Array) then
- value.sort
- elsif value.is_a?(String) then
- value.split("").sort.join("")
- end
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/squeeze.rb b/lib/puppet/parser/functions/squeeze.rb
deleted file mode 100644
index 81fadfdb..00000000
--- a/lib/puppet/parser/functions/squeeze.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# squeeze.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:squeeze, :type => :rvalue, :doc => <<-EOS
-Returns a new string where runs of the same character that occur in this set are replaced by a single character.
- EOS
- ) do |arguments|
-
- if ((arguments.size != 2) and (arguments.size != 1)) then
- raise(Puppet::ParseError, "squeeze(): Wrong number of arguments "+
- "given #{arguments.size} for 2 or 1")
- end
-
- item = arguments[0]
- squeezeval = arguments[1]
-
- if item.is_a?(Array) then
- if squeezeval then
- item.collect { |i| i.squeeze(squeezeval) }
- else
- item.collect { |i| i.squeeze }
- end
- else
- if squeezeval then
- item.squeeze(squeezeval)
- else
- item.squeeze
- end
- end
-
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/str2bool.rb b/lib/puppet/parser/functions/str2bool.rb
deleted file mode 100644
index 446732ec..00000000
--- a/lib/puppet/parser/functions/str2bool.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# str2bool.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS
-This converts a string to a boolean. This attempt to convert strings that
-contain things like: y, 1, t, true to 'true' and strings that contain things
-like: 0, f, n, false, no to 'false'.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "str2bool(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- string = arguments[0]
-
- # If string is already Boolean, return it
- if !!string == string
- return string
- end
-
- unless string.is_a?(String)
- raise(Puppet::ParseError, 'str2bool(): Requires either ' +
- 'string to work with')
- end
-
- # We consider all the yes, no, y, n and so on too ...
- result = case string
- #
- # This is how undef looks like in Puppet ...
- # We yield false in this case.
- #
- when /^$/, '' then false # Empty string will be false ...
- when /^(1|t|y|true|yes)$/ then true
- when /^(0|f|n|false|no)$/ then false
- when /^(undef|undefined)$/ then false # This is not likely to happen ...
- else
- raise(Puppet::ParseError, 'str2bool(): Unknown type of boolean given')
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/str2saltedsha1.rb b/lib/puppet/parser/functions/str2saltedsha1.rb
deleted file mode 100644
index e51a861a..00000000
--- a/lib/puppet/parser/functions/str2saltedsha1.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# str2saltedsha1.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:str2saltedsha1, :type => :rvalue, :doc => <<-EOS
-This converts a string to a salted-SHA1 password hash (which is used for
-OS X versions >= 10.7). Given any simple string, you will get a hex version
-of a salted-SHA1 password hash that can be inserted into your Puppet
-manifests as a valid password attribute.
- EOS
- ) do |arguments|
- require 'digest/sha2'
-
- raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " +
- "passed (#{arguments.size} but we require 1)") if arguments.size != 1
-
- password = arguments[0]
-
- unless password.is_a?(String)
- raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' +
- "String argument, you passed: #{password.class}")
- end
-
- seedint = rand(2**31 - 1)
- seedstring = Array(seedint).pack("L")
- saltedpass = Digest::SHA1.digest(seedstring + password)
- (seedstring + saltedpass).unpack('H*')[0]
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/str2saltedsha512.rb b/lib/puppet/parser/functions/str2saltedsha512.rb
deleted file mode 100644
index 7fe7b012..00000000
--- a/lib/puppet/parser/functions/str2saltedsha512.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# str2saltedsha512.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:str2saltedsha512, :type => :rvalue, :doc => <<-EOS
-This converts a string to a salted-SHA512 password hash (which is used for
-OS X versions >= 10.7). Given any simple string, you will get a hex version
-of a salted-SHA512 password hash that can be inserted into your Puppet
-manifests as a valid password attribute.
- EOS
- ) do |arguments|
- require 'digest/sha2'
-
- raise(Puppet::ParseError, "str2saltedsha512(): Wrong number of arguments " +
- "passed (#{arguments.size} but we require 1)") if arguments.size != 1
-
- password = arguments[0]
-
- unless password.is_a?(String)
- raise(Puppet::ParseError, 'str2saltedsha512(): Requires a ' +
- "String argument, you passed: #{password.class}")
- end
-
- seedint = rand(2**31 - 1)
- seedstring = Array(seedint).pack("L")
- saltedpass = Digest::SHA512.digest(seedstring + password)
- (seedstring + saltedpass).unpack('H*')[0]
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/str2sha1_and_salt.rb b/lib/puppet/parser/functions/str2sha1_and_salt.rb
deleted file mode 100644
index 9ec382d0..00000000
--- a/lib/puppet/parser/functions/str2sha1_and_salt.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# str2saltedsha1.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:str2sha1_and_salt, :type => :rvalue, :doc => <<-EOS
-This converts a string to an array containing the salted SHA1 password hash in
-the first field, and the salt itself in second field of the returned array.
-This combination is used i.e. for couchdb passwords.
- EOS
- ) do |arguments|
- require 'digest/sha1'
-
- raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " +
- "passed (#{arguments.size} but we require 1)") if arguments.size != 1
-
- password = arguments[0]
-
- unless password.is_a?(String)
- raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' +
- "String argument, you passed: #{password.class}")
- end
-
- seedint = rand(2**31 - 1)
- seedstring = Array(seedint).pack("L")
- salt = Digest::MD5.hexdigest(seedstring)
- saltedpass = Digest::SHA1.hexdigest(password + salt)
-
- array = Array.new
- array << saltedpass
- array << salt
- return array
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/str_and_salt2sha1.rb b/lib/puppet/parser/functions/str_and_salt2sha1.rb
deleted file mode 100644
index 71d69cf5..00000000
--- a/lib/puppet/parser/functions/str_and_salt2sha1.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# str_and_salt2sha1.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:str_and_salt2sha1, :type => :rvalue, :doc => <<-EOS
-This converts a string to an array containing the salted SHA1 password hash in
-the first field, and the salt itself in second field of the returned array.
-This combination is used i.e. for couchdb passwords.
- EOS
- ) do |arguments|
- require 'digest/sha1'
-
- raise(Puppet::ParseError, "str_and_salt2sha1(): Wrong number of arguments " +
- "passed (#{arguments.size} but we require 1)") if arguments.size != 1
-
- str_and_salt = arguments[0]
-
- unless str_and_salt.is_a?(Array)
- raise(Puppet::ParseError, 'str_and_salt2sha1(): Requires a ' +
- "Array argument, you passed: #{password.class}")
- end
-
- str = str_and_salt[0]
- salt = str_and_salt[1]
- sha1 = Digest::SHA1.hexdigest(str+ salt)
-
- return sha1
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/strftime.rb b/lib/puppet/parser/functions/strftime.rb
deleted file mode 100644
index 0b52adec..00000000
--- a/lib/puppet/parser/functions/strftime.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-#
-# strftime.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:strftime, :type => :rvalue, :doc => <<-EOS
-This function returns formatted time.
-
-*Examples:*
-
-To return the time since epoch:
-
- strftime("%s")
-
-To return the date:
-
- strftime("%Y-%m-%d")
-
-*Format meaning:*
-
- %a - The abbreviated weekday name (``Sun'')
- %A - The full weekday name (``Sunday'')
- %b - The abbreviated month name (``Jan'')
- %B - The full month name (``January'')
- %c - The preferred local date and time representation
- %C - Century (20 in 2009)
- %d - Day of the month (01..31)
- %D - Date (%m/%d/%y)
- %e - Day of the month, blank-padded ( 1..31)
- %F - Equivalent to %Y-%m-%d (the ISO 8601 date format)
- %h - Equivalent to %b
- %H - Hour of the day, 24-hour clock (00..23)
- %I - Hour of the day, 12-hour clock (01..12)
- %j - Day of the year (001..366)
- %k - hour, 24-hour clock, blank-padded ( 0..23)
- %l - hour, 12-hour clock, blank-padded ( 0..12)
- %L - Millisecond of the second (000..999)
- %m - Month of the year (01..12)
- %M - Minute of the hour (00..59)
- %n - Newline (\n)
- %N - Fractional seconds digits, default is 9 digits (nanosecond)
- %3N millisecond (3 digits)
- %6N microsecond (6 digits)
- %9N nanosecond (9 digits)
- %p - Meridian indicator (``AM'' or ``PM'')
- %P - Meridian indicator (``am'' or ``pm'')
- %r - time, 12-hour (same as %I:%M:%S %p)
- %R - time, 24-hour (%H:%M)
- %s - Number of seconds since 1970-01-01 00:00:00 UTC.
- %S - Second of the minute (00..60)
- %t - Tab character (\t)
- %T - time, 24-hour (%H:%M:%S)
- %u - Day of the week as a decimal, Monday being 1. (1..7)
- %U - Week number of the current year,
- starting with the first Sunday as the first
- day of the first week (00..53)
- %v - VMS date (%e-%b-%Y)
- %V - Week number of year according to ISO 8601 (01..53)
- %W - Week number of the current year,
- starting with the first Monday as the first
- day of the first week (00..53)
- %w - Day of the week (Sunday is 0, 0..6)
- %x - Preferred representation for the date alone, no time
- %X - Preferred representation for the time alone, no date
- %y - Year without a century (00..99)
- %Y - Year with century
- %z - Time zone as hour offset from UTC (e.g. +0900)
- %Z - Time zone name
- %% - Literal ``%'' character
- EOS
- ) do |arguments|
-
- # Technically we support two arguments but only first is mandatory ...
- raise(Puppet::ParseError, "strftime(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- format = arguments[0]
-
- raise(Puppet::ParseError, 'strftime(): You must provide ' +
- 'format for evaluation') if format.empty?
-
- # The Time Zone argument is optional ...
- time_zone = arguments[1] if arguments[1]
-
- time = Time.new
-
- # There is probably a better way to handle Time Zone ...
- if time_zone and not time_zone.empty?
- original_zone = ENV['TZ']
-
- local_time = time.clone
- local_time = local_time.utc
-
- ENV['TZ'] = time_zone
-
- time = local_time.localtime
-
- ENV['TZ'] = original_zone
- end
-
- result = time.strftime(format)
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/strip.rb b/lib/puppet/parser/functions/strip.rb
deleted file mode 100644
index 3fac47d5..00000000
--- a/lib/puppet/parser/functions/strip.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# strip.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:strip, :type => :rvalue, :doc => <<-EOS
-This function removes leading and trailing whitespace from a string or from
-every string inside an array.
-
-*Examples:*
-
- strip(" aaa ")
-
-Would result in: "aaa"
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "strip(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'strip(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- result = value.collect { |i| i.is_a?(String) ? i.strip : i }
- else
- result = value.strip
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/suffix.rb b/lib/puppet/parser/functions/suffix.rb
deleted file mode 100644
index f7792d6f..00000000
--- a/lib/puppet/parser/functions/suffix.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# suffix.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:suffix, :type => :rvalue, :doc => <<-EOS
-This function applies a suffix to all elements in an array.
-
-*Examples:*
-
- suffix(['a','b','c'], 'p')
-
-Will return: ['ap','bp','cp']
- EOS
- ) do |arguments|
-
- # Technically we support two arguments but only first is mandatory ...
- raise(Puppet::ParseError, "suffix(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- array = arguments[0]
-
- unless array.is_a?(Array)
- raise Puppet::ParseError, "suffix(): expected first argument to be an Array, got #{array.inspect}"
- end
-
- suffix = arguments[1] if arguments[1]
-
- if suffix
- unless suffix.is_a? String
- raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}"
- end
- end
-
- # Turn everything into string same as join would do ...
- result = array.collect do |i|
- i = i.to_s
- suffix ? i + suffix : i
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/swapcase.rb b/lib/puppet/parser/functions/swapcase.rb
deleted file mode 100644
index eb7fe137..00000000
--- a/lib/puppet/parser/functions/swapcase.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# swapcase.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:swapcase, :type => :rvalue, :doc => <<-EOS
-This function will swap the existing case of a string.
-
-*Examples:*
-
- swapcase("aBcD")
-
-Would result in: "AbCd"
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "swapcase(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'swapcase(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.swapcase : i }
- else
- result = value.swapcase
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/time.rb b/lib/puppet/parser/functions/time.rb
deleted file mode 100644
index 0cddaf86..00000000
--- a/lib/puppet/parser/functions/time.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# time.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:time, :type => :rvalue, :doc => <<-EOS
-This function will return the current time since epoch as an integer.
-
-*Examples:*
-
- time()
-
-Will return something like: 1311972653
- EOS
- ) do |arguments|
-
- # The Time Zone argument is optional ...
- time_zone = arguments[0] if arguments[0]
-
- if (arguments.size != 0) and (arguments.size != 1) then
- raise(Puppet::ParseError, "time(): Wrong number of arguments "+
- "given #{arguments.size} for 0 or 1")
- end
-
- time = Time.new
-
- # There is probably a better way to handle Time Zone ...
- if time_zone and not time_zone.empty?
- original_zone = ENV['TZ']
-
- local_time = time.clone
- local_time = local_time.utc
-
- ENV['TZ'] = time_zone
-
- time = local_time.localtime
-
- ENV['TZ'] = original_zone
- end
-
- # Calling Time#to_i on a receiver changes it. Trust me I am the Doctor.
- result = time.strftime('%s')
- result = result.to_i
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/to_bytes.rb b/lib/puppet/parser/functions/to_bytes.rb
deleted file mode 100644
index df490ea8..00000000
--- a/lib/puppet/parser/functions/to_bytes.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:to_bytes, :type => :rvalue, :doc => <<-EOS
- Converts the argument into bytes, for example 4 kB becomes 4096.
- Takes a single string value as an argument.
- These conversions reflect a layperson's understanding of
- 1 MB = 1024 KB, when in fact 1 MB = 1000 KB, and 1 MiB = 1024 KiB.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "to_bytes(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size != 1
-
- arg = arguments[0]
-
- return arg if arg.is_a? Numeric
-
- value,prefix = */([0-9.e+-]*)\s*([^bB]?)/.match(arg)[1,2]
-
- value = value.to_f
- case prefix
- when '' then return value.to_i
- when 'k' then return (value*(1<<10)).to_i
- when 'M' then return (value*(1<<20)).to_i
- when 'G' then return (value*(1<<30)).to_i
- when 'T' then return (value*(1<<40)).to_i
- when 'P' then return (value*(1<<50)).to_i
- when 'E' then return (value*(1<<60)).to_i
- else raise Puppet::ParseError, "to_bytes(): Unknown prefix #{prefix}"
- end
- end
-end
diff --git a/lib/puppet/parser/functions/type.rb b/lib/puppet/parser/functions/type.rb
deleted file mode 100644
index 016529b0..00000000
--- a/lib/puppet/parser/functions/type.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# type.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:type, :type => :rvalue, :doc => <<-EOS
- DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.
- EOS
- ) do |args|
-
- warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.")
- if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x)
- Puppet::Parser::Functions.autoloader.load(:type3x)
- end
- function_type3x(args + [false])
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/type3x.rb b/lib/puppet/parser/functions/type3x.rb
deleted file mode 100644
index 0800b4a3..00000000
--- a/lib/puppet/parser/functions/type3x.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-#
-# type3x.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:type3x, :type => :rvalue, :doc => <<-EOS
-DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system.
-
-Returns the type when passed a value. Type can be one of:
-
-* string
-* array
-* hash
-* float
-* integer
-* boolean
- EOS
- ) do |args|
- raise(Puppet::ParseError, "type3x(): Wrong number of arguments " +
- "given (#{args.size} for 1)") if args.size < 1
-
- value = args[0]
-
- klass = value.class
-
- if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass)
- raise(Puppet::ParseError, 'type3x(): Unknown type')
- end
-
- klass = klass.to_s # Ugly ...
-
- # We note that Integer is the parent to Bignum and Fixnum ...
- result = case klass
- when /^(?:Big|Fix)num$/ then 'integer'
- when /^(?:True|False)Class$/ then 'boolean'
- else klass
- end
-
- if result == "String" then
- if value == value.to_i.to_s then
- result = "Integer"
- elsif value == value.to_f.to_s then
- result = "Float"
- end
- end
-
- return result.downcase
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/union.rb b/lib/puppet/parser/functions/union.rb
deleted file mode 100644
index c91bb805..00000000
--- a/lib/puppet/parser/functions/union.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# union.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:union, :type => :rvalue, :doc => <<-EOS
-This function returns a union of two arrays.
-
-*Examples:*
-
- union(["a","b","c"],["b","c","d"])
-
-Would return: ["a","b","c","d"]
- EOS
- ) do |arguments|
-
- # Two arguments are required
- raise(Puppet::ParseError, "union(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size != 2
-
- first = arguments[0]
- second = arguments[1]
-
- unless first.is_a?(Array) && second.is_a?(Array)
- raise(Puppet::ParseError, 'union(): Requires 2 arrays')
- end
-
- result = first | second
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/unique.rb b/lib/puppet/parser/functions/unique.rb
deleted file mode 100644
index cf770f3b..00000000
--- a/lib/puppet/parser/functions/unique.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# unique.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:unique, :type => :rvalue, :doc => <<-EOS
-This function will remove duplicates from strings and arrays.
-
-*Examples:*
-
- unique("aabbcc")
-
-Will return:
-
- abc
-
-You can also use this with arrays:
-
- unique(["a","a","b","b","c","c"])
-
-This returns:
-
- ["a","b","c"]
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "unique(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'unique(): Requires either ' +
- 'array or string to work with')
- end
-
- result = value.clone
-
- string = value.is_a?(String) ? true : false
-
- # We turn any string value into an array to be able to shuffle ...
- result = string ? result.split('') : result
- result = result.uniq # Remove duplicates ...
- result = string ? result.join : result
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/upcase.rb b/lib/puppet/parser/functions/upcase.rb
deleted file mode 100644
index 4302b29e..00000000
--- a/lib/puppet/parser/functions/upcase.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# upcase.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:upcase, :type => :rvalue, :doc => <<-EOS
-Converts a string or an array of strings to uppercase.
-
-*Examples:*
-
- upcase("abcd")
-
-Will return:
-
- ASDF
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "upcase(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'upcase(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? i.upcase : i }
- else
- result = value.upcase
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/uriescape.rb b/lib/puppet/parser/functions/uriescape.rb
deleted file mode 100644
index a486eee5..00000000
--- a/lib/puppet/parser/functions/uriescape.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# uriescape.rb
-#
-require 'uri'
-
-module Puppet::Parser::Functions
- newfunction(:uriescape, :type => :rvalue, :doc => <<-EOS
- Urlencodes a string or array of strings.
- Requires either a single string or an array as an input.
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "uriescape(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- value = arguments[0]
-
- unless value.is_a?(Array) || value.is_a?(String)
- raise(Puppet::ParseError, 'uriescape(): Requires either ' +
- 'array or string to work with')
- end
-
- if value.is_a?(Array)
- # Numbers in Puppet are often string-encoded which is troublesome ...
- result = value.collect { |i| i.is_a?(String) ? URI.escape(i,unsafe) : i }
- else
- result = URI.escape(value)
- end
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/validate_absolute_path.rb b/lib/puppet/parser/functions/validate_absolute_path.rb
deleted file mode 100644
index b6966809..00000000
--- a/lib/puppet/parser/functions/validate_absolute_path.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:validate_absolute_path, :doc => <<-'ENDHEREDOC') do |args|
- Validate the string represents an absolute path in the filesystem. This function works
- for windows and unix style paths.
-
- The following values will pass:
-
- $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet'
- validate_absolute_path($my_path)
- $my_path2 = '/var/lib/puppet'
- validate_absolute_path($my_path2)
- $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet']
- validate_absolute_path($my_path3)
- $my_path4 = ['/var/lib/puppet','/usr/share/puppet']
- validate_absolute_path($my_path4)
-
- The following values will fail, causing compilation to abort:
-
- validate_absolute_path(true)
- validate_absolute_path('../var/lib/puppet')
- validate_absolute_path('var/lib/puppet')
- validate_absolute_path([ 'var/lib/puppet', '/var/foo' ])
- validate_absolute_path([ '/var/lib/puppet', 'var/foo' ])
- $undefined = undef
- validate_absolute_path($undefined)
-
- ENDHEREDOC
-
- require 'puppet/util'
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_absolute_path(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- # put arg to candidate var to be able to replace it
- candidates = arg
- # if arg is just a string with a path to test, convert it to an array
- # to avoid test code duplication
- unless arg.is_a?(Array) then
- candidates = Array.new(1,arg)
- end
- # iterate over all pathes within the candidates array
- candidates.each do |path|
- # This logic was borrowed from
- # [lib/puppet/file_serving/base.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/file_serving/base.rb)
- # Puppet 2.7 and beyond will have Puppet::Util.absolute_path? Fall back to a back-ported implementation otherwise.
- if Puppet::Util.respond_to?(:absolute_path?) then
- unless Puppet::Util.absolute_path?(path, :posix) or Puppet::Util.absolute_path?(path, :windows)
- raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.")
- end
- else
- # This code back-ported from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path?
- # Determine in a platform-specific way whether a path is absolute. This
- # defaults to the local platform if none is specified.
- # Escape once for the string literal, and once for the regex.
- slash = '[\\\\/]'
- name = '[^\\\\/]+'
- regexes = {
- :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i,
- :posix => %r!^/!,
- }
- rval = (!!(path =~ regexes[:posix])) || (!!(path =~ regexes[:windows]))
- rval or raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.")
- end
- end
- end
- end
-end
diff --git a/lib/puppet/parser/functions/validate_array.rb b/lib/puppet/parser/functions/validate_array.rb
deleted file mode 100644
index 34b51182..00000000
--- a/lib/puppet/parser/functions/validate_array.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_array, :doc => <<-'ENDHEREDOC') do |args|
- Validate that all passed values are array data structures. Abort catalog
- compilation if any value fails this check.
-
- The following values will pass:
-
- $my_array = [ 'one', 'two' ]
- validate_array($my_array)
-
- The following values will fail, causing compilation to abort:
-
- validate_array(true)
- validate_array('some_string')
- $undefined = undef
- validate_array($undefined)
-
- ENDHEREDOC
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_array(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless arg.is_a?(Array)
- raise Puppet::ParseError, ("#{arg.inspect} is not an Array. It looks to be a #{arg.class}")
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/validate_augeas.rb b/lib/puppet/parser/functions/validate_augeas.rb
deleted file mode 100644
index 4ea4fe07..00000000
--- a/lib/puppet/parser/functions/validate_augeas.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require 'tempfile'
-
-module Puppet::Parser::Functions
- newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args|
- Perform validation of a string using an Augeas lens
- The first argument of this function should be a string to
- test, and the second argument should be the name of the Augeas lens to use.
- If Augeas fails to parse the string with the lens, the compilation will
- abort with a parse error.
-
- A third argument can be specified, listing paths which should
- not be found in the file. The `$file` variable points to the location
- of the temporary file being tested in the Augeas tree.
-
- For example, if you want to make sure your passwd content never contains
- a user `foo`, you could write:
-
- validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo'])
-
- Or if you wanted to ensure that no users used the '/bin/barsh' shell,
- you could use:
-
- validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]']
-
- If a fourth argument is specified, this will be the error message raised and
- seen by the user.
-
- A helpful error message can be returned like this:
-
- validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas')
-
- ENDHEREDOC
- unless Puppet.features.augeas?
- raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Augeas#Pre-requisites for how to activate it.")
- end
-
- if (args.length < 2) or (args.length > 4) then
- raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)")
- end
-
- msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}"
-
- require 'augeas'
- aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD)
- begin
- content = args[0]
-
- # Test content in a temporary file
- tmpfile = Tempfile.new("validate_augeas")
- begin
- tmpfile.write(content)
- ensure
- tmpfile.close
- end
-
- # Check for syntax
- lens = args[1]
- aug.transform(
- :lens => lens,
- :name => 'Validate_augeas',
- :incl => tmpfile.path
- )
- aug.load!
-
- unless aug.match("/augeas/files#{tmpfile.path}//error").empty?
- error = aug.get("/augeas/files#{tmpfile.path}//error/message")
- msg += " with error: #{error}"
- raise Puppet::ParseError, (msg)
- end
-
- # Launch unit tests
- tests = args[2] || []
- aug.defvar('file', "/files#{tmpfile.path}")
- tests.each do |t|
- msg += " testing path #{t}"
- raise Puppet::ParseError, (msg) unless aug.match(t).empty?
- end
- ensure
- aug.close
- tmpfile.unlink
- end
- end
-end
diff --git a/lib/puppet/parser/functions/validate_bool.rb b/lib/puppet/parser/functions/validate_bool.rb
deleted file mode 100644
index 59a08056..00000000
--- a/lib/puppet/parser/functions/validate_bool.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_bool, :doc => <<-'ENDHEREDOC') do |args|
- Validate that all passed values are either true or false. Abort catalog
- compilation if any value fails this check.
-
- The following values will pass:
-
- $iamtrue = true
- validate_bool(true)
- validate_bool(true, true, false, $iamtrue)
-
- The following values will fail, causing compilation to abort:
-
- $some_array = [ true ]
- validate_bool("false")
- validate_bool("true")
- validate_bool($some_array)
-
- ENDHEREDOC
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_bool(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless function_is_bool([arg])
- raise Puppet::ParseError, ("#{arg.inspect} is not a boolean. It looks to be a #{arg.class}")
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/validate_cmd.rb b/lib/puppet/parser/functions/validate_cmd.rb
deleted file mode 100644
index 5df3c609..00000000
--- a/lib/puppet/parser/functions/validate_cmd.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'puppet/util/execution'
-require 'tempfile'
-
-module Puppet::Parser::Functions
- newfunction(:validate_cmd, :doc => <<-'ENDHEREDOC') do |args|
- Perform validation of a string with an external command.
- The first argument of this function should be a string to
- test, and the second argument should be a path to a test command
- taking a % as a placeholder for the file path (will default to the end).
- If the command, launched against a tempfile containing the passed string,
- returns a non-null value, compilation will abort with a parse error.
-
- If a third argument is specified, this will be the error message raised and
- seen by the user.
-
- A helpful error message can be returned like this:
-
- Example:
-
- # Defaults to end of path
- validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content')
-
- # % as file location
- validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content')
-
- ENDHEREDOC
- if (args.length < 2) or (args.length > 3) then
- raise Puppet::ParseError, ("validate_cmd(): wrong number of arguments (#{args.length}; must be 2 or 3)")
- end
-
- msg = args[2] || "validate_cmd(): failed to validate content with command #{args[1].inspect}"
-
- content = args[0]
- checkscript = args[1]
-
- # Test content in a temporary file
- tmpfile = Tempfile.new("validate_cmd")
- begin
- tmpfile.write(content)
- tmpfile.close
-
- if checkscript =~ /\s%(\s|$)/
- check_with_correct_location = checkscript.gsub(/%/,tmpfile.path)
- else
- check_with_correct_location = "#{checkscript} #{tmpfile.path}"
- end
-
- if Puppet::Util::Execution.respond_to?('execute')
- Puppet::Util::Execution.execute(check_with_correct_location)
- else
- Puppet::Util.execute(check_with_correct_location)
- end
- rescue Puppet::ExecutionFailure => detail
- msg += "\n#{detail}"
- raise Puppet::ParseError, msg
- rescue Exception => detail
- msg += "\n#{detail.class.name} #{detail}"
- raise Puppet::ParseError, msg
- ensure
- tmpfile.unlink
- end
- end
-end
diff --git a/lib/puppet/parser/functions/validate_hash.rb b/lib/puppet/parser/functions/validate_hash.rb
deleted file mode 100644
index 9bdd5432..00000000
--- a/lib/puppet/parser/functions/validate_hash.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_hash, :doc => <<-'ENDHEREDOC') do |args|
- Validate that all passed values are hash data structures. Abort catalog
- compilation if any value fails this check.
-
- The following values will pass:
-
- $my_hash = { 'one' => 'two' }
- validate_hash($my_hash)
-
- The following values will fail, causing compilation to abort:
-
- validate_hash(true)
- validate_hash('some_string')
- $undefined = undef
- validate_hash($undefined)
-
- ENDHEREDOC
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_hash(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless arg.is_a?(Hash)
- raise Puppet::ParseError, ("#{arg.inspect} is not a Hash. It looks to be a #{arg.class}")
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/validate_ipv4_address.rb b/lib/puppet/parser/functions/validate_ipv4_address.rb
deleted file mode 100644
index fc02748e..00000000
--- a/lib/puppet/parser/functions/validate_ipv4_address.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC
- Validate that all values passed are valid IPv4 addresses.
- Fail compilation if any value fails this check.
-
- The following values will pass:
-
- $my_ip = "1.2.3.4"
- validate_ipv4_address($my_ip)
- validate_bool("8.8.8.8", "172.16.0.1", $my_ip)
-
- The following values will fail, causing compilation to abort:
-
- $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ]
- validate_ipv4_address($some_array)
-
- ENDHEREDOC
- ) do |args|
-
- require "ipaddr"
- rescuable_exceptions = [ ArgumentError ]
-
- if defined?(IPAddr::InvalidAddressError)
- rescuable_exceptions << IPAddr::InvalidAddressError
- end
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless arg.is_a?(String)
- raise Puppet::ParseError, "#{arg.inspect} is not a string."
- end
-
- begin
- unless IPAddr.new(arg).ipv4?
- raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address."
- end
- rescue *rescuable_exceptions
- raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address."
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/validate_ipv6_address.rb b/lib/puppet/parser/functions/validate_ipv6_address.rb
deleted file mode 100644
index b0f2558d..00000000
--- a/lib/puppet/parser/functions/validate_ipv6_address.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC
- Validate that all values passed are valid IPv6 addresses.
- Fail compilation if any value fails this check.
-
- The following values will pass:
-
- $my_ip = "3ffe:505:2"
- validate_ipv6_address(1)
- validate_ipv6_address($my_ip)
- validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip)
-
- The following values will fail, causing compilation to abort:
-
- $some_array = [ true, false, "garbage string", "1.2.3.4" ]
- validate_ipv6_address($some_array)
-
- ENDHEREDOC
- ) do |args|
-
- require "ipaddr"
- rescuable_exceptions = [ ArgumentError ]
-
- if defined?(IPAddr::InvalidAddressError)
- rescuable_exceptions << IPAddr::InvalidAddressError
- end
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless arg.is_a?(String)
- raise Puppet::ParseError, "#{arg.inspect} is not a string."
- end
-
- begin
- unless IPAddr.new(arg).ipv6?
- raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address."
- end
- rescue *rescuable_exceptions
- raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address."
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/validate_re.rb b/lib/puppet/parser/functions/validate_re.rb
deleted file mode 100644
index ca25a702..00000000
--- a/lib/puppet/parser/functions/validate_re.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-module Puppet::Parser::Functions
- newfunction(:validate_re, :doc => <<-'ENDHEREDOC') do |args|
- Perform simple validation of a string against one or more regular
- expressions. The first argument of this function should be a string to
- test, and the second argument should be a stringified regular expression
- (without the // delimiters) or an array of regular expressions. If none
- of the regular expressions match the string passed in, compilation will
- abort with a parse error.
-
- If a third argument is specified, this will be the error message raised and
- seen by the user.
-
- The following strings will validate against the regular expressions:
-
- validate_re('one', '^one$')
- validate_re('one', [ '^one', '^two' ])
-
- The following strings will fail to validate, causing compilation to abort:
-
- validate_re('one', [ '^two', '^three' ])
-
- A helpful error message can be returned like this:
-
- validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7')
-
- ENDHEREDOC
- if (args.length < 2) or (args.length > 3) then
- raise Puppet::ParseError, ("validate_re(): wrong number of arguments (#{args.length}; must be 2 or 3)")
- end
-
- msg = args[2] || "validate_re(): #{args[0].inspect} does not match #{args[1].inspect}"
-
- # We're using a flattened array here because we can't call String#any? in
- # Ruby 1.9 like we can in Ruby 1.8
- raise Puppet::ParseError, (msg) unless [args[1]].flatten.any? do |re_str|
- args[0] =~ Regexp.compile(re_str)
- end
-
- end
-end
diff --git a/lib/puppet/parser/functions/validate_slength.rb b/lib/puppet/parser/functions/validate_slength.rb
deleted file mode 100644
index 7d534f37..00000000
--- a/lib/puppet/parser/functions/validate_slength.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_slength, :doc => <<-'ENDHEREDOC') do |args|
- Validate that the first argument is a string (or an array of strings), and
- less/equal to than the length of the second argument. An optional third
- parameter can be given a the minimum length. It fails if the first
- argument is not a string or array of strings, and if arg 2 and arg 3 are
- not convertable to a number.
-
- The following values will pass:
-
- validate_slength("discombobulate",17)
- validate_slength(["discombobulate","moo"],17)
- validate_slength(["discombobulate","moo"],17,3)
-
- The following valueis will not:
-
- validate_slength("discombobulate",1)
- validate_slength(["discombobulate","thermometer"],5)
- validate_slength(["discombobulate","moo"],17,10)
-
- ENDHEREDOC
-
- raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3
-
- input, max_length, min_length = *args
-
- begin
- max_length = Integer(max_length)
- raise ArgumentError if max_length <= 0
- rescue ArgumentError, TypeError
- raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}"
- end
-
- if min_length
- begin
- min_length = Integer(min_length)
- raise ArgumentError if min_length < 0
- rescue ArgumentError, TypeError
- raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}"
- end
- else
- min_length = 0
- end
-
- if min_length > max_length
- raise Puppet::ParseError, "validate_slength(): Expected second argument to be larger than third argument"
- end
-
- validator = lambda do |str|
- unless str.length <= max_length and str.length >= min_length
- raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}"
- end
- end
-
- case input
- when String
- validator.call(input)
- when Array
- input.each_with_index do |arg, pos|
- if arg.is_a? String
- validator.call(arg)
- else
- raise Puppet::ParseError, "validate_slength(): Expected element at array position #{pos} to be a String, got #{arg.class}"
- end
- end
- else
- raise Puppet::ParseError, "validate_slength(): Expected first argument to be a String or Array, got #{input.class}"
- end
- end
-end
diff --git a/lib/puppet/parser/functions/validate_string.rb b/lib/puppet/parser/functions/validate_string.rb
deleted file mode 100644
index c841f6ab..00000000
--- a/lib/puppet/parser/functions/validate_string.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module Puppet::Parser::Functions
-
- newfunction(:validate_string, :doc => <<-'ENDHEREDOC') do |args|
- Validate that all passed values are string data structures. Abort catalog
- compilation if any value fails this check.
-
- The following values will pass:
-
- $my_string = "one two"
- validate_string($my_string, 'three')
-
- The following values will fail, causing compilation to abort:
-
- validate_string(true)
- validate_string([ 'some', 'array' ])
-
- Note: validate_string(undef) will not fail in this version of the
- functions API (incl. current and future parser). Instead, use:
-
- if $var == undef {
- fail('...')
- }
-
- ENDHEREDOC
-
- unless args.length > 0 then
- raise Puppet::ParseError, ("validate_string(): wrong number of arguments (#{args.length}; must be > 0)")
- end
-
- args.each do |arg|
- unless arg.is_a?(String)
- raise Puppet::ParseError, ("#{arg.inspect} is not a string. It looks to be a #{arg.class}")
- end
- end
-
- end
-
-end
diff --git a/lib/puppet/parser/functions/values.rb b/lib/puppet/parser/functions/values.rb
deleted file mode 100644
index 16067561..00000000
--- a/lib/puppet/parser/functions/values.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# values.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:values, :type => :rvalue, :doc => <<-EOS
-When given a hash this function will return the values of that hash.
-
-*Examples:*
-
- $hash = {
- 'a' => 1,
- 'b' => 2,
- 'c' => 3,
- }
- values($hash)
-
-This example would return:
-
- [1,2,3]
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "values(): Wrong number of arguments " +
- "given (#{arguments.size} for 1)") if arguments.size < 1
-
- hash = arguments[0]
-
- unless hash.is_a?(Hash)
- raise(Puppet::ParseError, 'values(): Requires hash to work with')
- end
-
- result = hash.values
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/values_at.rb b/lib/puppet/parser/functions/values_at.rb
deleted file mode 100644
index f350f539..00000000
--- a/lib/puppet/parser/functions/values_at.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-#
-# values_at.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:values_at, :type => :rvalue, :doc => <<-EOS
-Finds value inside an array based on location.
-
-The first argument is the array you want to analyze, and the second element can
-be a combination of:
-
-* A single numeric index
-* A range in the form of 'start-stop' (eg. 4-9)
-* An array combining the above
-
-*Examples*:
-
- values_at(['a','b','c'], 2)
-
-Would return ['c'].
-
- values_at(['a','b','c'], ["0-1"])
-
-Would return ['a','b'].
-
- values_at(['a','b','c','d','e'], [0, "2-3"])
-
-Would return ['a','c','d'].
- EOS
- ) do |arguments|
-
- raise(Puppet::ParseError, "values_at(): Wrong number of " +
- "arguments given (#{arguments.size} for 2)") if arguments.size < 2
-
- array = arguments.shift
-
- unless array.is_a?(Array)
- raise(Puppet::ParseError, 'values_at(): Requires array to work with')
- end
-
- indices = [arguments.shift].flatten() # Get them all ... Pokemon ...
-
- if not indices or indices.empty?
- raise(Puppet::ParseError, 'values_at(): You must provide ' +
- 'at least one positive index to collect')
- end
-
- result = []
- indices_list = []
-
- indices.each do |i|
- i = i.to_s
- if m = i.match(/^(\d+)(\.\.\.?|\-)(\d+)$/)
- start = m[1].to_i
- stop = m[3].to_i
-
- type = m[2]
-
- if start > stop
- raise(Puppet::ParseError, 'values_at(): Stop index in ' +
- 'given indices range is smaller than the start index')
- elsif stop > array.size - 1 # First element is at index 0 is it not?
- raise(Puppet::ParseError, 'values_at(): Stop index in ' +
- 'given indices range exceeds array size')
- end
-
- range = case type
- when /^(\.\.|\-)$/ then (start .. stop)
- when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ...
- end
-
- range.each { |i| indices_list << i.to_i }
- else
- # Only positive numbers allowed in this case ...
- if not i.match(/^\d+$/)
- raise(Puppet::ParseError, 'values_at(): Unknown format ' +
- 'of given index')
- end
-
- # In Puppet numbers are often string-encoded ...
- i = i.to_i
-
- if i > array.size - 1 # Same story. First element is at index 0 ...
- raise(Puppet::ParseError, 'values_at(): Given index ' +
- 'exceeds array size')
- end
-
- indices_list << i
- end
- end
-
- # We remove nil values as they make no sense in Puppet DSL ...
- result = indices_list.collect { |i| array[i] }.compact
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/parser/functions/zip.rb b/lib/puppet/parser/functions/zip.rb
deleted file mode 100644
index 3074f282..00000000
--- a/lib/puppet/parser/functions/zip.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# zip.rb
-#
-
-module Puppet::Parser::Functions
- newfunction(:zip, :type => :rvalue, :doc => <<-EOS
-Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments.
-
-*Example:*
-
- zip(['1','2','3'],['4','5','6'])
-
-Would result in:
-
- ["1", "4"], ["2", "5"], ["3", "6"]
- EOS
- ) do |arguments|
-
- # Technically we support three arguments but only first is mandatory ...
- raise(Puppet::ParseError, "zip(): Wrong number of arguments " +
- "given (#{arguments.size} for 2)") if arguments.size < 2
-
- a = arguments[0]
- b = arguments[1]
-
- unless a.is_a?(Array) and b.is_a?(Array)
- raise(Puppet::ParseError, 'zip(): Requires array to work with')
- end
-
- flatten = function_str2bool([arguments[2]]) if arguments[2]
-
- result = a.zip(b)
- result = flatten ? result.flatten : result
-
- return result
- end
-end
-
-# vim: set ts=2 sw=2 et :
diff --git a/lib/puppet/provider/file_line/ruby.rb b/lib/puppet/provider/file_line/ruby.rb
deleted file mode 100644
index ae1a8b3d..00000000
--- a/lib/puppet/provider/file_line/ruby.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-Puppet::Type.type(:file_line).provide(:ruby) do
- def exists?
- lines.find do |line|
- line.chomp == resource[:line].chomp
- end
- end
-
- def create
- if resource[:match]
- handle_create_with_match
- elsif resource[:after]
- handle_create_with_after
- else
- append_line
- end
- end
-
- def destroy
- local_lines = lines
- File.open(resource[:path],'w') do |fh|
- fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join(''))
- end
- end
-
- private
- def lines
- # If this type is ever used with very large files, we should
- # write this in a different way, using a temp
- # file; for now assuming that this type is only used on
- # small-ish config files that can fit into memory without
- # too much trouble.
- @lines ||= File.readlines(resource[:path])
- end
-
- def handle_create_with_match()
- regex = resource[:match] ? Regexp.new(resource[:match]) : nil
- match_count = count_matches(regex)
- if match_count > 1 && resource[:multiple].to_s != 'true'
- raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'"
- end
- File.open(resource[:path], 'w') do |fh|
- lines.each do |l|
- fh.puts(regex.match(l) ? resource[:line] : l)
- end
-
- if (match_count == 0)
- fh.puts(resource[:line])
- end
- end
- end
-
- def handle_create_with_after
- regex = Regexp.new(resource[:after])
- count = count_matches(regex)
- case count
- when 1 # find the line to put our line after
- File.open(resource[:path], 'w') do |fh|
- lines.each do |l|
- fh.puts(l)
- if regex.match(l) then
- fh.puts(resource[:line])
- end
- end
- end
- when 0 # append the line to the end of the file
- append_line
- else
- raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'. One or no line must match the pattern."
- end
- end
-
- def count_matches(regex)
- lines.select{|l| l.match(regex)}.size
- end
-
- ##
- # append the line to the file.
- #
- # @api private
- def append_line
- File.open(resource[:path], 'a') do |fh|
- fh.puts resource[:line]
- end
- end
-end
diff --git a/lib/puppet/type/anchor.rb b/lib/puppet/type/anchor.rb
deleted file mode 100644
index fe1e5aa1..00000000
--- a/lib/puppet/type/anchor.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-Puppet::Type.newtype(:anchor) do
- desc <<-'ENDOFDESC'
- A simple resource type intended to be used as an anchor in a composite class.
-
- In Puppet 2.6, when a class declares another class, the resources in the
- interior class are not contained by the exterior class. This interacts badly
- with the pattern of composing complex modules from smaller classes, as it
- makes it impossible for end users to specify order relationships between the
- exterior class and other modules.
-
- The anchor type lets you work around this. By sandwiching any interior
- classes between two no-op resources that _are_ contained by the exterior
- class, you can ensure that all resources in the module are contained.
-
- class ntp {
- # These classes will have the correct order relationship with each
- # other. However, without anchors, they won't have any order
- # relationship to Class['ntp'].
- class { 'ntp::package': }
- -> class { 'ntp::config': }
- -> class { 'ntp::service': }
-
- # These two resources "anchor" the composed classes within the ntp
- # class.
- anchor { 'ntp::begin': } -> Class['ntp::package']
- Class['ntp::service'] -> anchor { 'ntp::end': }
- }
-
- This allows the end user of the ntp module to establish require and before
- relationships with Class['ntp']:
-
- class { 'ntp': } -> class { 'mcollective': }
- class { 'mcollective': } -> class { 'ntp': }
-
- ENDOFDESC
-
- newparam :name do
- desc "The name of the anchor resource."
- end
-
- def refresh
- # We don't do anything with them, but we need this to
- # show that we are "refresh aware" and not break the
- # chain of propagation.
- end
-end
diff --git a/lib/puppet/type/file_line.rb b/lib/puppet/type/file_line.rb
deleted file mode 100644
index df263e6a..00000000
--- a/lib/puppet/type/file_line.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-Puppet::Type.newtype(:file_line) do
-
- desc <<-EOT
- Ensures that a given line is contained within a file. The implementation
- matches the full line, including whitespace at the beginning and end. If
- the line is not contained in the given file, Puppet will add the line to
- ensure the desired state. Multiple resources may be declared to manage
- multiple lines in the same file.
-
- Example:
-
- file_line { 'sudo_rule':
- path => '/etc/sudoers',
- line => '%sudo ALL=(ALL) ALL',
- }
- file_line { 'sudo_rule_nopw':
- path => '/etc/sudoers',
- line => '%sudonopw ALL=(ALL) NOPASSWD: ALL',
- }
-
- In this example, Puppet will ensure both of the specified lines are
- contained in the file /etc/sudoers.
-
- **Autorequires:** If Puppet is managing the file that will contain the line
- being managed, the file_line resource will autorequire that file.
-
- EOT
-
- ensurable do
- defaultvalues
- defaultto :present
- end
-
- newparam(:name, :namevar => true) do
- desc 'An arbitrary name used as the identity of the resource.'
- end
-
- newparam(:match) do
- desc 'An optional regular expression to run against existing lines in the file;\n' +
- 'if a match is found, we replace that line rather than adding a new line.'
- end
-
- newparam(:multiple) do
- desc 'An optional value to determine if match can change multiple lines.'
- newvalues(true, false)
- end
-
- newparam(:after) do
- desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)'
- end
-
- newparam(:line) do
- desc 'The line to be appended to the file located by the path parameter.'
- end
-
- newparam(:path) do
- desc 'The file Puppet will ensure contains the line specified by the line parameter.'
- validate do |value|
- unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
- raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'")
- end
- end
- end
-
- # Autorequire the file resource if it's being managed
- autorequire(:file) do
- self[:path]
- end
-
- validate do
- unless self[:line] and self[:path]
- raise(Puppet::Error, "Both line and path are required attributes")
- end
- end
-end