require "sfn" require "bogo-config" module Sfn # Top level configuration class Config < Bogo::Config # Override attribute helper to detect Hash types and automatically # add type conversion for CLI provided values + description update # # @param name [String, Symbol] name of attribute # @param type [Class, Array<Class>] valid types # @param info [Hash] attribute information # @return [Hash] def self.attribute(name, type, info = Smash.new) if [type].flatten.any? { |t| t.ancestors.include?(Hash) } unless info[:coerce] info[:coerce] = lambda do |v| case v when String Smash[ v.split(/,(?=[^,]*:)/).map do |item_pair| item_pair.split(/[=:]/, 2) end ] when Hash v.to_smash else v end end info[:description] ||= "" info[:description] << " (Key:Value[,Key:Value,...])" end end super(name, type, info) end # Only values allowed designating bool type BOOLEAN = BOOLEAN_VALUES = [TrueClass, FalseClass].freeze # Boolean type with nil included TRISTATE_BOOLEAN = (BOOLEAN + [NilClass]).freeze autoload :Conf, "sfn/config/conf" autoload :Create, "sfn/config/create" autoload :Describe, "sfn/config/describe" autoload :Destroy, "sfn/config/destroy" autoload :Describe, "sfn/config/describe" autoload :Diff, "sfn/config/diff" autoload :Events, "sfn/config/events" autoload :Export, "sfn/config/export" autoload :Graph, "sfn/config/graph" autoload :Import, "sfn/config/import" autoload :Init, "sfn/config/init" autoload :Inspect, "sfn/config/inspect" autoload :Lint, "sfn/config/lint" autoload :List, "sfn/config/list" autoload :Plan, "sfn/config/plan" autoload :Print, "sfn/config/print" autoload :Promote, "sfn/config/promote" autoload :Realize, "sfn/config/realize" autoload :Trace, "sfn/config/trace" autoload :Update, "sfn/config/update" autoload :Validate, "sfn/config/validate" attribute( :config, String, :description => "Configuration file path", :short_flag => "c", ) attribute( :credentials, Smash, :description => "Provider credentials", :short_flag => "C", ) attribute( :ignore_parameters, String, :multiple => true, :description => "Parameters to ignore during modifications", :short_flag => "i", ) attribute( :interactive_parameters, [TrueClass, FalseClass], :default => true, :description => "Prompt for template parameters", :short_flag => "I", ) attribute( :poll, [TrueClass, FalseClass], :default => true, :description => "Poll stack events on modification actions", :short_flag => "p", ) attribute( :defaults, [TrueClass, FalseClass], :description => "Automatically accept default values", :short_flag => "d", ) attribute( :yes, [TrueClass, FalseClass], :description => "Automatically accept any requests for confirmation", :short_flag => "y", ) attribute( :debug, [TrueClass, FalseClass], :description => "Enable debug output", :short_flag => "u", ) attribute( :log, String, :description => "Enable logging with given level", :short_flag => "G", ) attribute( :colors, [TrueClass, FalseClass], :description => "Enable colorized output", :default => true, ) attribute :conf, Conf, :coerce => proc { |v| Conf.new(v) } attribute :create, Create, :coerce => proc { |v| Create.new(v) } attribute :update, Update, :coerce => proc { |v| Update.new(v) } attribute :destroy, Destroy, :coerce => proc { |v| Destroy.new(v) } attribute :events, Events, :coerce => proc { |v| Events.new(v) } attribute :export, Export, :coerce => proc { |v| Export.new(v) } attribute :import, Import, :coerce => proc { |v| Import.new(v) } attribute :inspect, Inspect, :coerce => proc { |v| Inpsect.new(v) } attribute :describe, Describe, :coerce => proc { |v| Describe.new(v) } attribute :list, List, :coerce => proc { |v| List.new(v) } attribute :promote, Promote, :coerce => proc { |v| Promote.new(v) } attribute :validate, Validate, :coerce => proc { |v| Validate.new(v) } # Provide all options for config class (includes global configs) # # @param klass [Class] # @return [Smash] def self.options_for(klass) shorts = ["h"] # always reserve `-h` for help _options_for(klass, shorts) end # Provide options for config class # # @param klass [Class] # @param shorts [Array<String>] # @return [Smash] def self._options_for(klass, shorts) opts = Smash[ [klass].map do |a| if a.ancestors.include?(Bogo::Config) && !a.attributes.empty? a.attributes end end.compact.reverse.inject(Smash.new) { |m, n| m.deep_merge(n) }.map do |name, info| next unless info[:description] short = info[:short_flag] if !short.to_s.empty? && shorts.include?(short) raise ArgumentError.new "Short flag already in use! (`#{short}` not available for `#{klass}`)" end unless short.to_s.empty? shorts << short info[:short] = short end info[:long] = name.tr("_", "-") info[:boolean] = [info[:type]].compact.flatten.all? { |t| BOOLEAN_VALUES.include?(t) } [name, info] end.compact ] opts end end end