diff options
| author | elijah <elijah@riseup.net> | 2012-10-09 00:05:44 -0700 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2012-10-09 00:05:44 -0700 | 
| commit | 73b126976ad7843eb47a84944cf191bf05b14216 (patch) | |
| tree | 918656f8d7c637e8c7a8f0c010eff55bfd98ae1b /lib/leap_cli | |
| parent | 578ac2f5dc7432317d7a022bed9d869ab89ee45c (diff) | |
fixed paths
Diffstat (limited to 'lib/leap_cli')
| -rw-r--r-- | lib/leap_cli/commands/README | 101 | ||||
| -rw-r--r-- | lib/leap_cli/commands/compile.rb | 15 | ||||
| -rw-r--r-- | lib/leap_cli/commands/deploy.rb | 20 | ||||
| -rw-r--r-- | lib/leap_cli/commands/init.rb | 24 | ||||
| -rw-r--r-- | lib/leap_cli/commands/list.rb | 61 | ||||
| -rw-r--r-- | lib/leap_cli/commands/pre.rb | 38 | ||||
| -rw-r--r-- | lib/leap_cli/config.rb | 119 | ||||
| -rw-r--r-- | lib/leap_cli/config_list.rb | 77 | ||||
| -rw-r--r-- | lib/leap_cli/config_manager.rb | 200 | ||||
| -rw-r--r-- | lib/leap_cli/init.rb | 74 | ||||
| -rw-r--r-- | lib/leap_cli/log.rb | 44 | ||||
| -rw-r--r-- | lib/leap_cli/path.rb | 79 | ||||
| -rw-r--r-- | lib/leap_cli/version.rb | 3 | 
13 files changed, 855 insertions, 0 deletions
| diff --git a/lib/leap_cli/commands/README b/lib/leap_cli/commands/README new file mode 100644 index 0000000..00fcd84 --- /dev/null +++ b/lib/leap_cli/commands/README @@ -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/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb new file mode 100644 index 0000000..6b38de5 --- /dev/null +++ b/lib/leap_cli/commands/compile.rb @@ -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/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb new file mode 100644 index 0000000..3694a38 --- /dev/null +++ b/lib/leap_cli/commands/deploy.rb @@ -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/lib/leap_cli/commands/init.rb b/lib/leap_cli/commands/init.rb new file mode 100644 index 0000000..75cc876 --- /dev/null +++ b/lib/leap_cli/commands/init.rb @@ -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/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb new file mode 100644 index 0000000..a186049 --- /dev/null +++ b/lib/leap_cli/commands/list.rb @@ -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/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb new file mode 100644 index 0000000..ae58fc8 --- /dev/null +++ b/lib/leap_cli/commands/pre.rb @@ -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/lib/leap_cli/config.rb b/lib/leap_cli/config.rb new file mode 100644 index 0000000..44e66be --- /dev/null +++ b/lib/leap_cli/config.rb @@ -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/lib/leap_cli/config_list.rb b/lib/leap_cli/config_list.rb new file mode 100644 index 0000000..c8ff23b --- /dev/null +++ b/lib/leap_cli/config_list.rb @@ -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/lib/leap_cli/config_manager.rb b/lib/leap_cli/config_manager.rb new file mode 100644 index 0000000..d383cc1 --- /dev/null +++ b/lib/leap_cli/config_manager.rb @@ -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/lib/leap_cli/init.rb b/lib/leap_cli/init.rb new file mode 100644 index 0000000..bebede7 --- /dev/null +++ b/lib/leap_cli/init.rb @@ -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/lib/leap_cli/log.rb b/lib/leap_cli/log.rb new file mode 100644 index 0000000..f51ca1e --- /dev/null +++ b/lib/leap_cli/log.rb @@ -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/lib/leap_cli/path.rb b/lib/leap_cli/path.rb new file mode 100644 index 0000000..5dc8fe8 --- /dev/null +++ b/lib/leap_cli/path.rb @@ -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/lib/leap_cli/version.rb b/lib/leap_cli/version.rb new file mode 100644 index 0000000..c272647 --- /dev/null +++ b/lib/leap_cli/version.rb @@ -0,0 +1,3 @@ +module LeapCli +  VERSION = '0.0.1' +end | 
