]> gitweb.fluxo.info Git - leap/leap_cli.git/commitdiff
initial code commit
authorelijah <elijah@riseup.net>
Tue, 9 Oct 2012 07:01:55 +0000 (00:01 -0700)
committerelijah <elijah@riseup.net>
Tue, 9 Oct 2012 07:01:55 +0000 (00:01 -0700)
48 files changed:
cli/DEVNOTES [new file with mode: 0644]
cli/Gemfile [new file with mode: 0644]
cli/Gemfile.lock [new file with mode: 0644]
cli/README.md [new file with mode: 0644]
cli/Rakefile [new file with mode: 0644]
cli/bin/leap [new file with mode: 0755]
cli/leap_cli.gemspec [new file with mode: 0644]
cli/leap_cli.rdoc [new file with mode: 0644]
cli/lib/leap_cli.rb [new file with mode: 0644]
cli/lib/leap_cli/commands/README [new file with mode: 0644]
cli/lib/leap_cli/commands/compile.rb [new file with mode: 0644]
cli/lib/leap_cli/commands/deploy.rb [new file with mode: 0644]
cli/lib/leap_cli/commands/init.rb [new file with mode: 0644]
cli/lib/leap_cli/commands/list.rb [new file with mode: 0644]
cli/lib/leap_cli/commands/pre.rb [new file with mode: 0644]
cli/lib/leap_cli/config.rb [new file with mode: 0644]
cli/lib/leap_cli/config_list.rb [new file with mode: 0644]
cli/lib/leap_cli/config_manager.rb [new file with mode: 0644]
cli/lib/leap_cli/init.rb [new file with mode: 0644]
cli/lib/leap_cli/log.rb [new file with mode: 0644]
cli/lib/leap_cli/path.rb [new file with mode: 0644]
cli/lib/leap_cli/version.rb [new file with mode: 0644]
cli/test/default_test.rb [new file with mode: 0644]
cli/test/provider/common.json [new file with mode: 0644]
cli/test/provider/files/ca/ca.crt [new file with mode: 0644]
cli/test/provider/files/ca/ca.key [new file with mode: 0644]
cli/test/provider/files/public-definitions/provider.json.erb [new file with mode: 0644]
cli/test/provider/files/vpn1/vpn1.rewire.co.crt [new file with mode: 0644]
cli/test/provider/files/vpn1/vpn1.rewire.co.key [new file with mode: 0644]
cli/test/provider/hiera/couch1.rewire.yaml [new file with mode: 0644]
cli/test/provider/hiera/couch2.rewire.yaml [new file with mode: 0644]
cli/test/provider/hiera/ns1.rewire.yaml [new file with mode: 0644]
cli/test/provider/hiera/ns2.rewire.yaml [new file with mode: 0644]
cli/test/provider/hiera/vpn1.rewire.yaml [new file with mode: 0644]
cli/test/provider/hiera/web1.rewire.yaml [new file with mode: 0644]
cli/test/provider/nodes/couch1.json [new file with mode: 0644]
cli/test/provider/nodes/couch2.json [new file with mode: 0644]
cli/test/provider/nodes/ns1.json [new file with mode: 0644]
cli/test/provider/nodes/ns2.json [new file with mode: 0644]
cli/test/provider/nodes/vpn1.json [new file with mode: 0644]
cli/test/provider/nodes/web1.json [new file with mode: 0644]
cli/test/provider/provider.json [new file with mode: 0644]
cli/test/provider/services/couchdb.json [new file with mode: 0644]
cli/test/provider/services/dns.json [new file with mode: 0644]
cli/test/provider/services/openvpn.json [new file with mode: 0644]
cli/test/provider/services/webapp.json [new file with mode: 0644]
cli/test/provider/tags/production.json [new file with mode: 0644]
cli/test/test_helper.rb [new file with mode: 0644]

diff --git a/cli/DEVNOTES b/cli/DEVNOTES
new file mode 100644 (file)
index 0000000..eceac87
--- /dev/null
@@ -0,0 +1,97 @@
+Features to add
+==========================
+
+templates
+--------------------
+
+templates for nodes and services stored in leap_platform
+
+commands:
+
+    leap add-service   # menu of services, adding copies the template
+    leap add-node      # you pick a service, then it copies template
+
+deploy
+---------------------
+
+not yet working
+
+    leap bootstrap
+    leap dryrun
+    leap deploy
+
+key management
+-------------------------
+
+not yet working
+
+    leap add-ssh-keypair
+    leap add-x509-keypair
+
+hiera logic
+------------------------
+
+leap_platform should be able insert ruby logic in the generation of hiera .yml files. for example, so say that a webapp node should get a list of all the couchdb nodes its config.
+
+this code might look like this
+
+    node['couchdb_ips'] = @nodes[:services => :couchdb].map(&:ip_address)
+
+or
+
+    node['couchdb_ips'] = @services[:couchdb].nodes.map(&:ip_address)
+
+maybe see http://blog.bigbinary.com/2008/10/17/under-the-hood-how-named-scope-works.html
+
+
+json validation
+------------------------
+
+json validation
+http://www.kuwata-lab.com/kwalify/ruby/users-guide.html
+
+
+useful liberaries
+================================
+
+user interaction
+
+  readline
+  highline
+  terminal-tables
+  rainbow
+  http://stackoverflow.com/questions/9577718/what-ruby-libraries-should-i-use-for-building-a-console-based-application
+
+testing
+
+  aruba -- test for cli
+
+help
+
+  gem-man -- install man pages with gems
+  ronn -- write man pages in markdown
+
+push examples
+
+  https://github.com/net-ssh/net-ssh
+
+  https://github.com/seattlerb/rake-remote_task
+    http://docs.seattlerb.org/rake-remote_task/
+    https://github.com/seattlerb/rake-remote_task/blob/master/lib/rake/remote_task.rb
+
+  https://github.com/davidwinter/sooty
+    push puppet with rake/remote_task
+    https://github.com/davidwinter/sooty/blob/master/lib/sooty.rb
+
+  calling rsync from ruby
+    https://github.com/RichGuk/rrsync/blob/master/rrsync.rb
+    http://rubyforge.org/projects/six-rsync/
+
+  https://github.com/automateit/automateit
+
+  http://www.jedi.be/blog/2009/11/17/shell-scripting-dsl-in-ruby/
+
+  parallel shell
+    https://github.com/delano/rye
+    https://github.com/adamwiggins/rush
+
diff --git a/cli/Gemfile b/cli/Gemfile
new file mode 100644 (file)
index 0000000..e45e65f
--- /dev/null
@@ -0,0 +1,2 @@
+source :rubygems
+gemspec
diff --git a/cli/Gemfile.lock b/cli/Gemfile.lock
new file mode 100644 (file)
index 0000000..60ce6f8
--- /dev/null
@@ -0,0 +1,22 @@
+PATH
+  remote: .
+  specs:
+    leap_cli (0.0.1)
+      gli (~> 2.3)
+      oj
+      terminal-table
+
+GEM
+  remote: http://rubygems.org/
+  specs:
+    gli (2.3.0)
+    oj (1.3.7)
+    rake (0.9.2.2)
+    terminal-table (1.4.5)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  leap_cli!
+  rake
diff --git a/cli/README.md b/cli/README.md
new file mode 100644 (file)
index 0000000..fccd6d1
--- /dev/null
@@ -0,0 +1,132 @@
+About LEAP command line interface
+=================================
+
+This gem installs an executable 'leap' that allows you to manage servers using the leap platform.
+
+Installation
+=================================
+
+To install the gem:
+
+    gem install leap_cli
+
+To run from a clone of the git repo, see "Development", below.
+
+Usage
+=================================
+
+This tool is incomplete, so most commands don't yet work.
+
+Run `leap help` for a usage instructions.
+
+Here is an example usage:
+
+    leap init provider
+    cd provider
+    edit configuration files (see below)
+    leap compile
+
+Directories and Files
+=================================
+
+The general structure of leap project looks like this:
+
+    my_leap_project/                 # the 'root' directory
+      leap_platform/                 # a clone of the leap_platform puppet recipes
+      provider/                      # your provider-specific configurations
+
+The "leap" command should be run from within the "provider" directory.
+
+You can name these directories whatever you like. The leap command will walk up the directory tree until it finds a directory that looks like a 'root' directory.
+
+Within the "provider" directory:
+
+    nodes/               # one configuration file per node (i.e. server)
+    services/            # nodes inherit from these files if specified in node config.
+    tags/                # nodes inherit from these files if specified in node config.
+    files/               # text and binary files needed for services and nodes, including keypairs
+    users/               # crypto key material for sysadmins
+    common.yaml          # all nodes inherit these options
+    provider.yaml        # global service provider definition
+
+Configuration Files
+=================================
+
+All configuration files are in JSON format. For example
+
+    {
+      "key1": "value1",
+      "key2": "value2"
+    }
+
+Keys should match /[a-z0-9_]/
+
+Unlike traditional JSON, comments are allowed. If the first non-whitespace character is '#' the line is treated as a comment.
+
+    # this is a comment
+    {
+      # this is a comment
+      "key": "value"  # this is an error
+    }
+
+Options in the configuration files might be nested. For example:
+
+    {
+      "openvpn": {
+        "ip_address": "1.1.1.1"
+      }
+    }
+
+When compiled into hiera and made available in puppet, this becomes a Hash object with flattened keys:
+
+    {"openvpn.ip_address" => "1.1.1.1"}
+
+Node Configuration
+=================================
+
+The name of the file will be the hostname of the node.
+
+An example configuration "nodes/dns-europe.json"
+
+    {
+       "services": "dns",
+       "tags": ["production", "europe"],
+       "ip_address": "1.1.1.1"
+    }
+
+This node will have hostname "dns-europe" and it will inherit from the following files (in this order):
+
+    common.json
+    services/dns.json
+    tags/europe.json
+    tags/production.json
+
+Development
+=================================
+
+prerequisites:
+
+* rubygems (``apt-get install rubygems``)
+* bundler  (``gem install bundler``)
+
+Install command line ``leap``:
+
+    git clone git://leap.se/leap_cli   # clone leap cli code
+    cd leap_cli
+    bundle                             # install required gems
+    ln -s `pwd`/bin/leap ~/bin         # link executable somewhere in your bin path
+
+You can experiment using the example provider in the test directory
+
+    cd test/provider
+    leap
+
+Alternately, you can create your own provider for testing:
+
+    mkdir ~/dev/example.org
+    cd ~/dev/example.org
+    git clone git://leap.se/leap_platform
+    leap init provider
+    cd provider
+    leap
+
diff --git a/cli/Rakefile b/cli/Rakefile
new file mode 100644 (file)
index 0000000..c97688b
--- /dev/null
@@ -0,0 +1,44 @@
+require 'rake/clean'
+require 'rubygems'
+require 'rubygems/package_task'
+require 'rdoc/task'
+require 'cucumber'
+require 'cucumber/rake/task'
+Rake::RDocTask.new do |rd|
+  rd.main = "README.rdoc"
+  rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
+  rd.title = 'Your application title'
+end
+
+spec = eval(File.read('leap_cli.gemspec'))
+
+Gem::PackageTask.new(spec) do |pkg|
+end
+CUKE_RESULTS = 'results.html'
+CLEAN << CUKE_RESULTS
+desc 'Run features'
+Cucumber::Rake::Task.new(:features) do |t|
+  opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
+  opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
+  t.cucumber_opts =  opts
+  t.fork = false
+end
+
+desc 'Run features tagged as work-in-progress (@wip)'
+Cucumber::Rake::Task.new('features:wip') do |t|
+  tag_opts = ' --tags ~@pending'
+  tag_opts = ' --tags @wip'
+  t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}"
+  t.fork = false
+end
+
+task :cucumber => :features
+task 'cucumber:wip' => 'features:wip'
+task :wip => 'features:wip'
+require 'rake/testtask'
+Rake::TestTask.new do |t|
+  t.libs << "test"
+  t.test_files = FileList['test/*_test.rb']
+end
+
+task :default => [:test,:features]
diff --git a/cli/bin/leap b/cli/bin/leap
new file mode 100755 (executable)
index 0000000..4155b58
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby
+begin
+  require 'leap_cli'
+rescue LoadError
+  #
+  # When developing a gem with a command, you normally use `bundle exec bin/command-name`
+  # to run your app. At install-time, RubyGems will make sure lib, etc. are in the load path,
+  # so that you can run the command directly.
+  #
+  # However, I don't like using 'bundle exec'. It is slow, and limits which directory you can
+  # run in. So, instead, we fall back to some path manipulation hackery.
+  #
+  # This allows you to run the command directly while developing the gem, and also lets you
+  # run from anywhere (I like to link 'bin/leap' to /usr/local/bin/leap).
+  #
+  file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+  lib_dir = File.expand_path(File.dirname(file) + '/../lib')
+  $LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include?(lib_dir)
+  require 'rubygems'
+  require 'leap_cli'
+end
+
+require 'gli'
+require 'highline'
+require 'forwardable'
+require 'terminal-table'
+
+#
+# Typically, GLI and Highline methods are loaded into the global namespace.
+# Instead, here we load these into the module LeapCli::Commands in order to
+# ensure that the cli logic and code is kept isolated to leap_cli/commands/*.rb
+#
+# no cheating!
+#
+module LeapCli::Commands
+  extend GLI::App
+  extend Forwardable
+  extend Terminal::Table::TableHelper
+
+  ENV['GLI_DEBUG'] = "true"
+
+  #
+  # delegate highline methods to make them available to sub-commands
+  #
+  @terminal = HighLine.new
+  def_delegator :@terminal, :ask, 'self.ask'
+  def_delegator :@terminal, :agree, 'self.agree'
+  def_delegator :@terminal, :ask, 'self.ask'
+  def_delegator :@terminal, :choose, 'self.choose'
+  def_delegator :@terminal, :say, 'self.say'
+
+  #
+  # info about leap command line suite
+  #
+  program_desc       'LEAP platform command line interface'
+  program_long_desc  'This is the long description. It is very interesting.'
+  version            LeapCli::VERSION
+
+  #
+  # load commands and run
+  #
+  commands_from('leap_cli/commands')
+  exit run(ARGV)
+end
diff --git a/cli/leap_cli.gemspec b/cli/leap_cli.gemspec
new file mode 100644 (file)
index 0000000..208355d
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# Ensure we require the local version and not one we might have installed already
+#
+require File.join([File.dirname(__FILE__),'lib','leap_cli','version.rb'])
+
+spec = Gem::Specification.new do |s|
+
+  ##
+  ## ABOUT THIS GEM
+  ##
+  s.name = 'leap_cli'
+  s.version = LeapCli::VERSION
+  s.author = 'LEAP'
+  s.email = 'root@leap.se'
+  s.homepage = 'https://leap.se'
+  s.platform = Gem::Platform::RUBY
+  s.summary = 'Command line interface to the leap platform.'
+
+  ##
+  ## GEM FILES
+  ##
+  s.files = `find lib -name '*.rb'`.split("\n") << "bin/leap"
+  s.require_paths << 'lib'
+
+  s.bindir = 'bin'
+  s.executables << 'leap'
+
+  ##
+  ## DOCUMENTATION
+  ##
+  #s.has_rdoc = true
+  #s.extra_rdoc_files = ['README.rdoc','leap_cli.rdoc']
+  #s.rdoc_options << '--title' << 'leap_cli' << '--main' << 'README.rdoc' << '-ri'
+
+  ##
+  ## DEPENDENCIES
+  ##
+  s.add_development_dependency('rake')
+  #s.add_development_dependency('rdoc')
+  #s.add_development_dependency('aruba')
+
+  s.add_runtime_dependency('gli','~> 2.3')
+  s.add_runtime_dependency('oj')
+  s.add_runtime_dependency('terminal-table')
+end
diff --git a/cli/leap_cli.rdoc b/cli/leap_cli.rdoc
new file mode 100644 (file)
index 0000000..31d5deb
--- /dev/null
@@ -0,0 +1,5 @@
+= leap_cli
+
+Generate this with
+    leap_cli rdoc
+After you have described your command line interface
\ No newline at end of file
diff --git a/cli/lib/leap_cli.rb b/cli/lib/leap_cli.rb
new file mode 100644 (file)
index 0000000..b935e35
--- /dev/null
@@ -0,0 +1,19 @@
+module LeapCli; end
+
+unless defined?(LeapCli::VERSION)
+  # ^^ I am not sure why this is needed.
+  require 'leap_cli/version.rb'
+end
+
+require 'leap_cli/init'
+require 'leap_cli/path'
+require 'leap_cli/log'
+require 'leap_cli/config'
+require 'leap_cli/config_list'
+require 'leap_cli/config_manager'
+
+unless String.method_defined?(:to_a)
+  class String
+    def to_a; [self]; end
+  end
+end
\ No newline at end of file
diff --git a/cli/lib/leap_cli/commands/README b/cli/lib/leap_cli/commands/README
new file mode 100644 (file)
index 0000000..00fcd84
--- /dev/null
@@ -0,0 +1,101 @@
+This directory contains ruby source files that define the available sub-commands of the `leap` executable.
+
+For example, the command:
+
+  leap init <directory>
+
+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.
+
+
+      c.command
+      c.commands
+      c.default_command
+      c.default_value
+      c.get_default_command
+      c.commands
+      c.commands_declaration_order
+
+      c.flag
+      c.flags
+      c.switch
+      c.switches
+
+      c.long_desc
+
+      c.default_desc
+      c.default_description
+      c.desc
+      c.description
+      c.long_description
+      c.context_description
+      c.usage
+
+      c.arg_name
+      c.arguments_description
+      c.arguments_options
+
+      c.skips_post
+      c.skips_pre
+      c.skips_around
+
+      c.action
+
+      c.copy_options_to_aliases
+      c.nodoc
+      c.aliases
+      c.execute
+      c.names
+
+
+#desc 'Describe some switch here'
+#switch [:s,:switch]
+
+#desc 'Describe some flag here'
+#default_value 'the default'
+#arg_name 'The name of the argument'
+#flag [:f,:flagname]
+
+# desc 'Describe deploy here'
+# arg_name 'Describe arguments to deploy here'
+# command :deploy do |c|
+#   c.action do |global_options,options,args|
+#     puts "deploy command ran"
+#   end
+# end
+
+# desc 'Describe dryrun here'
+# arg_name 'Describe arguments to dryrun here'
+# command :dryrun do |c|
+#   c.action do |global_options,options,args|
+#     puts "dryrun command ran"
+#   end
+# end
+
+# desc 'Describe add-node here'
+# arg_name 'Describe arguments to add-node here'
+# command :"add-node" do |c|
+#   c.desc 'Describe a switch to init'
+#   c.switch :s
+#
+#   c.desc 'Describe a flag to init'
+#   c.default_value 'default'
+#   c.flag :f
+#   c.action do |global_options,options,args|
+#     puts "add-node command ran"
+#   end
+# end
+
+# post do |global,command,options,args|
+#   # Post logic here
+#   # Use skips_post before a command to skip this
+#   # block on that command only
+# end
+
+# on_error do |exception|
+#   # Error logic here
+#   # return false to skip default error handling
+#   true
+# end
diff --git a/cli/lib/leap_cli/commands/compile.rb b/cli/lib/leap_cli/commands/compile.rb
new file mode 100644 (file)
index 0000000..6b38de5
--- /dev/null
@@ -0,0 +1,15 @@
+module LeapCli
+  module Commands
+
+    desc 'Compile json files to hiera configs'
+    command :compile do |c|
+      c.action do |global_options,options,args|
+        manager = ConfigManager.new
+        manager.load(Path.provider)
+        Path.ensure_dir(Path.hiera)
+        manager.export(Path.hiera)
+      end
+    end
+
+  end
+end
\ No newline at end of file
diff --git a/cli/lib/leap_cli/commands/deploy.rb b/cli/lib/leap_cli/commands/deploy.rb
new file mode 100644 (file)
index 0000000..3694a38
--- /dev/null
@@ -0,0 +1,20 @@
+module LeapCli
+  module Commands
+
+    desc 'Apply recipes to a node or set of nodes'
+    long_desc 'The node filter can be the name of a node, service, or tag.'
+    arg_name '<node filter>'
+    command :deploy do |c|
+      c.action do |global_options,options,args|
+        nodes = ConfigManager.filter(args)
+        say "Deploying to these nodes: #{nodes.keys.join(', ')}"
+        if agree "Continue? "
+          say "deploy not yet implemented"
+        else
+          say "OK. Bye."
+        end
+      end
+    end
+
+  end
+end
\ No newline at end of file
diff --git a/cli/lib/leap_cli/commands/init.rb b/cli/lib/leap_cli/commands/init.rb
new file mode 100644 (file)
index 0000000..75cc876
--- /dev/null
@@ -0,0 +1,24 @@
+module LeapCli
+  module Commands
+    desc 'Creates a new provider configuration directory.'
+    arg_name '<directory>'
+    skips_pre
+    command :init do |c|
+      c.action do |global_options,options,args|
+        directory = args.first
+        unless directory && directory.any?
+          help_now! "Directory name is required."
+        end
+        directory = File.expand_path(directory)
+        if File.exists?(directory)
+          raise "#{directory} already exists."
+        end
+        if agree("Create directory '#{directory}'? ")
+          LeapCli.init(directory)
+        else
+          puts "OK, bye."
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/cli/lib/leap_cli/commands/list.rb b/cli/lib/leap_cli/commands/list.rb
new file mode 100644 (file)
index 0000000..a186049
--- /dev/null
@@ -0,0 +1,61 @@
+module LeapCli
+  module Commands
+
+    def self.print_config_table(type, config_list)
+      style = {:border_x => '-', :border_y => ':', :border_i => '-', :width => 60}
+
+      if type == :services
+        t = table do
+          self.style = style
+          self.headings = ['SERVICE', 'NODES']
+          list = config_list.keys.sort
+          list.each do |name|
+            add_row [name, config_list[name].nodes.keys.join(', ')]
+            add_separator unless name == list.last
+          end
+        end
+        puts t
+        puts "\n\n"
+      elsif type == :tags
+        t = table do
+          self.style = style
+          self.headings = ['TAG', 'NODES']
+          list = config_list.keys.sort
+          list.each do |name|
+            add_row [name, config_list[name].nodes.keys.join(', ')]
+            add_separator unless name == list.last
+          end
+        end
+        puts t
+        puts "\n\n"
+      elsif type == :nodes
+        t = table do
+          self.style = style
+          self.headings = ['NODE', 'SERVICES', 'TAGS']
+          list = config_list.keys.sort
+          list.each do |name|
+            add_row [name, config_list[name].services.to_a.join(', '), config_list[name].tags.to_a.join(', ')]
+            add_separator unless name == list.last
+          end
+        end
+        puts t
+      end
+    end
+
+    desc 'List nodes and their classifications'
+    long_desc 'Prints out a listing of nodes, services, or tags.'
+    arg_name 'filter'
+    command :list do |c|
+      c.action do |global_options,options,args|
+        if args.any?
+          print_config_table(:nodes, ConfigManager.filter(args))
+        else
+          print_config_table(:services, ConfigManager.services)
+          print_config_table(:tags,     ConfigManager.tags)
+          print_config_table(:nodes,  ConfigManager.nodes)
+        end
+      end
+    end
+
+  end
+end
diff --git a/cli/lib/leap_cli/commands/pre.rb b/cli/lib/leap_cli/commands/pre.rb
new file mode 100644 (file)
index 0000000..ae58fc8
--- /dev/null
@@ -0,0 +1,38 @@
+
+#
+# check to make sure we can find the root directory of the platform
+#
+module LeapCli
+  module Commands
+
+    desc 'Verbosity level 0..2'
+    arg_name 'level'
+    default_value '0'
+    flag [:v, :verbose]
+
+    desc 'Specify the root directory'
+    arg_name 'path'
+    default_value Path.root
+    flag [:root]
+
+    pre do |global,command,options,args|
+      #
+      # set verbosity
+      #
+      LeapCli.log_level = global[:verbose].to_i
+
+      #
+      # require a root directory
+      #
+      if global[:root]
+        Path.set_root(global[:root])
+      end
+      if Path.ok?
+        true
+      else
+        exit_now!("Could not find the root directory. Change current working directory or try --root")
+      end
+    end
+
+  end
+end
diff --git a/cli/lib/leap_cli/config.rb b/cli/lib/leap_cli/config.rb
new file mode 100644 (file)
index 0000000..44e66be
--- /dev/null
@@ -0,0 +1,119 @@
+module LeapCli
+  #
+  # This class represents the configuration for a single node, service, or tag.
+  #
+  class Config < Hash
+
+    def initialize(config_type, manager)
+      @manager = manager
+      @type = config_type
+    end
+
+    #
+    # lazily eval dynamic values when we encounter them.
+    #
+    def [](key)
+      value = fetch(key, nil)
+      if value.is_a? Array
+        value
+      elsif value.nil?
+        nil
+      else
+        if value =~ /^= (.*)$/
+          value = eval($1)
+          self[key] = value
+        end
+        value
+      end
+    end
+
+    #
+    # make the type appear to be a normal Hash in yaml.
+    #
+    def to_yaml_type
+     "!map"
+    end
+
+    #
+    # just like Hash#to_yaml, but sorted
+    #
+    def to_yaml(opts = {})
+      YAML::quick_emit(self, opts) do |out|
+        out.map(taguri, to_yaml_style) do |map|
+          keys.sort.each do |k|
+            v = self.fetch(k)
+            map.add(k, v)
+          end
+        end
+      end
+    end
+
+    #
+    # make obj['name'] available as obj.name
+    #
+    def method_missing(method, *args, &block)
+      if has_key?(method.to_s)
+        self[method.to_s]
+      else
+        super
+      end
+    end
+
+    #
+    # convert self into a plain hash, but only include the specified keys
+    #
+    def to_h(*keys)
+      keys.map(&:to_s).inject({}) do |hsh, key|
+        if has_key?(key)
+          hsh[key] = self[key]
+        end
+        hsh
+      end
+    end
+
+    def nodes
+      if @type == :node
+        @manager.nodes
+      else
+        @nodes ||= ConfigList.new
+      end
+    end
+
+    def services
+      if @type == :node
+        self['services'] || []
+      else
+        @manager.services
+      end
+    end
+
+    def tags
+      if @type == :node
+        self['tags'] || []
+      else
+        @manager.tags
+      end
+    end
+
+    private
+
+    ##
+    ## MACROS
+    ## these are methods used when eval'ing a value in the .json configuration
+    ##
+
+    #
+    # inserts the contents of a file
+    #
+    def file(filename)
+      filepath = Path.find_file(name, filename)
+      if filepath
+        File.read(filepath)
+      else
+        log0('no such file, "%s"' % filename)
+        ""
+      end
+    end
+
+  end # class
+end # module
\ No newline at end of file
diff --git a/cli/lib/leap_cli/config_list.rb b/cli/lib/leap_cli/config_list.rb
new file mode 100644 (file)
index 0000000..c8ff23b
--- /dev/null
@@ -0,0 +1,77 @@
+module LeapCli
+  class ConfigList < Hash
+
+    def initialize(config=nil)
+      if config
+        self << config
+      end
+    end
+
+    #
+    # if the key is a hash, we treat it as a condition and filter all the configs using the condition
+    #
+    # for example:
+    #
+    #   nodes[:public_dns => true]
+    #
+    # will return a ConfigList with node configs that have public_dns set to true
+    #
+    def [](key)
+      if key.is_a? Hash
+        results = ConfigList.new
+        field, match_value = key.to_a.first
+        field = field.is_a?(Symbol) ? field.to_s : field
+        match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
+        each do |name, config|
+          value = config[field]
+          if !value.nil?
+            if value.is_a? Array
+              if value.includes?(match_value)
+                results[name] = config
+              end
+            else
+              if value == match_value
+                results[name] = config
+              end
+            end
+          end
+        end
+        results
+      else
+        super
+      end
+    end
+
+    def <<(config)
+      if config.is_a? ConfigList
+        self.merge!(config)
+      else
+        self[config['name']] = config
+      end
+    end
+
+    #
+    # converts the hash of configs into an array of hashes, with ONLY the specified fields
+    #
+    def fields(*fields)
+      result = []
+      keys.sort.each do |name|
+        result << self[name].to_h(*fields)
+      end
+      result
+    end
+
+    #
+    # like fields(), but returns an array of values instead of an array of hashes.
+    #
+    def field(field)
+      field = field.to_s
+      result = []
+      keys.sort.each do |name|
+        result << self[name][field]
+      end
+      result
+    end
+
+  end
+end
diff --git a/cli/lib/leap_cli/config_manager.rb b/cli/lib/leap_cli/config_manager.rb
new file mode 100644 (file)
index 0000000..d383cc1
--- /dev/null
@@ -0,0 +1,200 @@
+require 'oj'
+require 'yaml'
+
+module LeapCli
+
+  class ConfigManager
+
+    attr_reader :services, :tags, :nodes
+
+    ##
+    ## IMPORT EXPORT
+    ##
+
+    #
+    # load .json configuration files
+    #
+    def load(dir)
+      @services = load_all_json("#{dir}/services/*.json")
+      @tags     = load_all_json("#{dir}/tags/*.json")
+      @common   = load_all_json("#{dir}/common.json")['common']
+      @nodes    = load_all_json("#{dir}/nodes/*.json", :node)
+      @nodes.each do |name, node|
+        apply_inheritance(node)
+      end
+      @nodes.each do |name, node|
+        node.each {|key,value| node[key] } # force evaluation of dynamic values
+      end
+    end
+
+    #
+    # save compiled hiera .yaml files
+    #
+    def export(dir)
+      Dir.glob(dir + '/*.yaml').each do |f|
+        File.unlink(f)
+      end
+      @nodes.each do |name, node|
+        File.open("#{dir}/#{name}.#{node.domain_internal}.yaml", 'w') do |f|
+          f.write node.to_yaml
+        end
+      end
+    end
+
+    ##
+    ## FILTERING
+    ##
+
+    #
+    # returns a node list consisting only of nodes that satisfy the filter criteria.
+    #
+    # filter: condition [condition] [condition] [+condition]
+    # condition: [node_name | service_name | tag_name]
+    #
+    # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
+    #
+    def filter(filters)
+      if filters.empty?
+        return nodes
+      end
+      if filters[0] =~ /^\+/
+        # don't let the first filter have a + prefix
+        filters[0] = filters[0][1..-1]
+      end
+
+      node_list = ConfigList.new
+      filters.each do |filter|
+        if filter =~ /^\+/
+          keep_list = nodes_for_filter(filter[1..-1])
+          node_list.delete_if do |name, node|
+            if keep_list[name]
+              false
+            else
+              true
+            end
+          end
+        else
+          node_list << nodes_for_filter(filter)
+        end
+      end
+      return node_list
+    end
+
+    ##
+    ## CLASS METHODS
+    ##
+
+    def self.manager
+      @manager ||= begin
+        manager = ConfigManager.new
+        manager.load(Path.provider)
+        manager
+      end
+    end
+
+    def self.filter(filters); manager.filter(filters); end
+    def self.nodes; manager.nodes; end
+    def self.services; manager.services; end
+    def self.tags; manager.tags; end
+
+    private
+
+    def load_all_json(pattern, config_type = :class)
+      results = ConfigList.new
+      Dir.glob(pattern).each do |filename|
+        obj = load_json(filename, config_type)
+        if obj
+          name = File.basename(filename).sub(/\.json$/,'')
+          obj['name'] = name
+          results[name] = obj
+        end
+      end
+      results
+    end
+
+    def load_json(filename, config_type)
+      log2 { filename.sub(/^#{Regexp.escape(Path.root)}/,'') }
+
+      #
+      # read file, strip out comments
+      # (File.read(filename) would be faster, but we like ability to have comments)
+      #
+      buffer = StringIO.new
+      File.open(filename) do |f|
+        while (line = f.gets)
+          next if line =~ /^\s*#/
+          buffer << line
+        end
+      end
+
+      # parse json, and flatten hash
+      begin
+        hash = Oj.load(buffer.string) || {}
+      rescue SyntaxError => exc
+        log0 'Error in file "%s":' % filename
+        log0 exc.to_s
+        return nil
+      end
+      return flatten_hash(hash, Config.new(config_type, self))
+    end
+
+    #
+    # remove all the nesting from a hash.
+    #
+    def flatten_hash(input = {}, output = {}, options = {})
+      input.each do |key, value|
+        key = options[:prefix].nil? ? "#{key}" : "#{options[:prefix]}#{options[:delimiter]||"_"}#{key}"
+        if value.is_a? Hash
+          flatten_hash(value, output, :prefix => key, :delimiter => options[:delimiter])
+        else
+          output[key]  = value
+        end
+      end
+      output
+    end
+
+    #
+    # makes this node inherit options from the common, service, and tag json files.
+    #
+    def apply_inheritance(node)
+      new_node = Config.new(:node, self)
+      new_node.merge!(@common)
+      if node['services']
+        node['services'].sort.each do |node_service|
+          service = @services[node_service]
+          if service.nil?
+            log0('Error in node "%s": the service "%s" does not exist.' % [node['name'], node_service])
+          else
+            new_node.merge!(service)
+            service.nodes << node # this is odd, but we want the node pointer, not new_node pointer.
+          end
+        end
+      end
+      if node['tags']
+        node['tags'].sort.each do |node_tag|
+          tag = @tags[node_tag]
+          if tag.nil?
+            log0('Error in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag])
+          else
+            new_node.merge!(tag)
+            tag.nodes << node
+          end
+        end
+      end
+      new_node.merge!(node)
+      node.replace(new_node)
+    end
+
+    def nodes_for_filter(filter)
+      if node = self.nodes[filter]
+        ConfigList.new(node)
+      elsif service = self.services[filter]
+        service.nodes
+      elsif tag = self.tags[filter]
+        tag.nodes
+      end
+    end
+
+  end
+
+end
diff --git a/cli/lib/leap_cli/init.rb b/cli/lib/leap_cli/init.rb
new file mode 100644 (file)
index 0000000..bebede7
--- /dev/null
@@ -0,0 +1,74 @@
+require 'fileutils'
+
+module LeapCli
+  #
+  # creates new provider directory
+  #
+  def self.init(directory)
+    dirs = [directory]
+    mkdirs(dirs, false, false)
+
+    Dir.chdir(directory) do
+      dirs = ["nodes", "services", "keys", "tags"]
+      mkdirs(dirs, false, false)
+
+      #puts "Creating .provider"
+      #FileUtils.touch('.provider')
+
+      mkfile("provider.json", PROVIDER_CONTENT)
+      mkfile("common.json", COMMON_CONTENT)
+    end
+  end
+
+  def self.mkfile(filename, content)
+    puts "Creating #{filename}"
+    File.open(filename, 'w') do |f|
+      f.write content
+    end
+  end
+
+  def self.mkdirs(dirs,force,dry_run)
+    exists = false
+    if !force
+      dirs.each do |dir|
+        if File.exist? dir
+          raise "#{dir} exists; use --force to override"
+          exists = true
+        end
+      end
+    end
+    if !exists
+      dirs.each do |dir|
+        puts "Creating #{dir}/"
+        if dry_run
+          puts "dry-run; #{dir} not created"
+        else
+          FileUtils.mkdir_p dir
+        end
+      end
+    else
+      puts "Exiting..."
+      return false
+    end
+    true
+  end
+
+  PROVIDER_CONTENT = <<EOS
+#
+# Global provider definition file.
+#
+{
+  "domain": "example.org"
+}
+EOS
+
+  COMMON_CONTENT = <<EOS
+#
+# Options put here are inherited by all nodes.
+#
+{
+  "domain": "example.org"
+}
+EOS
+
+end
diff --git a/cli/lib/leap_cli/log.rb b/cli/lib/leap_cli/log.rb
new file mode 100644 (file)
index 0000000..f51ca1e
--- /dev/null
@@ -0,0 +1,44 @@
+module LeapCli
+
+  def self.log_level
+    @log_level
+  end
+
+  def self.log_level=(value)
+    @log_level = value
+  end
+
+end
+
+def log0(message=nil, &block)
+  if message
+    puts message
+  elsif block
+    puts yield(block)
+  end
+end
+
+def log1(message=nil, &block)
+  if LeapCli.log_level > 0
+    if message
+      puts message
+    elsif block
+      puts yield(block)
+    end
+  end
+end
+
+def log2(message=nil, &block)
+  if LeapCli.log_level > 1
+    if message
+      puts message
+    elsif block
+      puts yield(block)
+    end
+  end
+end
+
+def help!(message=nil)
+  ENV['GLI_DEBUG'] = "false"
+  help_now!(message)
+end
diff --git a/cli/lib/leap_cli/path.rb b/cli/lib/leap_cli/path.rb
new file mode 100644 (file)
index 0000000..5dc8fe8
--- /dev/null
@@ -0,0 +1,79 @@
+require 'fileutils'
+
+module LeapCli
+  module Path
+
+    def self.root
+      @root ||= File.expand_path("#{provider}/..")
+    end
+
+    def self.platform
+      @platform ||= File.expand_path("#{root}/leap_platform")
+    end
+
+    def self.provider
+      @provider ||= if @root
+        File.expand_path("#{root}/provider")
+      else
+        find_in_directory_tree('provider.json')
+      end
+    end
+
+    def self.hiera
+      @hiera ||= "#{provider}/hiera"
+    end
+
+    def self.files
+      @files ||= "#{provider}/files"
+    end
+
+    def self.ok?
+      provider != '/'
+    end
+
+    def self.set_root(root_path)
+      @root = File.expand_path(root_path)
+      raise "No such directory '#{@root}'" unless File.directory?(@root)
+    end
+
+    def self.ensure_dir(dir)
+      unless File.directory?(dir)
+        if File.exists?(dir)
+          raise 'Unable to create directory "%s", file already exists.' % dir
+        else
+          FileUtils.mkdir_p(dir)
+        end
+      end
+    end
+
+    def self.find_file(name, filename)
+      path = [Path.files, filename].join('/')
+      return path if File.exists?(path)
+      path = [Path.files, name, filename].join('/')
+      return path if File.exists?(path)
+      path = [Path.files, 'nodes', name, filename].join('/')
+      return path if File.exists?(path)
+      path = [Path.files, 'services', name, filename].join('/')
+      return path if File.exists?(path)
+      path = [Path.files, 'tags', name, filename].join('/')
+      return path if File.exists?(path)
+
+      # give up
+      return nil
+    end
+
+    private
+
+    def self.find_in_directory_tree(filename)
+      search_dir = Dir.pwd
+      while search_dir != "/"
+        Dir.foreach(search_dir) do |f|
+          return search_dir if f == filename
+        end
+        search_dir = File.dirname(search_dir)
+      end
+      return search_dir
+    end
+
+  end
+end
diff --git a/cli/lib/leap_cli/version.rb b/cli/lib/leap_cli/version.rb
new file mode 100644 (file)
index 0000000..c272647
--- /dev/null
@@ -0,0 +1,3 @@
+module LeapCli
+  VERSION = '0.0.1'
+end
diff --git a/cli/test/default_test.rb b/cli/test/default_test.rb
new file mode 100644 (file)
index 0000000..c363bbb
--- /dev/null
@@ -0,0 +1,14 @@
+require 'test_helper'
+
+class DefaultTest < Test::Unit::TestCase
+
+  def setup
+  end
+
+  def teardown
+  end
+
+  def test_the_truth
+    assert true
+  end
+end
diff --git a/cli/test/provider/common.json b/cli/test/provider/common.json
new file mode 100644 (file)
index 0000000..ead9d68
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# Options put here are inherited by all nodes.
+#
+{
+  "domain": {
+     "public": "rewire.co",
+     "internal": "rewire"
+  },
+  "public_dns": true,
+  "fqdn": "= name + '.' + (public_dns ? domain_public : domain_internal)"
+}
diff --git a/cli/test/provider/files/ca/ca.crt b/cli/test/provider/files/ca/ca.crt
new file mode 100644 (file)
index 0000000..ed12e15
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAl2gAwIBAgIEUFDp9TANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRU
+RVNUMB4XDTEyMDkxMjIwMDA1M1oXDTEzMDkxMjIwMDA1M1owDzENMAsGA1UEAxME
+VEVTVDCCAbgwDQYJKoZIhvcNAQEBBQADggGlADCCAaACggGXANsoS1m9wj9iv+UV
+BXfeq14SR94gSot96eJu7PZVRrcGlGe/PRfbmfxF3j/gXM9B8sIkyM2L46OMtOKw
+1iOTKtYYdMhtnUSd3FRshWGtYeuy+OCe9umU0jfZDBZ2pXlUmSqCNqfD0OPkksYL
+GDjQUKjaEd1oURwpCG8uEU+3tjBNCMuEwhcMEoUYmI8t4vss2hdFb+LKefVMPTzz
+oiNM/o8Z/ANzWCC0qSW5FsB4wGhUS5HKLDOr4tACgdxaJSWtAqFFAnyMeG9g8aqe
+PTM+URlqVnzzGckrJwBbd4y0zEpv/R7SAiSAP725cnB1GKptwdrcNIIHnQjOdAOl
+uNg6JlRXrv6fV1gApka4INfJAf1yMf+fA0WdZ22UJQ9Up7tdzi8lL+3HsEpEx4Pz
+NyzuqzEw9LJ6SUmMcE/VP00t4RjTOVoncwcLjvURY8jt2DQ9E36JEPwUoyALq/De
+bGBjeK2KGzBZcOu1HZAwWLLWR2++WKuCEXbRbahwSIlbMfmAe8xGx4bbHol0D1A+
+wmu0uxjAze6FvUkCAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8E
+BQMDBwQAMB0GA1UdDgQWBBT/PX8XZ0Y2jDkppz6PHs23IgzQEDANBgkqhkiG9w0B
+AQsFAAOCAZcAMfi+HLbcFaB0/Mv8/GkIdjpThUBVEeFrIiDy9GmGWUDOXgP1Skld
+5H4eY5inE5lFfB69yacHIGS4OiZIBuBKfKNl5d6XO+ztJEJpG3yrbF4MtGV/aHEp
+OlbJCncnk3fspBk6tFGrv4Inak4gza6SQPfBEZj29ciwfwrqrtuWZ7km+og0Clcd
+pIB0g+DK0K//NtaDZDK0havQw2AFJKyXlNfI8XZ2jsNmQYR1wtiMci+UfGQr7bjn
+Kw9yyVCf0ohXvnSK4ortz/bDQbcMWkK0m/VCCEK8PSldk+XFzPWFWn5ndKCczcvd
+1BQc392n12ZstEuzm6+d9A0D3kCxralJUXUC+4kThq4Rtjey/gBjyZQnZ+5tIxMF
+5ZFAStEglNxqm6HB17q7owJqTvIg9Cf9GATsvoFFQDJrBXewRX7cWVeSr0zNSQB4
+ydIlSUOkyE3AyfLN+lx8NVS/I7gp4fWDuHrh27NKKDtMxalxPL5pTGO7l4uTybLY
+4aVzQYGvzA5HVS++VAtcTQ6TP9p4HURL2cllEU9u9A==
+-----END CERTIFICATE-----
diff --git a/cli/test/provider/files/ca/ca.key b/cli/test/provider/files/ca/ca.key
new file mode 100644 (file)
index 0000000..9721c35
--- /dev/null
@@ -0,0 +1,41 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/cli/test/provider/files/public-definitions/provider.json.erb b/cli/test/provider/files/public-definitions/provider.json.erb
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cli/test/provider/files/vpn1/vpn1.rewire.co.crt b/cli/test/provider/files/vpn1/vpn1.rewire.co.crt
new file mode 100644 (file)
index 0000000..d2c9734
--- /dev/null
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEWjCCAqygAwIBAgIEUFDqXzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRU
+RVNUMB4XDTEyMDkxMjIwMDIzOVoXDTEzMDkxMjIwMDIzOVowKzEPMA0GA1UEChMG
+eHgub3JnMRgwFgYDVQQDEw9ob3N0bmFtZS54eC5vcmcwggG4MA0GCSqGSIb3DQEB
+AQUAA4IBpQAwggGgAoIBlwCvGqkXry509EWGAqbFUB5nqvsvA3kSIh0prgzaPYCg
+MMst58ZB89eTgiuM+U3jSF7LZr+CuE1DAb4m2U2f8D8IfViwK1yCa5AohG+LCmwe
+4w4bgXtxwEBDac4u23JZ4AKE/EcKMeBbXe2d3o1S3P3XdN1ZVP0DVw04+H8cdh+0
+ggvhAA+9W/NvAQCAffL8sospj9nbl2OhRnrlFAzMWECyEyySPK5TEchU0hnFBRys
+DuYso5klLC6QXfSOkCSCOg9WQgjoyYBndTYxS3GwBnwyM+5V4TNtdc+P4vkkj+ZB
+D9R9oMpamUuuRQvk5/hJa7i8AaBy7ZmOO9QtL3866ONa6cLUN/QfBUkgM5iS6oQt
+X/qKxZFFYZPZUGBJqavuT+n4FB8XlIwcnqunK7rLD9OZwumYuZlHDtdAsgQ9Fd2z
+06e7sDp28jcrk6gmpOapLqNPtPHVOGNA6mCZza4LonDMOSQKTfb6ZEXty+a8f2ig
+zErhHvmyCvREytDc9pIf2bL+Sz4ULTq62GDKf1Y3tRi2uHFjhKLTAgMBAAGjdjB0
+MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0PAQH/BAUD
+AwegADAdBgNVHQ4EFgQUmQ5YZjESlkYq2FBaYqGp2HAnSfAwHwYDVR0jBBgwFoAU
+/z1/F2dGNow5Kac+jx7NtyIM0BAwDQYJKoZIhvcNAQELBQADggGXAHiaPMSeIzac
+rRZQ/dJA7VDgvuFcY67zj9531zsDVi848kBkpSZ+9UyZOdhy5b9Din/IAVvd/XUP
++VWwVsvKPbrWK79T/w8wj5nQR0uYfLdpMu7ZGjPhNes+/DnMX8Are2eb012g1km/
+HhOUxNg8/YpOJI4ZRnZls7j+u5kmHhc47sOQH9sY1FkHcWJ+K/lVhTk8Fmcm1vbN
+p7rjO4BItPVDxle0XF6bItwF1ahsK9MTzJIEO9ulHQnKYdhT9BcJbcwA3vhcn8nN
+uPN/RbDcWZTjONy58LVr7GxDQ267nZs5/wj4Cv3vDVq83kQJ7lCsYGTvxOejHWeQ
+QjcXREdBih9CPO3f86TOI8GRipIGvDcEll4DzgGRi+uTSWG69uC9yud/7+rnLd9Z
+WlobzAzRwljnR3aNACq0adYv1Wl05Fi2ab+QqL/C5ySrF5jL4OFUMpBu7nDPjty0
+KjQSmI9t6By6ORx14XT6piSlvSFn5phdMexXx1AYZEtdPSQduh2OquIPjN/qSdHO
+J+ZXOqDL1Jv+a89ghE8=
+-----END CERTIFICATE-----
diff --git a/cli/test/provider/files/vpn1/vpn1.rewire.co.key b/cli/test/provider/files/vpn1/vpn1.rewire.co.key
new file mode 100644 (file)
index 0000000..3fdf38a
--- /dev/null
@@ -0,0 +1,41 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIHRwIBAAKCAZcArxqpF68udPRFhgKmxVAeZ6r7LwN5EiIdKa4M2j2AoDDLLefG
+QfPXk4IrjPlN40hey2a/grhNQwG+JtlNn/A/CH1YsCtcgmuQKIRviwpsHuMOG4F7
+ccBAQ2nOLttyWeAChPxHCjHgW13tnd6NUtz913TdWVT9A1cNOPh/HHYftIIL4QAP
+vVvzbwEAgH3y/LKLKY/Z25djoUZ65RQMzFhAshMskjyuUxHIVNIZxQUcrA7mLKOZ
+JSwukF30jpAkgjoPVkII6MmAZ3U2MUtxsAZ8MjPuVeEzbXXPj+L5JI/mQQ/UfaDK
+WplLrkUL5Of4SWu4vAGgcu2ZjjvULS9/OujjWunC1Df0HwVJIDOYkuqELV/6isWR
+RWGT2VBgSamr7k/p+BQfF5SMHJ6rpyu6yw/TmcLpmLmZRw7XQLIEPRXds9Onu7A6
+dvI3K5OoJqTmqS6jT7Tx1ThjQOpgmc2uC6JwzDkkCk32+mRF7cvmvH9ooMxK4R75
+sgr0RMrQ3PaSH9my/ks+FC06uthgyn9WN7UYtrhxY4Si0wIDAQABAoIBlgvJw7Bw
+frQo7bVD4G5QInvgKuDTXwc1fLkdmofmtA4UutjwTYyLGew4Sy5GalPuv1L6K9Jl
+De6A44hCANPPnK65oYraoHO3QhE4OTonDXhW2NBJO0JBKxJewR6ub1hVmFXTlgS9
+rtj3zdNe9Cyr6/rhRzWIXzAmYCGBuSz1VtUUHDCdjHG3CwBiFOKRmBYi/vNhV81M
+t8xXrlZVrzbiihhy6gE+TI4TGGV9b3awDWoX5q8CpIC4JLpbWOdwFMUfm4C3GVpy
+lk5gubE/wnXiQyzqEzyHzC3OrNyh/JTbz2XBi+Agc45gRFL6t3EstNJY14lWwAy5
+pwLUFQnwVJQ0ljtA+qMo5nwGBaHgj1TutshLLcrP+cikule5DYm23VHU/u4epYPM
+hEB6KrYr7h3/IhXJ5rp5kmsJKGlg1vyPkwcskL5fMiN1BnPV5cwmrg574SsoDydr
+u40DJiijABVJG2aTnlOMGKyrnkbbOTq3adxjIWPPTK5r95pOWp3TpZWQzGa8Waum
+Q3S9LpmGCiVnuXTyGqRXAeECgcwAzPJWd5P/lCrVgmCd+cc+ldbG2SLQ/v3vDqe3
+R0UPnkIkmOOKw9cgC9qy8XgZb2hcRKDwifZBCVKTVi3NAdtF9WF5DLmwBP2NGdWk
+vNz9NF5Zd0GYa78Dec6Ej6nOJauDi5ymiJQxexx+N3I/ZjJMOpnIePz1yQbiB9dF
+YM6lifd8WoeahOvp1m92qlF637JL7hmXjagB0H+27bLgDD7dtUcigYMvPOuO5S0s
+Ec1PRg0lNhym9sJ0xm7uby88i83RyQKbCFEHyuQFZS8CgcwA2rk8X6WPF6NTmYP5
+VXnMAW1T1CoHCWQOW3KaYCHHgdWVTVl7MKXZ1zxz/8pKySX+QJrUsobSn7xjxGqT
+ZTcbhaFtEsfS4meEyn2Ef+yT2kslF59aYQfFAQ2HR5bhg2kNXFPwIpV3o5zwmJH9
+5H32XHjpneHT5QjTvQezsHtQbl61w8QqEmB5Cy5ZzcfSp+iZnR1gBquVgRSLpphE
+sUzmTAlm2W8FZNLw1cDyB+8hNDrp/t9RQfJzzcCi4TAgZWy+DKjO7nj6tl7oe50C
+gctgtVXh9T9b3l6DuC2zaLZ6pC+O1KQEPzUoGDIe+lKlXhbA4lZflUq07U0VLpPq
+AzfO1pbKsx37VTDbMJ+Vaa/4WzdwrsqFgFOtxieHS0xcAs16vcQ7y5XLS4038Wuq
+UOWw4ome1zcGHerdJRcPlVptKJX1qYAdjRbplkZRqRFqKhNO8MAUSvI70rsPIYW2
+uv6jawYdvRKmHS3nukmI3b1mxhtdO9b1iz4RnKA4AkaPCrLtdtW+iQHrhPsUEhki
+60s76/PWF85yieiV1wKBzACauN5UarFGb2r79bezF22QtN3P/8rqgbUGS5OY3Uxc
+M9Jh3SKfzzLCZylHkLpGgHHTEbPUdjsYdBO/JgUOXGVDqmWWG3S6Y7Az7YaFV71f
+djjO9RLiALUDgaZopfrxEqc44MfGLyVqv+ISi3Om5tQXphDcdpuGMTBXT9N0zEah
+TK4XxfRc+5Gkry0nvGrwDEJeOiFrloUzwmzndF9jbJqcvynaNgcCw5VKICsWIbrD
+T8mnWiIJHJF+wv51fa3tEXd/TQrU9w+jYo/ioQKBzACluagmFiDwMcJFowdUYyya
+WJtxEQHej5PfyHRijBZ/qzhvPxyF2Ae2D5L9RS+uHsJA0ZVJDQgzkvrSZ8IcS/Q4
+q+zX3/AzgDL6IQGQIsETaAmFCco4RMLFaDMyDx/OJR29df+ibqYvfSoUkcmK8OyF
+PWS0AobzJnqIaDpRCCvD/sL9PCkrUm33HoDBfxuvEsqZypNVmq+/3myWc9gIMOmZ
+fpWS+744tFnNO9RdmZ8OZel4+iv8CGZvQxk14S+lpaSCpX+Zmfyy5PfPRg==
+-----END RSA PRIVATE KEY-----
diff --git a/cli/test/provider/hiera/couch1.rewire.yaml b/cli/test/provider/hiera/couch1.rewire.yaml
new file mode 100644 (file)
index 0000000..2c731ce
--- /dev/null
@@ -0,0 +1,8 @@
+--- 
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: couch1.rewire
+ip_address: 245.2.45.42
+name: couch1
+public_dns: false
+services: couchdb
diff --git a/cli/test/provider/hiera/couch2.rewire.yaml b/cli/test/provider/hiera/couch2.rewire.yaml
new file mode 100644 (file)
index 0000000..a835c79
--- /dev/null
@@ -0,0 +1,8 @@
+--- 
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: couch2.rewire
+ip_address: 73.45.87.11
+name: couch2
+public_dns: false
+services: couchdb
diff --git a/cli/test/provider/hiera/ns1.rewire.yaml b/cli/test/provider/hiera/ns1.rewire.yaml
new file mode 100644 (file)
index 0000000..264dd52
--- /dev/null
@@ -0,0 +1,25 @@
+--- 
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: ns1.rewire.co
+hosts_private: 
+- ip_address: 245.2.45.42
+  fqdn: couch1.rewire
+- ip_address: 73.45.87.11
+  fqdn: couch2.rewire
+hosts_public: 
+- ip_address: 1.1.1.1
+  fqdn: ns1.rewire.co
+- ip_address: 1.1.1.2
+  fqdn: ns2.rewire.co
+- ip_address: 2.2.2.2
+  fqdn: vpn1.rewire.co
+- ip_address: 6.6.7.7
+  fqdn: web1.rewire.co
+  dns_alias: user.rewire.co
+ip_address: 1.1.1.1
+name: ns1
+production: true
+public_dns: true
+services: dns
+tags: production
diff --git a/cli/test/provider/hiera/ns2.rewire.yaml b/cli/test/provider/hiera/ns2.rewire.yaml
new file mode 100644 (file)
index 0000000..2885424
--- /dev/null
@@ -0,0 +1,25 @@
+--- 
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: ns2.rewire.co
+hosts_private: 
+- ip_address: 245.2.45.42
+  fqdn: couch1.rewire
+- ip_address: 73.45.87.11
+  fqdn: couch2.rewire
+hosts_public: 
+- ip_address: 1.1.1.1
+  fqdn: ns1.rewire.co
+- ip_address: 1.1.1.2
+  fqdn: ns2.rewire.co
+- ip_address: 2.2.2.2
+  fqdn: vpn1.rewire.co
+- ip_address: 6.6.7.7
+  fqdn: web1.rewire.co
+  dns_alias: user.rewire.co
+ip_address: 1.1.1.2
+name: ns2
+production: true
+public_dns: true
+services: dns
+tags: production
diff --git a/cli/test/provider/hiera/vpn1.rewire.yaml b/cli/test/provider/hiera/vpn1.rewire.yaml
new file mode 100644 (file)
index 0000000..158bb1b
--- /dev/null
@@ -0,0 +1,158 @@
+--- 
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: vpn1.rewire.co
+ip_address: 2.2.2.2
+name: vpn1
+openvpn_ca_crt: |
+  -----BEGIN CERTIFICATE-----
+  MIIECzCCAl2gAwIBAgIEUFDp9TANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRU
+  RVNUMB4XDTEyMDkxMjIwMDA1M1oXDTEzMDkxMjIwMDA1M1owDzENMAsGA1UEAxME
+  VEVTVDCCAbgwDQYJKoZIhvcNAQEBBQADggGlADCCAaACggGXANsoS1m9wj9iv+UV
+  BXfeq14SR94gSot96eJu7PZVRrcGlGe/PRfbmfxF3j/gXM9B8sIkyM2L46OMtOKw
+  1iOTKtYYdMhtnUSd3FRshWGtYeuy+OCe9umU0jfZDBZ2pXlUmSqCNqfD0OPkksYL
+  GDjQUKjaEd1oURwpCG8uEU+3tjBNCMuEwhcMEoUYmI8t4vss2hdFb+LKefVMPTzz
+  oiNM/o8Z/ANzWCC0qSW5FsB4wGhUS5HKLDOr4tACgdxaJSWtAqFFAnyMeG9g8aqe
+  PTM+URlqVnzzGckrJwBbd4y0zEpv/R7SAiSAP725cnB1GKptwdrcNIIHnQjOdAOl
+  uNg6JlRXrv6fV1gApka4INfJAf1yMf+fA0WdZ22UJQ9Up7tdzi8lL+3HsEpEx4Pz
+  NyzuqzEw9LJ6SUmMcE/VP00t4RjTOVoncwcLjvURY8jt2DQ9E36JEPwUoyALq/De
+  bGBjeK2KGzBZcOu1HZAwWLLWR2++WKuCEXbRbahwSIlbMfmAe8xGx4bbHol0D1A+
+  wmu0uxjAze6FvUkCAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8E
+  BQMDBwQAMB0GA1UdDgQWBBT/PX8XZ0Y2jDkppz6PHs23IgzQEDANBgkqhkiG9w0B
+  AQsFAAOCAZcAMfi+HLbcFaB0/Mv8/GkIdjpThUBVEeFrIiDy9GmGWUDOXgP1Skld
+  5H4eY5inE5lFfB69yacHIGS4OiZIBuBKfKNl5d6XO+ztJEJpG3yrbF4MtGV/aHEp
+  OlbJCncnk3fspBk6tFGrv4Inak4gza6SQPfBEZj29ciwfwrqrtuWZ7km+og0Clcd
+  pIB0g+DK0K//NtaDZDK0havQw2AFJKyXlNfI8XZ2jsNmQYR1wtiMci+UfGQr7bjn
+  Kw9yyVCf0ohXvnSK4ortz/bDQbcMWkK0m/VCCEK8PSldk+XFzPWFWn5ndKCczcvd
+  1BQc392n12ZstEuzm6+d9A0D3kCxralJUXUC+4kThq4Rtjey/gBjyZQnZ+5tIxMF
+  5ZFAStEglNxqm6HB17q7owJqTvIg9Cf9GATsvoFFQDJrBXewRX7cWVeSr0zNSQB4
+  ydIlSUOkyE3AyfLN+lx8NVS/I7gp4fWDuHrh27NKKDtMxalxPL5pTGO7l4uTybLY
+  4aVzQYGvzA5HVS++VAtcTQ6TP9p4HURL2cllEU9u9A==
+  -----END CERTIFICATE-----
+
+openvpn_ca_key: |
+  -----BEGIN RSA PRIVATE KEY-----
+  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
+  -----END RSA PRIVATE KEY-----
+
+openvpn_filter_dns: false
+openvpn_gateway_address: 3.3.3.3
+openvpn_nat: true
+openvpn_ports: 
+- "80"
+- "443"
+- "53"
+- "1194"
+openvpn_server_crt: |
+  -----BEGIN CERTIFICATE-----
+  MIIEWjCCAqygAwIBAgIEUFDqXzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwRU
+  RVNUMB4XDTEyMDkxMjIwMDIzOVoXDTEzMDkxMjIwMDIzOVowKzEPMA0GA1UEChMG
+  eHgub3JnMRgwFgYDVQQDEw9ob3N0bmFtZS54eC5vcmcwggG4MA0GCSqGSIb3DQEB
+  AQUAA4IBpQAwggGgAoIBlwCvGqkXry509EWGAqbFUB5nqvsvA3kSIh0prgzaPYCg
+  MMst58ZB89eTgiuM+U3jSF7LZr+CuE1DAb4m2U2f8D8IfViwK1yCa5AohG+LCmwe
+  4w4bgXtxwEBDac4u23JZ4AKE/EcKMeBbXe2d3o1S3P3XdN1ZVP0DVw04+H8cdh+0
+  ggvhAA+9W/NvAQCAffL8sospj9nbl2OhRnrlFAzMWECyEyySPK5TEchU0hnFBRys
+  DuYso5klLC6QXfSOkCSCOg9WQgjoyYBndTYxS3GwBnwyM+5V4TNtdc+P4vkkj+ZB
+  D9R9oMpamUuuRQvk5/hJa7i8AaBy7ZmOO9QtL3866ONa6cLUN/QfBUkgM5iS6oQt
+  X/qKxZFFYZPZUGBJqavuT+n4FB8XlIwcnqunK7rLD9OZwumYuZlHDtdAsgQ9Fd2z
+  06e7sDp28jcrk6gmpOapLqNPtPHVOGNA6mCZza4LonDMOSQKTfb6ZEXty+a8f2ig
+  zErhHvmyCvREytDc9pIf2bL+Sz4ULTq62GDKf1Y3tRi2uHFjhKLTAgMBAAGjdjB0
+  MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0PAQH/BAUD
+  AwegADAdBgNVHQ4EFgQUmQ5YZjESlkYq2FBaYqGp2HAnSfAwHwYDVR0jBBgwFoAU
+  /z1/F2dGNow5Kac+jx7NtyIM0BAwDQYJKoZIhvcNAQELBQADggGXAHiaPMSeIzac
+  rRZQ/dJA7VDgvuFcY67zj9531zsDVi848kBkpSZ+9UyZOdhy5b9Din/IAVvd/XUP
+  +VWwVsvKPbrWK79T/w8wj5nQR0uYfLdpMu7ZGjPhNes+/DnMX8Are2eb012g1km/
+  HhOUxNg8/YpOJI4ZRnZls7j+u5kmHhc47sOQH9sY1FkHcWJ+K/lVhTk8Fmcm1vbN
+  p7rjO4BItPVDxle0XF6bItwF1ahsK9MTzJIEO9ulHQnKYdhT9BcJbcwA3vhcn8nN
+  uPN/RbDcWZTjONy58LVr7GxDQ267nZs5/wj4Cv3vDVq83kQJ7lCsYGTvxOejHWeQ
+  QjcXREdBih9CPO3f86TOI8GRipIGvDcEll4DzgGRi+uTSWG69uC9yud/7+rnLd9Z
+  WlobzAzRwljnR3aNACq0adYv1Wl05Fi2ab+QqL/C5ySrF5jL4OFUMpBu7nDPjty0
+  KjQSmI9t6By6ORx14XT6piSlvSFn5phdMexXx1AYZEtdPSQduh2OquIPjN/qSdHO
+  J+ZXOqDL1Jv+a89ghE8=
+  -----END CERTIFICATE-----
+
+openvpn_server_key: |
+  -----BEGIN RSA PRIVATE KEY-----
+  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
+  -----END RSA PRIVATE KEY-----
+
+production: true
+public_dns: true
+services: openvpn
+tags: production
diff --git a/cli/test/provider/hiera/web1.rewire.yaml b/cli/test/provider/hiera/web1.rewire.yaml
new file mode 100644 (file)
index 0000000..6fc76d9
--- /dev/null
@@ -0,0 +1,16 @@
+--- 
+dns_alias: user.rewire.co
+domain_internal: rewire
+domain_public: rewire.co
+fqdn: web1.rewire.co
+ip_address: 6.6.7.7
+name: web1
+public_dns: true
+services: webapp
+webapp_couchdb_hosts: 
+- couch1.rewire
+- couch2.rewire
+webapp_modules: 
+- user
+- billing
+- help
diff --git a/cli/test/provider/nodes/couch1.json b/cli/test/provider/nodes/couch1.json
new file mode 100644 (file)
index 0000000..fe5d7e5
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  "services": "couchdb",
+  "ip_address": "245.2.45.42"
+}
\ No newline at end of file
diff --git a/cli/test/provider/nodes/couch2.json b/cli/test/provider/nodes/couch2.json
new file mode 100644 (file)
index 0000000..1fe2977
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  "services": "couchdb",
+  "ip_address": "73.45.87.11"
+}
\ No newline at end of file
diff --git a/cli/test/provider/nodes/ns1.json b/cli/test/provider/nodes/ns1.json
new file mode 100644 (file)
index 0000000..6323d10
--- /dev/null
@@ -0,0 +1,8 @@
+#
+# this is a comment
+#
+{
+  "services": "dns",
+  "tags": "production",
+  "ip_address": "1.1.1.1"
+}
\ No newline at end of file
diff --git a/cli/test/provider/nodes/ns2.json b/cli/test/provider/nodes/ns2.json
new file mode 100644 (file)
index 0000000..eeba9df
--- /dev/null
@@ -0,0 +1,8 @@
+#
+# A nameserver
+#
+{
+  "services": "dns",
+  "tags": "production",
+  "ip_address": "1.1.1.2"
+}
\ No newline at end of file
diff --git a/cli/test/provider/nodes/vpn1.json b/cli/test/provider/nodes/vpn1.json
new file mode 100644 (file)
index 0000000..10859ac
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "services": "openvpn",
+  "ip_address": "2.2.2.2",
+  "tags": "production",
+  "openvpn": {
+    "gateway_address": "3.3.3.3"
+  }
+}
\ No newline at end of file
diff --git a/cli/test/provider/nodes/web1.json b/cli/test/provider/nodes/web1.json
new file mode 100644 (file)
index 0000000..894fb6e
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  "services": "webapp",
+  "ip_address": "6.6.7.7"
+}
\ No newline at end of file
diff --git a/cli/test/provider/provider.json b/cli/test/provider/provider.json
new file mode 100644 (file)
index 0000000..58b6728
--- /dev/null
@@ -0,0 +1,13 @@
+#
+# General service provider configuration.
+#
+{
+  "name": "The Rewire Company",
+  "description": "A demonstration service provider using the LEAP platform",
+  "languages": ["en"],
+  "ca": {
+    "name": "Rewire Root CA",
+    "organization": "#{name}",
+    "bit_size": 4096
+  }
+}
\ No newline at end of file
diff --git a/cli/test/provider/services/couchdb.json b/cli/test/provider/services/couchdb.json
new file mode 100644 (file)
index 0000000..7c13c8d
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "public_dns": false
+}
\ No newline at end of file
diff --git a/cli/test/provider/services/dns.json b/cli/test/provider/services/dns.json
new file mode 100644 (file)
index 0000000..3fea381
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "hosts": {
+    "public":  "= nodes[:public_dns => true].fields(:fqdn, :dns_alias, :ip_address)",
+    "private": "= nodes[:public_dns => false].fields(:fqdn, :dns_alias, :ip_address)"
+  }
+}
\ No newline at end of file
diff --git a/cli/test/provider/services/openvpn.json b/cli/test/provider/services/openvpn.json
new file mode 100644 (file)
index 0000000..ffaa313
--- /dev/null
@@ -0,0 +1,11 @@
+{
+  "openvpn": {
+    "ports": ["80", "443", "53", "1194"],
+    "filter_dns": false,
+    "nat": true,
+    "ca_crt": "= file 'ca/ca.crt'",
+    "ca_key": "= file 'ca/ca.key'",
+    "server_crt": "= file fqdn + '.crt'",
+    "server_key": "= file fqdn + '.key'"
+  }
+}
diff --git a/cli/test/provider/services/webapp.json b/cli/test/provider/services/webapp.json
new file mode 100644 (file)
index 0000000..1513d6f
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "dns_alias": "= 'user.' + domain_public",
+  "webapp": {
+    "modules": ["user", "billing", "help"],
+    "couchdb_hosts": "= nodes[:services => :couchdb].field(:fqdn)"
+  }
+}
\ No newline at end of file
diff --git a/cli/test/provider/tags/production.json b/cli/test/provider/tags/production.json
new file mode 100644 (file)
index 0000000..b35c065
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "production": true
+}
\ No newline at end of file
diff --git a/cli/test/test_helper.rb b/cli/test/test_helper.rb
new file mode 100644 (file)
index 0000000..2e33705
--- /dev/null
@@ -0,0 +1,9 @@
+require 'test/unit'
+
+# Add test libraries you want to use here, e.g. mocha
+
+class Test::Unit::TestCase
+
+  # Add global extensions to the test case class here
+  
+end