class MU::Cloud::AWS::Collection

An Amazon CloudFormation stack as configured in {MU::Config::BasketofKittens::collections}

Public Class Methods

cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, wait: false, credentials: nil, flags: {}) click to toggle source

Remove all CloudFormation stacks associated with the currently loaded deployment. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @param region [String]: The cloud provider region @param wait [Boolean]: Block on the removal of this stack; AWS deletion will continue in the background otherwise if false. @return [void]

# File modules/mu/providers/aws/collection.rb, line 245
        def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, wait: false, credentials: nil, flags: {})
          MU.log "AWS::Collection.cleanup: need to support flags['known']", MU::DEBUG, details: flags
          MU.log "Placeholder: AWS Collection artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster

# XXX needs to check tags instead of name- possible?
          resp = MU::Cloud::AWS.cloudformation(credentials: credentials, region: region).describe_stacks
          resp.stacks.each { |stack|
            ok = false
            stack.tags.each { |tag|
              ok = true if (tag.key == "MU-ID") and tag.value == deploy_id
            }
            if ok
              MU.log "Deleting CloudFormation stack #{stack.stack_name})"
              next if noop
              if stack.stack_status != "DELETE_IN_PROGRESS"
                MU::Cloud::AWS.cloudformation(credentials: credentials, region: region).delete_stack(stack_name: stack.stack_name)
              end
              if wait
                max_retries = 10
                retries = 0
                mystack = nil
                begin
                  mystack = nil
                  sleep 30
                  retries = retries + 1
                  desc = MU::Cloud::AWS.cloudformation(credentials: credentials, region: region).describe_stacks(stack_name: stack.stack_name)
                  if desc.size > 0
                    mystack = desc.first.stacks.first
                    if mystack.size > 0 and mystack.stack_status == "DELETE_FAILED"
                      MU.log "Couldn't delete CloudFormation stack #{stack.stack_name}", MU::ERR, details: mystack.stack_status_reason
                      return
                    end
                    MU.log "Waiting for CloudFormation stack #{stack.stack_name} to delete (#{stack.stack_status})...", MU::NOTICE
                  end
                rescue Aws::CloudFormation::Errors::ValidationError
                  # this is ok, it means deletion finally succeeded

                end while !desc.nil? and desc.size > 0 and retries < max_retries

                if retries >= max_retries and !mystack.nil? and mystack.stack_status != "DELETED"
                  MU.log "Failed to delete CloudFormation stack #{stack.stack_name}", MU::ERR
                end
              end

            end
          }
          return nil
        end
find(**args) click to toggle source

placeholder

# File modules/mu/providers/aws/collection.rb, line 301
def self.find(**args)
  found = nil
  resp = MU::Cloud::AWS.cloudformation(region: args[:region], credentials: args[:credentials]).describe_stacks(
    stack_name: args[:cloud_id]
  )
  if resp and resp.stacks
    found[args[:cloud_id]] = resp.stacks.first
  end

  found
end
isGlobal?() click to toggle source

Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]

# File modules/mu/providers/aws/collection.rb, line 235
def self.isGlobal?
  false
end
new(**args) click to toggle source

Initialize this cloud resource object. Calling super will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us. @param args [Hash]: Hash of named arguments passed via Ruby's double-splat

Calls superclass method
# File modules/mu/providers/aws/collection.rb, line 25
def initialize(**args)
  super
  @mu_name ||= @deploy.getResourceName(@config['name'], need_unique_string: true)
  MU.setVar("curRegion", @region) if !@region.nil?
end
schema(_config) click to toggle source

Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource

# File modules/mu/providers/aws/collection.rb, line 323
def self.schema(_config)
  toplevel_required = []
  schema = {}
  [toplevel_required, schema]
end
validateConfig(_stack, _configurator) click to toggle source

Cloud-specific pre-processing of {MU::Config::BasketofKittens::collections}, bare and unvalidated. @param _stack [Hash]: The resource to process and validate @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise

# File modules/mu/providers/aws/collection.rb, line 333
def self.validateConfig(_stack, _configurator)
  true
end

Public Instance Methods

arn() click to toggle source

Canonical Amazon Resource Number for this resource @return [String]

# File modules/mu/providers/aws/collection.rb, line 296
def arn
  cloud_desc.role_arn
end
create() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/aws/collection.rb, line 33
        def create
          flag="SUCCESS"
          MU.setVar("curRegion", @region) if !@region.nil?
          region = @region
          server=@config["name"]
          stack_name = getStackName(@config["name"])

          if @config["type"] !=nil && @config["type"]=="existing" then
# XXX this isn't correct, need to go through and list its resources
            return @config
          end
          @config["time"]=@deploy.timestamp

          begin

            stack_descriptor = {
                :stack_name => stack_name,
                :on_failure => @config["on_failure"],
                :timeout_in_minutes => @config["timeout"],
                :tags => [
                    {
                        :key => "Name",
                        :value => MU.appname.upcase + "-" + MU.environment.upcase + "-" + MU.timestamp.upcase + "-" + @config['name'].upcase
                    },
                    {
                        :key => "MU-ID",
                        :value => MU.deploy_id
                    }
                ]
            }

            keypairname, _ssh_private_key, _ssh_public_key = @deploy.SSHKey

            parameters = Array.new
            if !@config["parameters"].nil?
              @config["parameters"].each { |parameter|
                parameters << {
                    :parameter_key => parameter["parameter_key"],
                    :parameter_value => parameter["parameter_value"]
                }
              }
            end
            if @config["pass_deploy_key_as"] != nil
              parameters << {
                  :parameter_key => @config["pass_deploy_key_as"],
                  :parameter_value => keypairname
              }
            end
            stack_descriptor[:parameters] = parameters

            if @config["template_file"] != nil then
              # pass absolute path
              if !@config["template_file"].nil?
                if @config["template_file"].match(/^\//)
                  MU.log "Loading Cloudformation template from #{@config["template_file"]}"
                  template_body = File.read(@config["template_file"])
                else
                  path = File.expand_path(File.dirname(MU::Config.config_path)+"/"+@config["template_file"])
                  MU.log "Loading Cloudformation template from #{path}"
                  template_body = File.read(path)
                end
              else
                # json file and template path is same
                file_dir =File.dirname(ARGV[0])
                if File.exist? file_dir+"/"+@config["template_file"] then
                  template_body=File.read(file_dir+"/"+@config["template_file"]);
                end
              end
              stack_descriptor[:template_body] = template_body.to_s
            end

            if @config["template_url"] != nil then
              if @config["template_file"] == nil then
                stack_descriptor[:template_url] = @config["template_url"]
              end
            end

            MU.log "Creating CloudFormation stack '#{@config['name']}'", details: stack_descriptor
            MU::Cloud::AWS.cloudformation(region: region, credentials: @credentials).create_stack(stack_descriptor);

            sleep(10);
            stack_response = MU::Cloud::AWS.cloudformation(region: region, credentials: @credentials).describe_stacks({:stack_name => stack_name}).stacks.first
            attempts = 0
            begin
              if attempts % 5 == 0
                MU.log "Waiting for CloudFormation stack '#{@config['name']}' to be ready...", MU::NOTICE
              else
                MU.log "Waiting for CloudFormation stack '#{@config['name']}' to be ready...", MU::DEBUG
              end
              stack_response =MU::Cloud::AWS.cloudformation(region: region, credentials: @credentials).describe_stacks({:stack_name => stack_name}).stacks.first
              sleep 60
            end while stack_response.stack_status == "CREATE_IN_PROGRESS"

            if stack_response.stack_status == "CREATE_FAILED" then
              showStackError server
              flag="FAIL"
            end
          rescue Aws::EC2::Errors::ServiceError => e

            flag="FAIL"
            MU.log "#{stack_name} creation failed (#{e.inspect})", MU::ERR, details: e.backtrace

          end

          if flag == "FAIL" then
            MU::Cloud::AWS.cloudformation(region: region, credentials: @credentials).delete_stack({:stack_name => stack_name})
            exit 1
          end

          MU.log "CloudFormation stack '#{@config['name']}' complete"

          begin
            resources = MU::Cloud::AWS.cloudformation(region: region, credentials: @credentials).describe_stack_resources(:stack_name => stack_name)

            resources[:stack_resources].each { |resource|

              case resource.resource_type
                when "AWS::EC2::Instance"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  instance_name = MU.deploy_id+"-"+@config['name']+"-"+resource.logical_resource_id
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", instance_name, credentials: @credentials)

                  instance = MU::Cloud.resourceClass("AWS", "Server").notifyDeploy(
                      @config['name']+"-"+resource.logical_resource_id,
                      resource.physical_resource_id
                  )

                  MU::Master.addHostToSSHConfig(
                      instance_name,
                      instance["private_ip_address"],
                      instance["private_dns_name"],
                      # XXX this is a hack-around
                      user: "ec2-user",
                      public_dns: instance["public_ip_address"],
                      public_ip: instance["public_dns_name"],
                      key_name: instance["key_name"]
                  )

                  mu_zone, _junk = MU::Cloud::DNSZone.find(name: "mu")
                  if !mu_zone.nil?
                    MU::Cloud.resourceClass("AWS", "DNSZone").genericMuDNSEntry(instance_name, instance["private_ip_address"], MU::Cloud::Server)
                  else
                    MU::Master.addInstanceToEtcHosts(instance["public_ip_address"], instance_name)
                  end

                when "AWS::EC2::SecurityGroup"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", MU.deploy_id+"-"+@config['name']+'-'+resource.logical_resource_id, credentials: @credentials)
                  MU::Cloud.resourceClass("AWS", "FirewallRule").notifyDeploy(
                      @config['name']+"-"+resource.logical_resource_id,
                      resource.physical_resource_id
                  )
                when "AWS::EC2::Subnet"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", MU.deploy_id+"-"+@config['name']+'-'+resource.logical_resource_id, credentials: @credentials)
                  data = {
                      "collection" => @config["name"],
                      "subnet_id" => resource.physical_resource_id,
                  }
                  @deploy.notify("subnets", @config['name']+"-"+resource.logical_resource_id, data)
                when "AWS::EC2::VPC"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", MU.deploy_id+"-"+@config['name']+'-'+resource.logical_resource_id, credentials: @credentials)
                  data = {
                      "collection" => @config["name"],
                      "vpc_id" => resource.physical_resource_id,
                  }
                  @deploy.notify("vpcs", @config['name']+"-"+resource.logical_resource_id, data)
                when "AWS::EC2::InternetGateway"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", MU.deploy_id+"-"+@config['name']+'-'+resource.logical_resource_id, credentials: @credentials)
                when "AWS::EC2::RouteTable"
                  MU::Cloud::AWS.createStandardTags(resource.physical_resource_id)
                  MU::Cloud::AWS.createTag(resource.physical_resource_id, "Name", MU.deploy_id+"-"+@config['name']+'-'+resource.logical_resource_id, credentials: @credentials)

                # The rest of these aren't anything we act on
                when "AWS::EC2::Route"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::EC2::EIP"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::EC2::SecurityGroupIngress"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::EC2::SubnetRouteTableAssociation"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::EC2::VPCGatewayAttachment"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::IAM::InstanceProfile"
                  MU.log resource.resource_type, MU::DEBUG
                when "AWS::IAM::Role"
                  MU.log resource.resource_type, MU::DEBUG
                else
                  MU.log "Don't know what to do with #{resource.resource_type}, skipping it", MU::WARN
              end
            }
          rescue Aws::CloudFormation::Errors::ValidationError => e
            MU.log "Error processing created resource in CloudFormation stack #{stack_name}: #{e.inspect}", MU::ERR, details: e.backtrace
          end
        end
notify() click to toggle source

placeholder @return [Hash]

# File modules/mu/providers/aws/collection.rb, line 315
        def notify
# XXX move those individual resource type notify calls into here
          @deploy.notify("collections", @config["name"], @config)
        end

Private Instance Methods

getStackName(stack) click to toggle source

Generate a MU-friendly name for a CloudFormation stack @param stack [String]: The internal resource name of the stack @return [String]

# File modules/mu/providers/aws/collection.rb, line 342
def getStackName(stack)
  stack_name = MU.deploy_id + "-" + stack.upcase
  stack_name.gsub!(/[_\.]/, "-")
  return stack_name
end
showStackError(stack) click to toggle source

Log the Amazon-specific errors associated with a CloudFormation stack. We have to query the AWS API explicitly to get this. @param stack [String]: The internal resource name of the stack @return [void]

# File modules/mu/providers/aws/collection.rb, line 352
def showStackError(stack)
  region = stack['region']
  stack_name = getStackName(stack)
  begin
    resources = MU::Cloud::AWS.cloudformation(region: region).describe_stack_resources(:stack_name => stack_name)

    MU.log "CloudFormation stack #{stack_name} failed", MU::ERR

    resources[:stack_resources].each { |resource|
      MU.log "#{resource.resource_type} #{resource.resource_status} #{resource.resource_status_reason }", MU::ERR
    }
  rescue Aws::CloudFormation::Errors::ValidationError => e
    MU.log e.inspect, MU::ERR, details: e.backtrace
  end
end