modules/mu/clouds/aws/server.rb in cloud-mu-2.1.0beta vs modules/mu/clouds/aws/server.rb in cloud-mu-3.0.0beta
- old
+ new
@@ -73,35 +73,30 @@
# @return [Hash]
def self.ephemeral_mappings
@ephemeral_mappings
end
- attr_reader :mu_name
- attr_reader :config
- attr_reader :deploy
- attr_reader :cloud_id
- attr_reader :cloud_desc
- attr_reader :groomer
- attr_accessor :mu_windows_name
-
- # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
- # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::servers}
- def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
- @deploy = mommacat
- @config = MU::Config.manxify(kitten_cfg)
- @cloud_id = cloud_id
-
- if @deploy
- @userdata = MU::Cloud.fetchUserdata(
+ # 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
+ def initialize(**args)
+ super
+ @userdata = if @config['userdata_script']
+ @config['userdata_script']
+ elsif @deploy and !@config['scrub_mu_isms']
+ MU::Cloud.fetchUserdata(
platform: @config["platform"],
- cloud: "aws",
+ cloud: "AWS",
+ credentials: @config['credentials'],
template_variables: {
"deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
"deploySSHKey" => @deploy.ssh_public_key,
"muID" => MU.deploy_id,
"muUser" => MU.mu_user,
"publicIP" => MU.mu_public_ip,
+ "mommaCatPort" => MU.mommaCatPort,
+ "adminBucketName" => MU::Cloud::AWS.adminBucketName(@credentials),
+ "chefVersion" => MU.chefVersion,
"skipApplyUpdates" => @config['skipinitialupdates'],
"windowsAdminName" => @config['windows_admin_username'],
"resourceName" => @config["name"],
"resourceType" => "server",
"platform" => @config["platform"]
@@ -111,26 +106,23 @@
end
@disk_devices = MU::Cloud::AWS::Server.disk_devices
@ephemeral_mappings = MU::Cloud::AWS::Server.ephemeral_mappings
- if !mu_name.nil?
- @mu_name = mu_name
+ if !@mu_name.nil?
@config['mu_name'] = @mu_name
- # describe
@mu_windows_name = @deploydata['mu_windows_name'] if @mu_windows_name.nil? and @deploydata
else
if kitten_cfg.has_key?("basis")
@mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true)
else
@mu_name = @deploy.getResourceName(@config['name'])
end
@config['mu_name'] = @mu_name
- @config['instance_secret'] = Password.random(50)
end
- @groomer = MU::Groomer.new(self)
+ @config['instance_secret'] ||= Password.random(50)
end
@@userdata_semaphore = Mutex.new
@@ -244,11 +236,11 @@
else
MU.log "Node creation complete for #{@config['name']}"
end
MU::MommaCat.unlock(instance.instance_id+"-create")
else
- MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
+ MU::Cloud::AWS.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
MU::MommaCat.createTag(instance.instance_id, "Name", @mu_name, region: @config['region'], credentials: @config['credentials'])
end
done = true
rescue Exception => e
if !instance.nil? and !done
@@ -286,12 +278,13 @@
arn = nil
if @config['generate_iam_role']
role = @deploy.findLitterMate(name: @config['name'], type: "roles")
s3_objs = ["#{@deploy.deploy_id}-secret", "#{role.mu_name}.pfx", "#{role.mu_name}.crt", "#{role.mu_name}.key", "#{role.mu_name}-winrm.crt", "#{role.mu_name}-winrm.key"].map { |file|
- 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU.adminBucketName+'/'+file
+ 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(@credentials)+'/'+file
}
+ MU.log "Adding S3 read permissions to #{@mu_name}'s IAM profile", MU::NOTICE, details: s3_objs
role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
@config['iam_role'] = role.mu_name
arn = role.cloudobj.createInstanceProfile
# @cfm_role_name, @cfm_prof_name
@@ -380,19 +373,31 @@
instance_descriptor[:block_device_mappings] = configured_storage
instance_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
instance_descriptor[:monitoring] = {enabled: @config['monitoring']}
+ if @tags and @tags.size > 0
+ instance_descriptor[:tag_specifications] = [{
+ :resource_type => "instance",
+ :tags => @tags.keys.map { |k|
+ { :key => k, :value => @tags[k] }
+ }
+ }]
+ end
+
MU.log "Creating EC2 instance #{node}"
MU.log "Instance details for #{node}: #{instance_descriptor}", MU::DEBUG
# if instance_descriptor[:block_device_mappings].empty?
# instance_descriptor.delete(:block_device_mappings)
# end
retries = 0
begin
response = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).run_instances(instance_descriptor)
+ rescue Aws::EC2::Errors::InvalidRequest => e
+ MU.log e.message, MU::ERR, details: instance_descriptor
+ raise e
rescue Aws::EC2::Errors::InvalidGroupNotFound, Aws::EC2::Errors::InvalidSubnetIDNotFound, Aws::EC2::Errors::InvalidParameterValue => e
if retries < 10
if retries > 7
MU.log "Seeing #{e.inspect} while trying to launch #{node}, retrying a few more times...", MU::WARN, details: instance_descriptor
end
@@ -519,11 +524,11 @@
raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !instance
@cloud_id = instance.instance_id
return false if !MU::MommaCat.lock(instance.instance_id+"-orchestrate", true)
return false if !MU::MommaCat.lock(instance.instance_id+"-groom", true)
- MU::MommaCat.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
+ MU::Cloud::AWS.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
MU::MommaCat.createTag(instance.instance_id, "Name", node, region: @config['region'], credentials: @config['credentials'])
if @config['optional_tags']
MU::MommaCat.listOptionalTags.each { |key, value|
MU::MommaCat.createTag(instance.instance_id, key, value, region: @config['region'], credentials: @config['credentials'])
@@ -752,15 +757,15 @@
# If we've asked for additional subnets (and this @config is not a
# member of a Server Pool, which has different semantics), create
# extra interfaces to accomodate.
if !@config['vpc']['subnets'].nil? and @config['basis'].nil?
device_index = 1
- @vpc.subnets { |subnet|
- subnet_id = subnet.cloud_id
+ @vpc.subnets { |s|
+ subnet_id = s.cloud_id
MU.log "Adding network interface on subnet #{subnet_id} for #{node}"
iface = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_network_interface(subnet_id: subnet_id).network_interface
- MU::MommaCat.createStandardTags(iface.network_interface_id, region: @config['region'], credentials: @config['credentials'])
+ MU::Cloud::AWS.createStandardTags(iface.network_interface_id, region: @config['region'], credentials: @config['credentials'])
MU::MommaCat.createTag(iface.network_interface_id, "Name", node+"-ETH"+device_index.to_s, region: @config['region'], credentials: @config['credentials'])
if @config['optional_tags']
MU::MommaCat.listOptionalTags.each { |key, value|
MU::MommaCat.createTag(iface.network_interface_id, key, value, region: @config['region'], credentials: @config['credentials'])
@@ -966,13 +971,20 @@
# @param region [String]: The cloud provider region
# @param tag_key [String]: A tag key to search.
# @param tag_value [String]: The value of the tag specified by tag_key to match when searching by tag.
# @param flags [Hash]: Optional flags
# @return [Array<Hash<String,OpenStruct>>]: The cloud provider's complete descriptions of matching instances
- def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, credentials: nil, flags: {})
-# XXX put that 'ip' value into opts
- ip ||= flags['ip']
+# def self.find(cloud_id: nil, region: MU.curRegion, tag_key: "Name", tag_value: nil, credentials: nil, flags: {})
+ def self.find(**args)
+ ip ||= args[:flags]['ip'] if args[:flags] and args[:flags]['ip']
+
+ cloud_id = args[:cloud_id]
+ region = args[:region]
+ credentials = args[:credentials]
+ tag_key = args[:tag_key]
+ tag_value = args[:tag_value]
+
instance = nil
if !region.nil?
regions = [region]
else
regions = MU::Cloud::AWS.listRegions
@@ -982,35 +994,35 @@
search_semaphore = Mutex.new
search_threads = []
# If we got an instance id, go get it
if !cloud_id.nil? and !cloud_id.empty?
- regions.each { |region|
+ regions.each { |r|
search_threads << Thread.new {
- MU.log "Hunting for instance with cloud id '#{cloud_id}' in #{region}", MU::DEBUG
+ MU.log "Hunting for instance with cloud id '#{cloud_id}' in #{r}", MU::DEBUG
retries = 0
begin
- MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_instances(
+ MU::Cloud::AWS.ec2(region: r, credentials: credentials).describe_instances(
instance_ids: [cloud_id],
filters: [
{name: "instance-state-name", values: ["running", "pending"]}
]
).reservations.each { |resp|
if !resp.nil? and !resp.instances.nil?
- resp.instances.each { |instance|
+ resp.instances.each { |i|
search_semaphore.synchronize {
- found_instances[instance.instance_id] = instance
+ found_instances[i.instance_id] = i
}
}
end
}
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
if retries < 5
retries = retries + 1
sleep 5
else
- raise MuError, "#{e.inspect} in region #{region}"
+ raise MuError, "#{e.inspect} in region #{r}"
end
end
}
}
done_threads = []
@@ -1052,12 +1064,12 @@
{name: "tag:#{tag_key}", values: [tag_value]},
{name: "instance-state-name", values: ["running", "pending"]}
]
).reservations.each { |resp|
if !resp.nil? and resp.instances.size > 0
- resp.instances.each { |instance|
- found_instances[instance.instance_id] = instance
+ resp.instances.each { |i|
+ found_instances[i.instance_id] = i
}
end
}
end
@@ -1227,26 +1239,26 @@
purgecmd = "#{sudo} rm -rf /var/lib/cloud/instances/i-* /root/.ssh/authorized_keys /etc/ssh/ssh_host_*key* /etc/chef /etc/opscode/* /.mu-installer-ran-updates /var/chef /opt/mu_installed_chef /opt/chef ; #{sudo} sed -i 's/^HOSTNAME=.*//' /etc/sysconfig/network"
end
end
session.exec!(purgecmd)
session.close
- ami_id = MU::Cloud::AWS::Server.createImage(
+ ami_ids = MU::Cloud::AWS::Server.createImage(
name: @mu_name,
instance_id: @cloud_id,
storage: @config['storage'],
exclude_storage: img_cfg['image_exclude_storage'],
copy_to_regions: img_cfg['copy_to_regions'],
make_public: img_cfg['public'],
region: @config['region'],
tags: @config['tags'],
credentials: @config['credentials']
)
- @deploy.notify("images", @config['name'], {"image_id" => ami_id})
+ @deploy.notify("images", @config['name'], ami_ids)
@config['image_created'] = true
if img_cfg['image_then_destroy']
- MU::Cloud::AWS::Server.waitForAMI(ami_id, region: @config['region'], credentials: @config['credentials'])
- MU.log "AMI #{ami_id} ready, removing source node #{node}"
+ MU::Cloud::AWS::Server.waitForAMI(ami_ids[@config['region']], region: @config['region'], credentials: @config['credentials'])
+ MU.log "AMI #{ami_ids[@config['region']]} ready, removing source node #{node}"
MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name, credentials: @config['credentials'])
destroy
end
end
@@ -1257,10 +1269,12 @@
# @return [String]
def arn
"arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":ec2:"+@config['region']+":"+MU::Cloud::AWS.credToAcct(@config['credentials'])+":instance/"+@cloud_id
end
+ # Return the cloud provider's description for this instance
+ # @return [Openstruct]
def cloud_desc
max_retries = 5
retries = 0
if !@cloud_id.nil?
begin
@@ -1329,14 +1343,15 @@
# @param copy_to_regions [Array<String>]: Copy the resulting AMI into the listed regions.
# @param tags [Array<String>]: Extra/override tags to apply to the image.
# @return [String]: The cloud provider identifier of the new machine image.
def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, make_public: false, region: MU.curRegion, copy_to_regions: [], tags: [], credentials: nil)
ami_descriptor = {
- :instance_id => instance_id,
- :name => name,
- :description => "Image automatically generated by Mu from #{name}"
+ :instance_id => instance_id,
+ :name => name,
+ :description => "Image automatically generated by Mu from #{name}"
}
+ ami_ids = {}
storage_list = Array.new
if exclude_storage
instance = MU::Cloud::Server.find(cloud_id: instance_id, region: region)
instance.block_device_mappings.each { |vol|
@@ -1365,12 +1380,15 @@
resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).create_image(ami_descriptor)
rescue Aws::EC2::Errors::InvalidAMINameDuplicate => e
MU.log "AMI #{name} already exists, skipping", MU::WARN
return nil
end
+
ami = resp.image_id
- MU::MommaCat.createStandardTags(ami, region: region, credentials: credentials)
+
+ ami_ids[region] = ami
+ MU::Cloud::AWS.createStandardTags(ami, region: region, credentials: credentials)
MU::MommaCat.createTag(ami, "Name", name, region: region, credentials: credentials)
MU.log "AMI of #{name} in region #{region}: #{ami}"
if make_public
MU::Cloud::AWS::Server.waitForAMI(ami, region: region, credentials: credentials)
MU::Cloud::AWS.ec2(region: region, credentials: credentials).modify_image_attribute(
@@ -1392,12 +1410,13 @@
source_image_id: ami,
name: name,
description: "Image automatically generated by Mu from #{name}"
)
MU.log "Initiated copy of #{ami} from #{region} to #{r}: #{copy.image_id}"
+ ami_ids[r] = copy.image_id
- MU::MommaCat.createStandardTags(copy.image_id, region: r, credentials: credentials)
+ MU::Cloud::AWS.createStandardTags(copy.image_id, region: r, credentials: credentials)
MU::MommaCat.createTag(copy.image_id, "Name", name, region: r, credentials: credentials)
if !tags.nil?
tags.each { |tag|
MU::MommaCat.createTag(instance.instance_id, tag['key'], tag['value'], region: r, credentials: credentials)
}
@@ -1417,11 +1436,11 @@
copythreads.each { |t|
t.join
}
- return resp.image_id
+ return ami_ids
end
# Given a cloud platform identifier for a machine image, wait until it's
# flagged as ready.
# @param image_id [String]: The machine image to wait for.
@@ -1612,25 +1631,42 @@
# Add a volume to this instance
# @param dev [String]: Device name to use when attaching to instance
# @param size [String]: Size (in gb) of the new volume
# @param type [String]: Cloud storage type of the volume, if applicable
- def addVolume(dev, size, type: "gp2")
+ # @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
+ def addVolume(dev, size, type: "gp2", delete_on_termination: false)
if @cloud_id.nil? or @cloud_id.empty?
MU.log "#{self} didn't have a cloud id, couldn't determine 'active?' status", MU::ERR
return true
end
az = nil
MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_instances(
instance_ids: [@cloud_id]
).reservations.each { |resp|
if !resp.nil? and !resp.instances.nil?
resp.instances.each { |instance|
- az = instance.placement.availability_zone
- instance.block_device_mappings.each { |vol|
- if vol.device_name == dev
+ az = instance.placement.availability_zone
+ d_o_t_changed = true
+ mappings = MU.structToHash(instance.block_device_mappings)
+ mappings.each { |vol|
+ if vol[:ebs]
+ vol[:ebs].delete(:attach_time)
+ vol[:ebs].delete(:status)
+ end
+ }
+ mappings.each { |vol|
+ if vol[:device_name] == dev
MU.log "A volume #{dev} already attached to #{self}, skipping", MU::NOTICE
+ if vol[:ebs][:delete_on_termination] != delete_on_termination
+ vol[:ebs][:delete_on_termination] = delete_on_termination
+ MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
+ instance_id: @cloud_id,
+ block_device_mappings: mappings
+ )
+ end
return
end
}
}
end
@@ -1667,10 +1703,36 @@
attachment = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(volume_ids: [attachment.volume_id]).volumes.first.attachments.first
if !["attaching", "attached"].include?(attachment.state)
raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
end
end while attachment.state != "attached"
+
+ # Set delete_on_termination, which for some reason is an instance
+ # attribute and not on the attachment
+ mappings = MU.structToHash(cloud_desc.block_device_mappings)
+ changed = false
+
+ mappings.each { |mapping|
+ if mapping[:ebs]
+ mapping[:ebs].delete(:attach_time)
+ mapping[:ebs].delete(:status)
+ end
+ if mapping[:device_name] == dev and
+ mapping[:ebs][:delete_on_termination] != delete_on_termination
+ changed = true
+ mapping[:ebs][:delete_on_termination] = delete_on_termination
+ end
+ }
+
+ if changed
+ MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
+ instance_id: @cloud_id,
+ block_device_mappings: mappings
+ )
+ end
+
end
# Determine whether the node in question exists at the Cloud provider
# layer.
# @return [Boolean]
@@ -1938,31 +2000,21 @@
}
end
end
if !onlycloud and !mu_name.nil?
# DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
- if !zone_rrsets.empty?
+ if !zone_rrsets.nil? and !zone_rrsets.empty?
zone_rrsets.each { |rrset|
if rrset.name.match(/^#{mu_name.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
rrset.resource_records.each { |record|
MU::Cloud::DNSZone.genericMuDNSEntry(name: mu_name, target: record.value, cloudclass: MU::Cloud::Server, delete: true)
cleaned_dns = true
}
end
}
end
- # Expunge traces left in Chef, Puppet or what have you
- MU::Groomer.supportedGroomers.each { |groomer|
- groomclass = MU::Groomer.loadGroomer(groomer)
- if !server_obj.nil? and !server_obj.config.nil? and !server_obj.config['vault_access'].nil?
- groomclass.cleanup(mu_name, server_obj.config['vault_access'], noop)
- else
- groomclass.cleanup(mu_name, [], noop)
- end
- }
-
if !noop
if !server_obj.nil? and !server_obj.config.nil?
MU.mommacat.notify(MU::Cloud::Server.cfg_plural, server_obj.config['name'], {}, mu_name: server_obj.mu_name, remove: true) if MU.mommacat
end
end
@@ -1989,11 +2041,11 @@
known_hosts_files = [Etc.getpwuid(Process.uid).dir+"/.ssh/known_hosts"]
if Etc.getpwuid(Process.uid).name == "root"
known_hosts_files << Etc.getpwnam("nagios").dir+"/.ssh/known_hosts"
end
known_hosts_files.each { |known_hosts|
- next if !File.exists?(known_hosts)
+ next if !File.exist?(known_hosts)
MU.log "Cleaning up #{ips} from #{known_hosts}"
if !noop
File.open(known_hosts, File::CREAT|File::RDWR, 0644) { |f|
f.flock(File::LOCK_EX)
newlines = Array.new
@@ -2064,24 +2116,36 @@
end
MU.log "#{instance.instance_id} (#{name}) terminated" if !noop
end
end
+ # Return a BoK-style config hash describing a NAT instance. We use this
+ # to approximate NAT gateway functionality with a plain instance.
+ # @return [Hash]
+ def self.genericNAT
+ return {
+ "cloud" => "AWS",
+ "bastion" => true,
+ "size" => "t2.small",
+ "run_list" => [ "mu-utility::nat" ],
+ "platform" => "centos7",
+ "ssh_user" => "centos",
+ "associate_public_ip" => true,
+ "static_ip" => { "assign_ip" => true },
+ }
+ end
+
# 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
def self.schema(config)
toplevel_required = []
schema = {
"ami_id" => {
"type" => "string",
- "description" => "The Amazon EC2 AMI on which to base this instance. Will use the default appropriate for the platform, if not specified."
+ "description" => "Alias for +image_id+"
},
- "image_id" => {
- "type" => "string",
- "description" => "Synonymous with ami_id"
- },
"generate_iam_role" => {
"type" => "boolean",
"default" => true,
"description" => "Generate a unique IAM profile for this Server or ServerPool.",
},
@@ -2131,36 +2195,47 @@
# If someone accidentally specified an equivalent size from some other cloud provider, return something that makes sense. If nothing makes sense, return nil.
# @param size [String]: Instance type to check
# @param region [String]: Region to check against
# @return [String,nil]
def self.validateInstanceType(size, region)
- begin
- types = (MU::Cloud::AWS.listInstanceTypes(region))[region]
- rescue Aws::Pricing::Errors::UnrecognizedClientException
+ size = size.dup.to_s
+ types = begin
+ (MU::Cloud::AWS.listInstanceTypes(region))[region]
+ rescue Aws::Pricing::Errors::Unrecognitypes.has_key?(size)
MU.log "Saw authentication error communicating with Pricing API, going to assume our instance type is correct", MU::WARN
return size
end
+
+ return size if types.has_key?(size)
+
if size.nil? or !types.has_key?(size)
# See if it's a type we can approximate from one of the other clouds
- gtypes = (MU::Cloud::Google.listInstanceTypes)[MU::Cloud::Google.myRegion]
foundmatch = false
- if gtypes and gtypes.size > 0 and gtypes.has_key?(size)
- vcpu = gtypes[size]["vcpu"]
- mem = gtypes[size]["memory"]
- ecu = gtypes[size]["ecu"]
- types.keys.sort.reverse.each { |type|
- features = types[type]
- next if ecu == "Variable" and ecu != features["ecu"]
- next if features["vcpu"] != vcpu
- if (features["memory"] - mem.to_f).abs < 0.10*mem
- foundmatch = true
- MU.log "You specified a Google Compute instance type '#{size}.' Approximating with Amazon EC2 type '#{type}.'", MU::WARN
- size = type
- break
- end
- }
- end
+
+ MU::Cloud.availableClouds.each { |cloud|
+ next if cloud == "AWS"
+ cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
+ foreign_types = (cloudbase.listInstanceTypes)[cloudbase.myRegion]
+ if foreign_types and foreign_types.size > 0 and foreign_types.has_key?(size)
+ vcpu = foreign_types[size]["vcpu"]
+ mem = foreign_types[size]["memory"]
+ ecu = foreign_types[size]["ecu"]
+ types.keys.sort.reverse.each { |type|
+ features = types[type]
+ next if ecu == "Variable" and ecu != features["ecu"]
+ next if features["vcpu"] != vcpu
+ if (features["memory"] - mem.to_f).abs < 0.10*mem
+ foundmatch = true
+ MU.log "You specified #{cloud} instance type '#{size}.' Approximating with Amazon EC2 type '#{type}.'", MU::WARN
+ size = type
+ break
+ end
+ }
+ end
+ break if foundmatch
+ }
+
if !foundmatch
MU.log "Invalid size '#{size}' for AWS EC2 instance in #{region}. Supported types:", MU::ERR, details: types.keys.sort.join(", ")
return nil
end
end
@@ -2236,13 +2311,13 @@
end
server['ami_id'] ||= server['image_id']
if server['ami_id'].nil?
- if MU::Config.amazon_images.has_key?(server['platform']) and
- MU::Config.amazon_images[server['platform']].has_key?(server['region'])
- server['ami_id'] = configurator.getTail("server"+server['name']+"AMI", value: MU::Config.amazon_images[server['platform']][server['region']], prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
+ img_id = MU::Cloud.getStockImage("AWS", platform: server['platform'], region: server['region'])
+ if img_id
+ server['ami_id'] = configurator.getTail("server"+server['name']+"AMI", value: img_id, prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
else
MU.log "No AMI specified for #{server['name']} and no default available for platform #{server['platform']} in region #{server['region']}", MU::ERR, details: server
ok = false
end
end
@@ -2264,9 +2339,24 @@
ok = false
end
end
ok
+ end
+
+ # Return the date/time a machine image was created.
+ # @param ami_id [String]: AMI identifier of an Amazon Machine Image
+ # @param credentials [String]
+ # @return [DateTime]
+ def self.imageTimeStamp(ami_id, credentials: nil, region: nil)
+ begin
+ img = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_images(image_ids: [ami_id]).images.first
+ return DateTime.new if img.nil?
+ return DateTime.parse(img.creation_date)
+ rescue Aws::EC2::Errors::InvalidAMIIDNotFound => e
+ end
+
+ return DateTime.new
end
private
# Destroy a volume.