class BuildCloud

Public Class Methods

new( options ) click to toggle source
# File lib/build-cloud.rb, line 14
def initialize( options )

    @log = options[:logger] or Logger.new( STDERR )
    @mock = options[:mock] or false

    # Parse the first config file. We'll merge the remainder (if any) into
    # this one, regardless of whether they're passed on the command line
    # or in the YAML for this file itself.
    first_config_file = options[:config].shift
    @config = YAML::load( File.open( first_config_file ) )

    # include_files is going to be a list of files that we're going to
    # merge in to the first file.
    include_files = []

    # Work out the full, standardised pathnames for any further files
    # specified on the command line.  note that options[:config] only
    # contains the extra files at this point, as we shifted the first
    # one off the array earlier.
    #
    # IMPORTANT: relative paths given on the command line are considered
    # to be relative to $CWD. This decision is based on the principle of
    # least surprise, as that is how everything else works.
    cli_include_files = options[:config]
    cli_include_files.each do |inc|
        include_files << File.absolute_path( inc )
    end

    # Now look in the :include key in the YAML of the first file for
    # either a single, or an array of files to include. Work out the
    # standardised paths for each of these files, and push them onto
    # the include_files array.
    #
    # IMPORTANT: relative paths given in the :include key in the YAML
    # are considered to be relative to the config file specified, not
    # $CWD. This is to ensure consistency of application and backwards
    # compatibility. If this were relative to $CWD, a relative path
    # specified in the file could have different meanings, and would end
    # up being unpredictable.
    if include_yaml = @config.delete(:include)
        if include_yaml.is_a?(Array)
            # the :include key is an array, we need to iterate over it
            include_yaml.each do |yml|
                include_files << File.expand_path( yml, File.dirname( File.absolute_path(first_config_file) ) )
            end
        else
            # the :include key is a scalar, so just standardise that path
            include_files.push( File.expand_path( include_yaml, File.dirname( File.absolute_path(first_config_file) ) ) )
        end
    end

    include_files.each do |include_path|

        if File.exists?( include_path )
            @log.info( "Including YAML file #{include_path}" )
            included_conf = YAML::load( File.open( include_path ) )
            @config = @config.merge(included_conf) do |keys, oldval, newval|
                # we're iterating over elements that are in both the
                # config we've parsed so far, and the new file.
                (newval.is_a?(Array) ? (oldval + newval).uniq : newval)
                # oldval is from the existing config, newval is the incoming
                # value from this file. if newval is an array, merge it in with
                # what we already have, and make it unique. if newval is a
                # string, the new value takes precedence over what we have
                # already.
                #
                # edge cases:
                # 1. if we have a key :foo which is a scalar, and then a
                # :foo in a subsequent file which is an array (or v.v.)
                # then this will blow up. I think this is acceptable.
                # 2. if we have, eg. an instance, defined twice in
                # separate files, then the behaviour of uniq is to use
                # the entire hash as a test for uniqueness. Therefore
                # if the definition of those instances varies slightly,
                # the attempt to create those instances will likely fail.
            end

        end

    end

    @log.debug( @config.inspect )

    new_config = recursive_interpolate_config(@config)
    @config = new_config

    @log.debug( @config.inspect )

    connect_fog

    BuildCloud::dispatch.each_pair do |component, klass|

        if @config.has_key?(component)
            klass.load( @config[component], @fog_interfaces, @log )
        end

    end

end

Private Class Methods

create_order() click to toggle source
# File lib/build-cloud.rb, line 177
def self.create_order
    [
        :dhcp_options_sets,
        :vpcs,
        :internet_gateways,
        :iam_managed_policies,
        :iam_roles,
        :iam_users,
        :iam_groups,
        :subnets,
        :db_subnet_groups,
        :cache_subnet_groups,
        :route_tables,
        :zones,
        :security_groups,
        :network_interfaces,
        :routes,
        :db_parameter_groups,
        :rds_servers,
        :cache_parameter_groups,
        :cache_clusters,
        :launch_configurations,
        :load_balancers,
        :as_groups,
        :r53_record_sets,
        :s3_buckets,
        :sqs_queues,
        :ebs_volumes,
        :instances,
    ]
end
dispatch() click to toggle source
# File lib/build-cloud.rb, line 143
def self.dispatch

    {
        :vpcs                   => BuildCloud::VPC,
        :internet_gateways      => BuildCloud::InternetGateway,
        :subnets                => BuildCloud::Subnet,
        :route_tables           => BuildCloud::RouteTable,
        :zones                  => BuildCloud::Zone,
        :security_groups        => BuildCloud::SecurityGroup,
        :network_interfaces     => BuildCloud::NetworkInterface,
        :routes                 => BuildCloud::Route,
        :launch_configurations  => BuildCloud::LaunchConfiguration,
        :load_balancers         => BuildCloud::LoadBalancer,
        :as_groups              => BuildCloud::ASGroup,
        :r53_record_sets        => BuildCloud::R53RecordSet,
        :rds_servers            => BuildCloud::RDSServer,
        :db_subnet_groups       => BuildCloud::DbSubnetGroup,
        :db_parameter_groups    => BuildCloud::DbParameterGroup,
        :cache_subnet_groups    => BuildCloud::CacheSubnetGroup,
        :cache_clusters         => BuildCloud::CacheCluster,
        :cache_parameter_groups => BuildCloud::CacheParameterGroup,
        :iam_roles              => BuildCloud::IAMRole,
        :iam_managed_policies   => BuildCloud::IAMManagedPolicy,
        :iam_groups             => BuildCloud::IAMGroup,
        :iam_users              => BuildCloud::IAMUser,
        :s3_buckets             => BuildCloud::S3Bucket,
        :instances              => BuildCloud::Instance,
        :ebs_volumes            => BuildCloud::EBSVolume,
        :dhcp_options_sets      => BuildCloud::DHCPOptionsSet,
        :sqs_queues             => BuildCloud::SQSQueue,
    }

end

Public Instance Methods

all() click to toggle source
# File lib/build-cloud.rb, line 128
def all

    objects = []

    BuildCloud::create_order.each do |component|
        next unless BuildCloud::dispatch.has_key?( component )
        objects.concat BuildCloud::dispatch[component].objects()
    end

    objects

end
find( component, options ) click to toggle source
# File lib/build-cloud.rb, line 118
def find( component, options )

    if BuildCloud::dispatch.has_key?( component )
        BuildCloud::dispatch[component].search(options)
    else
        []
    end

end
pry() click to toggle source
# File lib/build-cloud.rb, line 114
def pry
    binding.pry
end

Private Instance Methods

connect_fog() click to toggle source
# File lib/build-cloud.rb, line 253
def connect_fog

    @mock and Fog.mock!

    fog_options_regionless = {
        :aws_access_key_id     => @config[:aws_access_key_id] ||= ENV['AWS_ACCESS_KEY_ID'],
        :aws_secret_access_key => @config[:aws_secret_access_key] ||= ENV['AWS_SECRET_ACCESS_KEY'],
        :aws_session_token     => @config[:aws_session_token] ||= ENV['AWS_SESSION_TOKEN'],
    }

    fog_options = fog_options_regionless.merge( { :region => @config[:aws_region] } )

    @fog_interfaces = {

        :compute     => Fog::Compute::AWS.new( fog_options ),
        :s3          => Fog::Storage::AWS.new( fog_options.merge(:path_style => true)),
        :as          => Fog::AWS::AutoScaling.new( fog_options ),
        :elb         => Fog::AWS::ELB.new( fog_options ),
        :iam         => Fog::AWS::IAM.new( fog_options_regionless ),
        :rds         => Fog::AWS::RDS.new( fog_options ),
        :elasticache => Fog::AWS::Elasticache.new( fog_options ),
        :r53         => Fog::DNS::AWS.new( fog_options_regionless ),
        :sqs         => Fog::AWS::SQS.new( fog_options )
    }

end
recursive_interpolate_config(h) click to toggle source
# File lib/build-cloud.rb, line 213
def recursive_interpolate_config(h)

    # Work through the given config replacing all strings matching
    # %{..x..} by looking up ..x.. in the existing @config hash and
    # substituting the template with the value
    #
    # If ..x.. is of the form variable||default, if the given key is
    # not defined anywhere, the default will be used.

    case h
    when Hash
        Hash[
        h.map do |k, v|
            [ k, recursive_interpolate_config(v) ]
        end
        ]
    when Enumerable
        h.map { |v| recursive_interpolate_config(v) }
    when String

        while /%\{(?<var>[^\|\}]*)(?:\|{2}(?<default>[^\}]*))?\}/ =~ h

            # $& is the whole matched expression above, $MATCH when using English
            exp = $&

            val = @config.fetch(var.to_sym, default).to_s
            raise "Attempt to interpolate with non-existant key '#{var}' with no default value set" if val.nil?

            h.gsub!(exp, val)

        end

        h

    else
        h
    end

end