module Chef::Knife::ChopBase

Public Class Methods

included(includer) click to toggle source

# File lib/chef/knife/chop_base.rb, line 178
def self.included(includer)
  includer.class_eval do

    deps do
      require 'readline'
      require 'colorize'
      require 'inifile'
      require 'chef/knife/chop/chef_knife'
      require 'chef/environment'
      require 'chef/knife/core/object_loader'
      require 'chef/cookbook_loader'
      require 'chef/cookbook_uploader'
      require 'chef/knife/chop/cookbook_upload'
      require 'chef/knife/chop/data_bag_from_file'
      require 'chef/knife/chop/role_from_file'
      require 'chef/knife/chop/environment_from_file'
      require 'chef/knife/chop/chef_part'
      require 'chef/knife/chop/chef_environment'
      require 'chef/knife/chop/chef_role'
      #require 'chef/knife/chop/chef_data_bag_item'
      require 'chef/json_compat'
      require 'chef/knife/bootstrap'
      require 'chef/knife/chop/ui'
      Chef::Knife::Bootstrap.load_deps
    end

    attr_reader   :argv

    # This will print an args summary.
    option  :help,
            :short        => "-h",
            :long         => "--help",
            :description  => "Show this message",
            :show_options => true,
            :exit         => 1
    # print the version.
    option  :version,
            :short        => '-V',
            :long         => "--version",
            :description  => "Show version",
            :proc         => Proc.new{ puts ::Knife::Chop::VERSION },
            :exit         => 2
    option  :verbosity,
            :short        => "-v",
            :long         => "--[no-]verbose [LEVEL]",
            :description  => "Run verbosely",
            :proc         => lambda{|s|
              if s.nil? or (s == '')
                $CHOP.verbosity += 1
              else
                $CHOP.verbosity = v.gsub(%r/['"]*/, '').to_i
              end
            }
    option  :log_path,
            :long         => '--log-path PATH',
            :description  => "Log destination path"
    option  :log_file,
            :long         => '--log-file PATH',
            :description  => "Log destination file"
    option  :log_level,
            :short        => '-l',
            :long         => ['--log_level LEVEL','--log-level LEVEL'],
            :description  => "Log level (#{::Chef::Knife.loglevels.to_s})",
            :proc         => lambda{|v|
              if ::Chef::Knife.loglevels.include? v.to_sym
                v.to_sym
              else
                level = ::Chef::Knife.loglevels.select{|l| l.to_s.match(%r(^#{v}))}
                unless level.size > 0
                  raise OptionParser::InvalidOption.new("Invalid log level: #{v}. Valid levels are #{::Chef::Knife.loglevels.ai}")
                end
                level[0].to_sym
              end
            },
            :default      => :step
    option  :inifile,
            :short        => "-f",
            :long         => "--inifile FILE",
            :description  => "INI file with settings"
    option  :parts,
            :short        => "-R",
            :long         => "--resources PARTS",
            :description  => "Parts to upload #{[ :all, ::Chef::Knife.allparts].flatten }. Default: all",
            :default      => ::Chef::Knife.allparts,
            :proc         => lambda{|v|
              parts = $CHOP.parseOptionString(v,',','parsePartSymbol')
              parts.each{ |part|
                raise ::OptionParser::InvalidOption.new("Invalid part: #{part.to_s}. Valid parts are: #{[::Chef::Knife.allparts, :all].to_s}") unless [::Chef::Knife.allparts, :all].flatten.include?(part.to_sym)
              }
              parts
            }
    option  :depends,
            :short        => "-I",
            :long         => "--[no-]include-dependencies [yes|no|true|false|0|1|enable|disable]",
            :description  => "Include Cookbook dependencies?, Default --include-dependencies or -I [1|yes|enable|true]",
            :default      => true
    option  :dry_run,
            :short        => "-n",
            :long         => "--[no-]dry-run",
            :description  => "Do a dry run, Default --no-dry-run",
            :default      => false
    option  :cookbook_path,
            :short        => "-P",
            :long         => "--cookbook-path PATH",
            :description  => "Cookbook search path, Default chef/cookbooks/:chef/vendor-cookbooks",
            :default      => ["cookbooks","vendor-cookbooks"],
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v,'[:,]','parsePath')
            }
    option  :repo_path,
            :long         => "--repo-path PATH",
            :description  => "Chef repo path, Default ./chef",
            :default      => ".",
            #:required     => true,
            :proc         => lambda{|v|
              File.expand_path(v)
            }
    option  :cookbooks,
            :short        => "-c",
            :long         => "--cookbooks COOKBOOKS",
            :description  => "Cookbooks to upload (List separated by commas or --all. Default: role",
            :default      => ['role'],
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v)
            }
    option  :envs,
            :short        => ["-e", "-E",],
            :long         => "--environments REGEXLIST",
            :description  => "Environments regex",
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v)
            },
            :default      => ['.*']
    option  :databags,
            :short        => "-b",
            :long         => "--databags BAGS",
            :description  => "Data bags to upload",
            :default      => ['.*:.*'],
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v)
            }
    option  :roles,
            :short        => "-r",
            :long         => "--roles ROLES",
            :description  => "Roles to upload",
            :default      => [".*"],
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v)
            }

    option :all,
           :short => "-a",
           :long  => "--all",
           :description => "Upload all items for resource group(s)"

    option :trace,
           :short => "-t",
           :long  => "--trace",
           :boolean => true,
           :default => false,
           :description => "Trace logging locations (file::line)"

    # ------------------------------------------------------------------------------------------------------------
    # Cookbooks
    # ------------------------------------------------------------------------------------------------------------
    option :freeze,
           :long => '--freeze',
           :description => 'Freeze this version of the cookbook so that it cannot be overwritten',
           :boolean => true

    #option :all,
    #       :short => "-a",
    #       :long => "--all",
    #       :description => "Upload all cookbooks, rather than just a single cookbook"

    option :force,
           :long => '--force',
           :boolean => true,
           :description => "Update cookbook versions even if they have been frozen"

    # ------------------------------------------------------------------------------------------------------------
    # Data bags
    # ------------------------------------------------------------------------------------------------------------
    option :secret,
           :short => "-s SECRET",
           :long  => "--secret ",
           :description => "The secret key to use to encrypt data bag item values"

    option :secret_file,
           :long => "--secret-file SECRET_FILE",
           :description => "A file containing the secret key to use to encrypt data bag item values"


    # ------------------------------------------------------------------------------------------------------------
    option  :precedence,
            :long         => "--precedence PREC",
            :description  => "Precedence order of parts extensions. Default: json:1,rb:2 or json,rb == [json rb] == { json => 1, rb => 2 } == .rb files will be used when there is both a .json and .rb",
            :default      => %w(json rb),
            :proc         => lambda{|v|
              prec = $CHOP.parseOptionString(v,',', 'parsePrecedence')
              prec.sort{|x,y| x.values.shift <=> y.values.shift }.map{|e| e.keys.shift }
            }
    option  :actions,
            :short        => '-a',
            :long         => "--action ACTION",
            :description  => "Actions to be performed #{[ :all, ::Chef::Knife.allactions].flatten }. Default: upload",
            #:default      => [:upload],
            :proc         => lambda{|v|
              actions = $CHOP.parseOptionString(v,',','parseActionSymbol')
              actions.each{ |act|
                raise ::OptionParser::InvalidOption.new("Invalid action: #{act.to_s}. Valid actions are: #{[ :all, ::Chef::Knife.allactions].flatten.to_s}") unless [ :all, ::Chef::Knife.allactions].flatten.include?(act.to_sym)
              }
              actions
            }
    option  :translate,
            :long         => "--translate PREC",
            :description  => "Translate parts. Default: json,rb == { :from => 'json', :to => 'rb' } == .json files will be read and .rb equivalents will be saved",
            :default      => %w(json rb),
            :proc         => lambda{|v|
              $CHOP.parseOptionString(v)
            }
  end
end
new(argv=[]) click to toggle source

Create a new instance of the current class configured for the given arguments and options

Calls superclass method
# File lib/chef/knife/chop_base.rb, line 405
def initialize(argv=[])
  @argv       = argv
  $CHOP       = self
  @verbosity  = 0
  @inis       = []
  @use_knife_api = true

  @stop = false
  @prec_max = 0
  @TODO = {}
  @actors = {}

  super
end

Public Instance Methods

build_option_arguments(opt_setting) click to toggle source

Calls superclass method
# File lib/chef/knife/chop_base.rb, line 421
def build_option_arguments(opt_setting)
  arguments = super
  arguments.flatten
end
configure_chef() click to toggle source

Calls superclass method
# File lib/chef/knife/chop_base.rb, line 470
def configure_chef
  super
  @config[:log_opts] ||= lambda{|mlll| {
      :pattern      => "%#{mlll}l: %m %C\n",
      :date_pattern => '%Y-%m-%d %H:%M:%S',
    }
  }

  @logger = getLogger(@config)
  @ui = Chef::Knife::ChopUI.new(@logger,@config)
  Chef::Log.logger = @logger
end
parseActionSymbol(v) click to toggle source

# File lib/chef/knife/chop_base.rb, line 106
def parseActionSymbol(v)
  if v.to_sym == :all
    ::Chef::Knife.allactions
  else
    s = v.to_sym
    allactions = [::Chef::Knife.allactions, :all].flatten
    unless allactions.include?(s)
      allactions.each{ |p|
        s = p if p.match(%r/^#{s}/)
      }
    end
    s = ::Chef::Knife.allactions if s == :all
    s
  end
end
parseList(v,s=',',method='parseString') click to toggle source

# File lib/chef/knife/chop_base.rb, line 133
def parseList(v,s=',',method='parseString')
  parts = []
  a = v.split(%r/#{s}/)
  a.each{ |t|
    parts << send(method,t)
  }
  parts
end
parseOptionString(v,s=',',method='parseString') click to toggle source

# File lib/chef/knife/chop_base.rb, line 143
def parseOptionString(v,s=',',method='parseString')
  bags = []
  if v.match(%r'#{s}')
    bags << parseList(v,s,method)
  else
    bags << send(method,v)
  end
  bags.flatten
end
parsePartSymbol(v) click to toggle source

# File lib/chef/knife/chop_base.rb, line 89
def parsePartSymbol(v)
  if v.to_sym == :all
    ::Chef::Knife.allparts
  else
    s = v.to_sym
    allparts = [::Chef::Knife.allparts, :all].flatten
    unless allparts.include?(s)
      allparts.each{ |p|
        s = p if p.match(%r/^#{s}/)
      }
    end
    s = ::Chef::Knife.allparts if s == :all
    s
  end
end
parsePath(v) click to toggle source

# File lib/chef/knife/chop_base.rb, line 128
def parsePath(v)
  File.expand_path(parseString(v))
end
parsePrecedence(v) click to toggle source

# File lib/chef/knife/chop_base.rb, line 154
def parsePrecedence(v)
  @prec_max += 1
  match = v.match(%r/^(json|rb|yaml)$/i)
  unless match
    m = "ERROR: Invalid precedence argument: #{v}. Accept only from this set: [json,rb,yaml]"
    puts m
    raise Exception.new(m)
  end
  s = { v => @prec_max }
  match = v.match(%r/^(\S+):(\d+)$/)
  if match
    begin
      a = match[1]
      i = match[2].to_i
      s = { a => i }
    rescue => e
      puts "ERROR: Unable to match precedence #{v}"
      raise e
    end
  end
  s
end
parseString(v) click to toggle source

# File lib/chef/knife/chop_base.rb, line 123
def parseString(v)
  v
end
parse_options(args,source=nil) click to toggle source

Calls superclass method
# File lib/chef/knife/chop_base.rb, line 427
def parse_options(args,source=nil)
  argv = super(args)

  @config = parse_and_validate_options(@config,source ? source : "ARGV - #{File.basename(__FILE__)}:#{__LINE__}")
  v = @config[:depends]
  m = (v.is_a?(String) && v.downcase.match(%r/^(no|false|disable|0)/) )
  @config[:depends] = (v === true) || (m.nil? ? true : false)

  unless @config[:actions]
    @config[:actions] = [ argv[1].to_sym ] # self.class.name.gsub(%r(Chef::Knife::Chop), '').downcase
  end
  @actors[argv[1].to_sym] = self
  others = @config[:actions].select{|a|
    a != argv[1].to_sym
  }
  index   = args.index '--action'
  others.each{|a|
    args[1] = a.to_s
    unless index.nil?
      args[index+1] = a.to_s
    end
    subcommand_class = ::Chef::Knife.subcommand_class_from(args)
    subcommand_class.load_deps
    instance = subcommand_class.new(args)
    instance.configure_chef
    @actors[a] = instance
  }
  argv
end
run_with_pretty_exceptions() click to toggle source
# File lib/chef/knife/chop_base.rb, line 483
def run_with_pretty_exceptions
  unless self.respond_to?(:run)
    ui.error "You need to add a #run method to your knife command before you can use it"
  end
  enforce_path_sanity

  raise ChopOptionError.new("The --repo-path '#{@config[:repo_path]}' is invalid!") unless File.directory?(@config[:repo_path])

  run
rescue ChopOptionError => e
  raise if Chef::Config[:verbosity] == 2
  humanize_exception(e)
  exit 100
rescue ChopError => e
  humanize_exception(e)
  exit 101
end

Private Instance Methods

callCmdProc(cmdp, a, b, c) click to toggle source

# File lib/chef/knife/chop_base.rb, line 768
def callCmdProc(cmdp, a, b, c)
  ret = nil
  begin
    if cmdp.is_a?(String)
      ret = cmdp
    elsif cmdp.is_a?(Proc)
      ret = cmdp.call(a, b, c)
    else
      raise ChopInternalError.new("'#{cmdp}' is not a Proc, Lambda or String!")
    end
  rescue ChopInternalError => e
    raise e
  rescue => e
    @logger.fatal "#{e.class.name} #{e.message}"
    raise ChopError.new("#{e.class.name} #{e.message}")
  end
  ret
end
checkArgsSources(options) click to toggle source

# File lib/chef/knife/chop_base.rb, line 619
def checkArgsSources(options)
  if @origins
    missing = @origins.select{ |k,v|
      v.nil?
    }.map{ |k,v| k }
    raise ChopError.new("Missing origins: #{missing.ai}") if missing.size > 0
  end
end
databags(options=nil,exts=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 788
def databags(options=nil,exts=nil)
  options = @config unless options
  unless @databags

    want = Hash.new
    options[:databags].each{ |b|
      match = b.match(%r/^(.*):(.*)$/)
      if match
        want[match[1]] = parseOptionString(match[2],';')
      end
    }
    @logger.debug want.ai

    chef = options[:repo_path]
    raise ChopError.new "Oops! Where is the '#{chef}' directory? Also check cookbook path '#{options[:cookbook_path]}'" unless File.directory?(chef)

    @databags={}
    Dir.glob("#{chef}/data_bags/*").each{ |d|
      if File.directory?(d)
        name  = File.basename(d)
        regex = "^(#{want.keys.join('|')})"
        match = matches(name,regex)
        if match
          @databags[name] = getPathSet(want[name], "data_bags/#{name}", exts)
          @logger.info "Data bags list: #{@databags[name].values.map{|f| "#{name}/#{File.basename(f)}" }}"
        end
      end
    }
  end
  @databags
end
environments(options=nil,exts=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 831
def environments(options=nil,exts=nil)
  options = @config unless options
  unless @environments
    @environments = getPathSet(options[:envs], 'environments', exts)
    @logger.info "Environments list: #{@environments.values.map{|f| File.basename(f)}.ai}"
  end
  @environments
end
execute(cmd,lead) click to toggle source

# File lib/chef/knife/chop_base.rb, line 733
def execute(cmd,lead)
  exit 1 if stop
  print lead if @logger.level < 4
  system cmd
end
getKnifeSubCommand(rsrc, verb) click to toggle source

# File lib/chef/knife/chop_base.rb, line 756
def getKnifeSubCommand(rsrc, verb)
  # WARNING: Don't be clever ... rsrc and verb can each have one or more spaces ... argv = [rsrc, verb]
  argv = "#{rsrc} #{verb}".split(%r(\s+))
  klass= ::Chef::Knife.subcommand_class_from(argv)
  subc = klass.new
  subc.config = @config.dup
  subc.config[:cookbook_path] = @config[:cookbook_path].map { |p| p.match(%r(^/)) ? p : "#{@config[:repo_path]}/#{p}" } #.join(::File::PATH_SEPARATOR)
  subc.ui = ::Chef::Knife::ChopUI.new(@logger, @config)
  subc
end
getPathSet(want, path, exts=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 647
def getPathSet(want, path, exts=nil)
  raise ChopInternalError.new("Bad call to #{self.class.name}.getPathSet: want == nil") unless want
  @logger.debug "Look for #{want.ai} in #{[path]} with #{exts} extensions"
  if exts.nil?
    exts = @config[:precedence]
  end
  file_regex=%r/^(\S+)\.(#{exts.join('|')})$/
  if exts.empty?
    file_regex=%r/^(\S+)()$/
    exts=['']
  end
  regex = "^(#{want.join('|')})$"
  set = {}
  chef = @config[:repo_path]
  raise ChopError.new "Oops! Where is the '#{chef}' directory? Also check cookbook path '#{@config[:cookbook_path]}'" unless File.directory?(chef)
  abs = File.realpath(File.expand_path("#{chef}/#{path}"))
  raise ChopError.new "Oops! Does 'chef/#{path}' directory exist?" unless File.directory?(abs)
  begin
    Dir.glob("#{abs}/*").each{ |f|
      match = File.basename(f).match(file_regex)
      if match
        name = match[1]
        ext  = match[2]
        set[ext] = {} unless set[ext]
        @logger.trace "#{name} =~ #{regex}"
        set[ext][name] = f if name.match(regex)
      end
    }
  rescue RegexpError => e
    raise ChopError.new "The regular expression attempting to match resources in '#{path}' is incorrect! #{e.message}"
  end
  @logger.debug "getPathSet set=#{set.ai}"
  res = {}
  # Iterate extension sets in increasing precedence order ...
  # Survivor will be the most desireable version of the item
  # i.e. the .rb environment, role, data bag, etc. will be preferred over the .json version
  exts.each{ |e|
    h = set[e]
    if h
      h.each{ |n,f|
        @logger.warn "Ignoring #{File.basename(res[n])}" if res[n]
        res[n] = f
      }
    else
      @logger.warn "'#{e}' set is empty! (No #{path}/*.#{e} files found using precedence #{exts})"
    end
  }
  set = res
end
matches(string, criterium) click to toggle source

# File lib/chef/knife/chop_base.rb, line 724
def matches(string, criterium)
  if criterium =~ %r/[\.\+\*\(\)\|\,\{\}\?\[\]\^\$]|\\[sSdDAzwWb]/
    string.match(%r/#{criterium}/)
  else
    string == criterium
  end
end
parseINIFile(options=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 506
def parseINIFile(options=nil)
  options = @config unless options
  if options.key?(:inifile)
    logStep "Parse INI file - #{options[:inifile]}"
    options[:inifile] = File.expand_path(options[:inifile])
    raise ChopError.new("Cannot find inifile (#{options[:inifile]})") unless File.exist?(options[:inifile])
    raise ChopError.new("Recursive call to inifile == '#{options[:inifile]}'") if @inis.include?(options[:inifile])
    ini = nil
    begin
      ini = IniFile.load(options[:inifile])
      @inis << options[:inifile]
      ini['global'].each { |key, value|
        logger.trace "#{key}=#{value} (#{value.class.name})"
        ENV[key]= value.nil? ? '' : value.to_s
      }
      argv=[]
      cli = ini['cli'] || []
      cli.each{ |key,value|
        argv << key.gsub(%r/:[0-9]+$/, '').gsub(%r/^([^-])/, '--\1')
        argv << value
      }
      if argv.size > 0
        parse_options(argv,"INI-#{options[:inifile]}")
      end
    rescue => e
      puts e.message.light_red
      logger.trace e.backtrace.ai
      raise e
    end
  end
  options
end
parse_and_validate_options(options=nil,source='ARGV') click to toggle source

# File lib/chef/knife/chop_base.rb, line 568
def parse_and_validate_options(options=nil,source='ARGV')
  options = @config unless options
  setOrigins(options,source)

  #options = parseOptions(options,source)
  unless @origins and @name_key_map
    # These are the essential default options which things like parseOptions depend on
    {
        :verbosity    => @verbosity,
        :auto_purge   => false,
    }.each{ |k,v|
      options[k] = v unless options[k]
    }
    setOrigins(options,'hardcoded-default')

    @name_key_map    = {} unless @name_key_map
    @options.each{ |name,args|
      @name_key_map[name]  = {} unless @name_key_map[name]
      [:short,:long,:description].each{|key|
        @name_key_map[name][key] = args[key] if args[key]
      }
    }
  end

  begin
    parseINIFile(options)
    setDefaultOptions(options)
    # Check for all the necessary options
    validate_options(options)
    checkArgsSources(options)
    #findRootPath(options)
  rescue ChopError => e
    puts e.message.light_red
    exit -1
  rescue Exception => e
    puts e.message.light_red
    exit -2
  end

  options
end
roles(options=nil,exts=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 821
def roles(options=nil,exts=nil)
  options = @config unless options
  unless @roles
    @roles = getPathSet(options[:roles], 'roles', exts)
    @logger.info "Roles list: #{@roles.values.map{|f| File.basename(f)}.ai}"
  end
  @roles
end
setDefaultOptions(options) click to toggle source

# File lib/chef/knife/chop_base.rb, line 540
def setDefaultOptions(options)
  @options.each{|name,args|
    if args[:default]
      options[name] = args[:default] unless options[name]
    end
  }
  setOrigins(options,'default')
end
setOrigins(options,source) click to toggle source

# File lib/chef/knife/chop_base.rb, line 611
def setOrigins(options,source)
  @origins = {} unless @origins
  options.each { |key, val|
    @origins[key] = source unless (@origins[key])
  }
end
todo(msg) click to toggle source

# File lib/chef/knife/chop_base.rb, line 698
def todo(msg)

  # Regular expression used to parse out caller information
  #
  # * $1 == filename
  # * $2 == line number
  # * $3 == method name (might be nil)
  caller_rgxp = %r/([-\.\/\(\)\w]+):(\d+)(?::in `(\w+)')?/o
  #CALLER_INDEX = 2
  caller_index = ((defined? JRUBY_VERSION and JRUBY_VERSION[%r/^1.6/]) or (defined? RUBY_ENGINE and RUBY_ENGINE[%r/^rbx/i])) ? 1 : 2
  stack = Kernel.caller[caller_index]
  return if stack.nil?

  match = caller_rgxp.match(stack)
  file = match[1]
  line = Integer(match[2])
  modl = match[3] unless match[3].nil?

  unless @TODO[line]
    le = ::Logging::LogEvent.new(@logger, ::Logging::LEVELS['todo'], msg, true)
    @logger.logEvent(le) unless @TODO[line]
    @TODO[line] = true
  end
end
validate_options(options=nil) click to toggle source

# File lib/chef/knife/chop_base.rb, line 550
def validate_options(options=nil)
  options = @config unless options

  # # Check for the necessary environment variables
  # logStep ("Check ENVironment")
  # env = ENV.to_hash
  # missing = {}
  # %w(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY KNIFE_CHEF_SERVER_URL KNIFE_CLIENT_KEY KNIFE_CLIENT_NAME).each { |k|
  #   missing[k] = true unless ENV.has_key?(k)
  # }
  #
  # if missing.count() > 0
  #   #@logger.error "Missing keys: #{missing.keys.ai}"
  #   raise ChopError.new("Missing environment variables: #{missing.keys}")
  # end
end
watch_for_break() click to toggle source

# File lib/chef/knife/chop_base.rb, line 740
def watch_for_break
  Thread.new do
    s=$stdin.read
    #puts "Consumed existing input: '#{$stdin.read}'"
    loop do
      s = gets.chomp
      if s != ""
        puts "Interrupted! You entered '#{s}'"
        @stop = true
        exit
      end
    end
  end
end