class MU::Cloud::Azure::Server
A server as configured in {MU::Config::BasketofKittens::servers}.
Public Class Methods
Remove all instances associated with the currently loaded deployment. Also cleans up associated volumes, droppings in the MU
master's /etc/hosts and ~/.ssh, and in whatever Groomer
was used. @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 @return [void]
# File modules/mu/providers/azure/server.rb, line 457 def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {}) end
Create an image out of a running server. Requires either the name of a MU
resource in the current deployment, or the cloud provider id of a running instance. @param name [String]: The MU
resource name of the server to use as the basis for this image. @param instance_id [String]: The cloud provider resource identifier of the server to use as the basis for this image. @param storage [Hash]: The storage devices to include in this image. @param exclude_storage [Boolean]: Do not include the storage device profile of the running instance when creating this image. @param region [String]: The cloud provider region @param tags [Array<String>]: Extra/override tags to apply to the image. @return [String]: The cloud provider identifier of the new machine image.
# File modules/mu/providers/azure/server.rb, line 353 def self.createImage(name: nil, instance_id: nil, storage: {}, exclude_storage: false, project: nil, make_public: false, tags: [], region: nil, family: "mu", zone: MU::Cloud::Azure.listAZs.sample, credentials: nil) end
stub
# File modules/mu/providers/azure/server.rb, line 642 def self.diskConfig(config, create = true, disk_as_url = true, credentials: nil) end
Retrieve the cloud descriptor for an Azure
machine image @param image_id [String]: A full Azure
resource id, or a shorthand string like OpenLogic/CentOS/7.6/7.6.20190808
. The third and fourth fields (major version numbers and release numbers, by convention) can be partial, and the release number can be omitted entirely. We default to the most recent matching release when applicable. @param credentials [String] @return [Azure::Compute::Mgmt::V2019_03_01::Models::VirtualMachineImage]
# File modules/mu/providers/azure/server.rb, line 649 def self.fetchImage(image_id, credentials: nil, region: MU::Cloud::Azure.myRegion) publisher = offer = sku = version = nil if image_id.match(/\/Subscriptions\/[^\/]+\/Providers\/Microsoft.Compute\/Locations\/([^\/]+)\/Publishers\/([^\/]+)\/ArtifactTypes\/VMImage\/Offers\/([^\/]+)\/Skus\/([^\/]+)\/Versions\/([^\/]+)$/) region = Regexp.last_match[1] publisher = Regexp.last_match[2] offer = Regexp.last_match[3] sku = Regexp.last_match[4] version = Regexp.last_match[5] return MU::Cloud::Azure.compute(credentials: credentials).virtual_machine_images.get(region, publisher, offer, sku, version) else publisher, offer, sku, version = image_id.split(/\//) end if !publisher or !offer or !sku raise MuError, "Azure image_id #{image_id} was invalid" end skus = MU::Cloud::Azure.compute(credentials: credentials).virtual_machine_images.list_skus(region, publisher, offer).map { |s| s.name } if !skus.include?(sku) skus.reject! { |s| !s.match(/^#{Regexp.quote(sku)}/) } skus.sort! { |a, b| MU.version_sort(a, b) }.reverse! sku = skus.first end version = nil begin versions = MU::Cloud::Azure.compute(credentials: credentials).virtual_machine_images.list(region, publisher, offer, sku).map { |v| v.name } if versions.nil? or versions.empty? skus.delete(sku) sku = skus.first end end while skus.size > 0 and (versions.nil? or versions.empty?) if versions.nil? or versions.empty? MU.log "Azure API returned empty machine image version list for publisher #{publisher} offer #{offer} sku #{sku}", MU::ERR, details: skus return nil end if version.nil? version = versions.sort { |a, b| MU.version_sort(a, b) }.reverse.first elsif !versions.include?(version) versions.sort { |a, b| MU.version_sort(a, b) }.reverse.each { |v| if v.match(/^#{Regexp.quote(version)}/) version = v break end } end MU::Cloud::Azure.compute(credentials: credentials).virtual_machine_images.get(region, publisher, offer, sku, version) end
@return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching instances
# File modules/mu/providers/azure/server.rb, line 221 def self.find(**args) found = {} MU.log "Azure::Server.find called", MU::NOTICE, details: args # told one, we may have to search all the ones we can see. resource_groups = if args[:resource_group] [args[:resource_group]] elsif args[:cloud_id] and args[:cloud_id].is_a?(MU::Cloud::Azure::Id) [args[:cloud_id].resource_group] else MU::Cloud::Azure.resources(credentials: args[:credentials]).resource_groups.list.map { |rg| rg.name } end if args[:cloud_id] id_str = args[:cloud_id].is_a?(MU::Cloud::Azure::Id) ? args[:cloud_id].name : args[:cloud_id] resource_groups.each { |rg| begin resp = MU::Cloud::Azure.compute(credentials: args[:credentials]).virtual_machines.get(rg, id_str) next if resp.nil? found[Id.new(resp.id)] = resp rescue MU::Cloud::Azure::APIError # this is fine, we're doing a blind search after all end } else if args[:resource_group] MU::Cloud::Azure.compute(credentials: args[:credentials]).virtual_machines.list(args[:resource_group]).each { |vm| found[Id.new(vm.id)] = vm } else MU::Cloud::Azure.compute(credentials: args[:credentials]).virtual_machines.list_all.each { |vm| found[Id.new(vm.id)] = vm } end end found 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]
# File modules/mu/providers/azure/server.rb, line 108 def self.genericNAT return { "cloud" => "Azure", "src_dst_check" => false, "bastion" => true, "size" => "Standard_B2s", "run_list" => [ "mu-nat" ], "groomer" => "Ansible", "platform" => "centos7", "associate_public_ip" => true, "static_ip" => { "assign_ip" => true }, } end
Return the date/time a machine image was created. @param image_id [String]: URL to a Azure
disk image @param credentials [String] @return [DateTime]
# File modules/mu/providers/azure/server.rb, line 76 def self.imageTimeStamp(image_id, credentials: nil) return DateTime.new(0) # Azure doesn't seem to keep this anywhere, boo # begin # img = fetchImage(image_id, credentials: credentials) # return DateTime.new if img.nil? # return DateTime.parse(img.creation_timestamp) # rescue ::Azure::Apis::ClientError => e # end # # return DateTime.new end
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/azure/server.rb, line 442 def self.isGlobal? false end
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
# File modules/mu/providers/azure/server.rb, line 32 def initialize(**args) super @userdata = if @config['userdata_script'] @config['userdata_script'] elsif @deploy and !@scrub_mu_isms MU::Cloud.fetchUserdata( platform: @config["platform"], cloud: "Azure", 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, "adminBucketName" => MU::Cloud::Azure.adminBucketName(@credentials), "chefVersion" => MU.chefVersion, "skipApplyUpdates" => @config['skipinitialupdates'], "windowsAdminName" => @config['windows_admin_username'], "mommaCatPort" => MU.mommaCatPort, "resourceName" => @config["name"], "resourceType" => "server", "platform" => @config["platform"] }, custom_append: @config['userdata_script'] ) end if !@mu_name if kitten_cfg.has_key?("basis") @mu_name = @deploy.getResourceName(@config['name'], need_unique_string: true) else @mu_name = @deploy.getResourceName(@config['name']) end end @config['instance_secret'] ||= Password.random(50) end
Denote whether this resource implementation is experiment, ready for testing, or ready for production use.
# File modules/mu/providers/azure/server.rb, line 448 def self.quality MU::Cloud::BETA 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
# File modules/mu/providers/azure/server.rb, line 463 def self.schema(config) toplevel_required = [] hosts_schema = MU::Config::CIDR_PRIMITIVE hosts_schema["pattern"] = "^(\\d+\\.\\d+\\.\\d+\\.\\d+\/[0-9]{1,2}|\\*)$" schema = { "roles" => MU::Cloud.resourceClass("Azure", "User").schema(config)[1]["roles"], "ingress_rules" => { "items" => { "properties" => { "hosts" => { "type" => "array", "items" => hosts_schema } } } }, "windows_admin_username" => { "type" => "string", "default" => "muadmin", }, "ssh_user" => { "default_if" => [ { "key_is" => "platform", "value_is" => "windows", "set" => "muadmin" }, { "key_is" => "platform", "value_is" => "win2k12", "set" => "muadmin" }, { "key_is" => "platform", "value_is" => "win2k12r2", "set" => "muadmin" }, { "key_is" => "platform", "value_is" => "win2k16", "set" => "muadmin" } ] } } [toplevel_required, schema] end
Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated. @param server [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/azure/server.rb, line 561 def self.validateConfig(server, configurator) ok = true server['region'] ||= MU::Cloud::Azure.myRegion(server['credentials']) server['ssh_user'] ||= "muadmin" if server['windows_admin_username'] == "Administrator" MU.log "Azure does not permit admin user to be 'Administrator'", MU::ERR ok = false end server['size'] = validateInstanceType(server["size"], server["region"]) ok = false if server['size'].nil? if server['image_id'].nil? img_id = MU::Cloud.getStockImage("Azure", platform: server['platform']) if img_id server['image_id'] = configurator.getTail("server"+server['name']+"Image", value: img_id, prettyname: "server"+server['name']+"Image") else MU.log "No image specified for #{server['name']} and no default available for platform #{server['platform']}", MU::ERR, details: server ok = false end end image_desc = MU::Cloud::Azure::Server.fetchImage(server['image_id'].to_s, credentials: server['credentials'], region: server['region']) if !image_desc MU.log "Failed to locate an Azure VM image for #{server['name']} from #{server['image_id']} in #{server['region']}", MU::ERR ok = false else if image_desc.plan terms = MU::Cloud::Azure.marketplace(credentials: @credentials).marketplace_agreements.get(image_desc.plan.publisher, image_desc.plan.product, image_desc.plan.name) if !terms.accepted MU.log "Deploying #{server['name']} will automatically agree to the licensing terms for #{terms.product}", MU::NOTICE, details: terms.license_text_link end end server['image_id'] = image_desc.id end if server['add_firewall_rules'] and server['add_firewall_rules'].size == 0 MU.log "Azure resources can only have one security group per network interface; use ingress_rules instead of add_firewall_rules.", MU::ERR ok = false end # Azure doesn't have default VPCs, so our fallback approach will be # to generate one on the fly. if server['vpc'].nil? vpc = { "name" => server['name']+"vpc", "cloud" => "Azure", "region" => server['region'], "credentials" => server['credentials'] } if !configurator.insertKitten(vpc, "vpcs", true) ok = false end MU::Config.addDependency(server, server['name']+"vpc", "vpc") MU::Config.addDependency(server, server['name']+"vpc-natstion", "server", their_phase: "groom") server['vpc'] = { "name" => server['name']+"vpc", "subnet_pref" => "private" } end server['vpc']['subnet_pref'] ||= "private" svcacct_desc = { "name" => server["name"]+"user", "region" => server["region"], "type" => "service", "cloud" => "Azure", "create_api_key" => true, "credentials" => server["credentials"], "roles" => server["roles"] } MU::Config.addDependency(server, server['name']+"user", "user") ok = false if !configurator.insertKitten(svcacct_desc, "users") ok end
Confirm that the given instance size is valid for the given region. 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]
# File modules/mu/providers/azure/server.rb, line 516 def self.validateInstanceType(size, region) size = size.dup.to_s types = (MU::Cloud::Azure.listInstanceTypes(region))[region] if types and (size.nil? or !types.has_key?(size)) # See if it's a type we can approximate from one of the other clouds foundmatch = false MU::Cloud.availableClouds.each { |cloud| next if cloud == "Azure" foreign_types = (MU::Cloud.cloudClass(cloud).listInstanceTypes).values.first if foreign_types.size == 1 foreign_types = foreign_types.values.first end 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| next if type.match(/_Promo$/i) 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 Azure Compute type '#{type}.'", MU::WARN size = type break end } end break if foundmatch } if !foundmatch MU.log "Invalid size '#{size}' for Azure Compute instance in #{region}. Supported types:", MU::ERR, details: types.keys.sort.join(", ") return nil end end size end
Public Instance Methods
Determine whether the node in question exists at the Cloud
provider layer. @return [Boolean]
# File modules/mu/providers/azure/server.rb, line 435 def active? !cloud_desc.nil? end
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 @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
# File modules/mu/providers/azure/server.rb, line 429 def addVolume(dev, size, type: "pd-standard", delete_on_termination: false) end
Return the IP address that we, the Mu server, should be using to access this host via the network. Note that this does not factor in SSH bastion hosts that may be in the path, see getSSHConfig if that's what you need.
# File modules/mu/providers/azure/server.rb, line 360 def canonicalIP describe(cloud_id: @cloud_id) if !cloud_desc raise MuError, "Couldn't retrieve cloud descriptor for server #{self}" end private_ips = [] public_ips = [] cloud_desc.network_profile.network_interfaces.each { |iface| iface_id = Id.new(iface.is_a?(Hash) ? iface['id'] : iface.id) iface_desc = MU::Cloud::Azure.network(credentials: @credentials).network_interfaces.get(@resource_group, iface_id.to_s) iface_desc.ip_configurations.each { |ipcfg| private_ips << ipcfg.private_ipaddress if ipcfg.respond_to?(:public_ipaddress) and ipcfg.public_ipaddress ip_id = Id.new(ipcfg.public_ipaddress.id) ip_desc = MU::Cloud::Azure.network(credentials: @credentials).public_ipaddresses.get(@resource_group, ip_id.to_s) if ip_desc public_ips << ip_desc.ip_address end end } } # Our deploydata gets corrupted often with server pools, this will cause us to use the wrong IP to identify a node # which will cause us to create certificates, DNS records and other artifacts with incorrect information which will cause our deploy to fail. # The cloud_id is always correct so lets use 'cloud_desc' to get the correct IPs if MU::Cloud.resourceClass("Azure", "VPC").haveRouteToInstance?(cloud_desc, credentials: @config['credentials']) or public_ips.size == 0 @config['canonical_ip'] = private_ips.first return private_ips.first else @config['canonical_ip'] = public_ips.first return public_ips.first end end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/azure/server.rb, line 89 def create create_update if !@config['async_groom'] sleep 5 MU::MommaCat.lock(@cloud_id.to_s+"-create") if !postBoot MU.log "#{@config['name']} is already being groomed, skipping", MU::NOTICE else MU.log "Node creation complete for #{@config['name']}" end MU::MommaCat.unlock(@cloud_id.to_s+"-create") end end
Figure out what's needed to SSH into this server. @return [Array<String>]: nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name, alternate_names
# File modules/mu/providers/azure/server.rb, line 141 def getSSHConfig describe(cloud_id: @cloud_id) # XXX add some awesome alternate names from metadata and make sure they end # up in MU::MommaCat's ssh config wangling ssh_keydir = Etc.getpwuid(Process.uid).dir+"/.ssh" return nil if @config.nil? or @deploy.nil? nat_ssh_key = nat_ssh_user = nat_ssh_host = nil if !@config["vpc"].nil? and !MU::Cloud.resourceClass("Azure", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials']) if !@nat.nil? and @nat.mu_name != @mu_name if @nat.cloud_desc.nil? MU.log "NAT #{@nat} was missing cloud descriptor when called in #{@mu_name}'s getSSHConfig", MU::ERR return nil end _foo, _bar, _baz, nat_ssh_host, nat_ssh_user, nat_ssh_key = @nat.getSSHConfig if nat_ssh_user.nil? and !nat_ssh_host.nil? MU.log "#{@config["name"]} (#{MU.deploy_id}) is configured to use #{@config['vpc']} NAT #{nat_ssh_host}, but username isn't specified. Guessing root.", MU::ERR, details: caller nat_ssh_user = "root" end end end if @config['ssh_user'].nil? if windows? @config['ssh_user'] = "muadmin" else @config['ssh_user'] = "root" end end return [nat_ssh_key, nat_ssh_user, nat_ssh_host, canonicalIP, @config['ssh_user'], @deploy.ssh_key_name] end
return [String]: A password string.
# File modules/mu/providers/azure/server.rb, line 420 def getWindowsAdminPassword @deploy.fetchSecret(@mu_name, "windows_admin_password") end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/azure/server.rb, line 266 def groom create_update MU::MommaCat.lock(@cloud_id.to_s+"-groom") node, _config, deploydata = describe(cloud_id: @cloud_id) if node.nil? or node.empty? raise MuError, "MU::Cloud::Azure::Server.groom was called without a mu_name" end # Make double sure we don't lose a cached mu_windows_name value. if windows? or !@config['active_directory'].nil? if @mu_windows_name.nil? @mu_windows_name = deploydata['mu_windows_name'] end end @groomer.saveDeployData begin @groomer.run(purpose: "Full Initial Run", max_retries: 15) rescue MU::Groomer::RunError MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN end if !@config['create_image'].nil? and !@config['image_created'] img_cfg = @config['create_image'] # Scrub things that don't belong on an AMI session = getSSHSession sudo = purgecmd = "" sudo = "sudo" if @config['ssh_user'] != "root" if windows? purgecmd = "rm -rf /cygdrive/c/mu_installed_chef" else purgecmd = "rm -rf /opt/mu_installed_chef" end if img_cfg['image_then_destroy'] if windows? purgecmd = "rm -rf /cygdrive/c/chef/ /home/#{@config['windows_admin_username']}/.ssh/authorized_keys /home/Administrator/.ssh/authorized_keys /cygdrive/c/mu-installer-ran-updates /cygdrive/c/mu_installed_chef" # session.exec!("powershell -Command \"& {(Get-WmiObject -Class Win32_Product -Filter \"Name='UniversalForwarder'\").Uninstall()}\"") else purgecmd = "#{sudo} rm -rf /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 stop image_id = MU::Cloud::Azure::Server.createImage( name: MU::Cloud::Azure.nameStr(@mu_name), instance_id: @cloud_id, region: @config['region'], storage: @config['storage'], family: ("mu-"+@config['platform']+"-"+MU.environment).downcase, project: @project_id, exclude_storage: img_cfg['image_exclude_storage'], make_public: img_cfg['public'], tags: @config['tags'], zone: @config['availability_zone'], credentials: @config['credentials'] ) @deploy.notify("images", @config['name'], {"image_id" => image_id}) @config['image_created'] = true if img_cfg['image_then_destroy'] MU.log "Image #{image_id} ready, removing source node #{node}" MU::Cloud::Azure.compute(credentials: @config['credentials']).delete_instance( @project_id, @config['availability_zone'], @cloud_id ) destroy else start end end MU::MommaCat.unlock(@cloud_id.to_s+"-groom") end
Return all of the IP addresses, public and private, from all of our network interfaces. @return [Array<String>]
# File modules/mu/providers/azure/server.rb, line 400 def listIPs ips = [] cloud_desc.network_profile.network_interfaces.each { |iface| iface_id = Id.new(iface.is_a?(Hash) ? iface['id'] : iface.id) iface_desc = MU::Cloud::Azure.network(credentials: @credentials).network_interfaces.get(@resource_group, iface_id.to_s) iface_desc.ip_configurations.each { |ipcfg| ips << ipcfg.private_ipaddress if ipcfg.respond_to?(:public_ipaddress) and ipcfg.public_ipaddress ip_id = Id.new(ipcfg.public_ipaddress.id) ip_desc = MU::Cloud::Azure.network(credentials: @credentials).public_ipaddresses.get(@resource_group, ip_id.to_s) if ip_desc ips << ip_desc.ip_address end end } } ips end
Return a description of this resource appropriate for deployment metadata. Arguments reflect the return values of the MU::Cloud::.describe method
# File modules/mu/providers/azure/server.rb, line 261 def notify MU.structToHash(cloud_desc) end
Apply tags, bootstrap our configuration management, and other administravia for a new instance.
# File modules/mu/providers/azure/server.rb, line 178 def postBoot(instance_id = nil) if !instance_id.nil? @cloud_id ||= instance_id end # Unless we're planning on associating a different IP later, set up a # DNS entry for this thing and let it sync in the background. We'll # come back to it later. if @config['static_ip'].nil? && !@named MU::MommaCat.nameKitten(self) @named = true end _nat_ssh_key, _nat_ssh_user, nat_ssh_host, _canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig if !nat_ssh_host and !MU::Cloud.resourceClass("Azure", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials']) # XXX check if canonical_ip is in the private ranges # raise MuError, "#{node} has no NAT host configured, and I have no other route to it" end # See if this node already exists in our config management. If it does, # we're done. if @groomer.haveBootstrapped? MU.log "Node #{@mu_name} has already been bootstrapped, skipping groomer setup.", MU::NOTICE @groomer.saveDeployData MU::MommaCat.unlock(@cloud_id.to_s+"-orchestrate") MU::MommaCat.unlock(@cloud_id.to_s+"-groom") return true end @groomer.bootstrap # Make sure we got our name written everywhere applicable if !@named MU::MommaCat.nameKitten(self) @named = true end MU::MommaCat.unlock(@cloud_id.to_s+"-groom") MU::MommaCat.unlock(@cloud_id.to_s+"-orchestrate") return true end
Ask the Azure
API to restart this node XXX unimplemented
# File modules/mu/providers/azure/server.rb, line 134 def reboot(hard = false) return if @cloud_id.nil? end
Ask the Azure
API to start this node
# File modules/mu/providers/azure/server.rb, line 128 def start MU.log "XXX Starting #{@cloud_id}" end
Ask the Azure
API to stop this node
# File modules/mu/providers/azure/server.rb, line 123 def stop MU.log "XXX Stopping #{@cloud_id}" end
Private Instance Methods
# File modules/mu/providers/azure/server.rb, line 704 def create_update ipcfg = MU::Cloud::Azure.network(:NetworkInterfaceIPConfiguration).new ipcfg.name = @mu_name ipcfg.private_ipallocation_method = MU::Cloud::Azure.network(:IPAllocationMethod)::Dynamic private_nets = @vpc.subnets.reject { |s| !s.private? } public_nets = @vpc.subnets.reject { |s| s.private? } stubnet = if @config['vpc']['subnet_id'] useme = nil @vpc.subnets.each { |s| if s.cloud_id.to_s == @config['vpc']['subnet_id'] useme = s break end } if !useme raise MuError, "Failed to locate subnet #{@config['vpc']['subnet_id']} in VPC #{@vpc.to_s}" end useme elsif @config['vpc']['subnet_pref'] == "private" or @config['vpc']['subnet_pref'] == "all_private" if private_nets.size == 0 raise MuError, "Server #{@mu_name} wanted a private subnet, but there are none in #{@vpc.to_s}" end private_nets.sample elsif @config['vpc']['subnet_pref'] == "public" or @config['vpc']['subnet_pref'] == "all_public" if public_nets.size == 0 raise MuError, "Server #{@mu_name} wanted a public subnet, but there are none in #{@vpc.to_s}" end public_nets.sample end # Allocate a public IP if we asked for one if @config['associate_public_ip'] or !stubnet.private? pubip_obj = MU::Cloud::Azure.network(:PublicIPAddress).new pubip_obj.public_ipallocation_method = MU::Cloud::Azure.network(:IPAllocationMethod)::Dynamic pubip_obj.location = @config['region'] pubip_obj.tags = @tags resp = MU::Cloud::Azure.network(credentials: @credentials).public_ipaddresses.create_or_update(@resource_group, @mu_name, pubip_obj) ipcfg.public_ipaddress = resp end ipcfg.subnet = MU::Cloud::Azure.network(:Subnet).new ipcfg.subnet.id = stubnet.cloud_desc.id sg = @deploy.findLitterMate(type: "firewall_rule", name: "server"+@config['name']) iface_obj = MU::Cloud::Azure.network(:NetworkInterface).new iface_obj.location = @config['region'] iface_obj.tags = @tags iface_obj.primary = true iface_obj.network_security_group = sg.cloud_desc if sg iface_obj.enable_ipforwarding = !@config['src_dst_check'] iface_obj.ip_configurations = [ipcfg] MU.log "Creating network interface #{@mu_name}", MU::DEBUG, details: iface_obj iface = MU::Cloud::Azure.network(credentials: @credentials).network_interfaces.create_or_update(@resource_group, @mu_name, iface_obj) img_obj = MU::Cloud::Azure.compute(:ImageReference).new @config['image_id'].match(/\/Subscriptions\/[^\/]+\/Providers\/Microsoft.Compute\/Locations\/[^\/]+\/Publishers\/([^\/]+)\/ArtifactTypes\/VMImage\/Offers\/([^\/]+)\/Skus\/([^\/]+)\/Versions\/([^\/]+)$/) img_obj.publisher = Regexp.last_match[1] img_obj.offer = Regexp.last_match[2] img_obj.sku = Regexp.last_match[3] img_obj.version = Regexp.last_match[4] hw_obj = MU::Cloud::Azure.compute(:HardwareProfile).new hw_obj.vm_size = @config['size'] os_obj = MU::Cloud::Azure.compute(:OSProfile).new if windows? winrm_listen = MU::Cloud::Azure.compute(:WinRMListener).new winrm_listen.certificate_url = "goddamn stupid ass thing" winrm_listen.protocol = "https" winrm = MU::Cloud::Azure.compute(:WinRMConfiguration).new winrm.listeners = [winrm_listen] win_obj = MU::Cloud::Azure.compute(:WindowsConfiguration).new win_obj.win_rmconfiguration = winrm os_obj.windows_configuration = win_obj os_obj.admin_username = @config['windows_admin_username'] os_obj.admin_password = begin @deploy.fetchSecret(@mu_name, "windows_admin_password") rescue MU::MommaCat::SecretError pw = MU.generateWindowsPassword @deploy.saveNodeSecret(@mu_name, pw, "windows_admin_password") pw end os_obj.computer_name = @deploy.getResourceName(@config["name"], max_length: 15, disallowed_chars: /[~!@#$%^&*()=+_\[\]{}\\\|;:\.'",<>\/\?]/) else os_obj.admin_username = @config['ssh_user'] os_obj.computer_name = @mu_name key_obj = MU::Cloud::Azure.compute(:SshPublicKey).new key_obj.key_data = @deploy.ssh_public_key key_obj.path = "/home/#{@config['ssh_user']}/.ssh/authorized_keys" ssh_obj = MU::Cloud::Azure.compute(:SshConfiguration).new ssh_obj.public_keys = [key_obj] lnx_obj = MU::Cloud::Azure.compute(:LinuxConfiguration).new lnx_obj.disable_password_authentication = true lnx_obj.ssh = ssh_obj os_obj.linux_configuration = lnx_obj end vm_id_obj = MU::Cloud::Azure.compute(:VirtualMachineIdentity).new vm_id_obj.type = "UserAssigned" svc_acct = @deploy.findLitterMate(type: "user", name: @config['name']+"user") raise MuError, "Failed to locate service account #{@config['name']}user" if !svc_acct vm_id_obj.user_assigned_identities = { svc_acct.cloud_desc.id => svc_acct.cloud_desc } vm_obj = MU::Cloud::Azure.compute(:VirtualMachine).new vm_obj.location = @config['region'] vm_obj.tags = @tags vm_obj.network_profile = MU::Cloud::Azure.compute(:NetworkProfile).new vm_obj.network_profile.network_interfaces = [iface] vm_obj.hardware_profile = hw_obj vm_obj.os_profile = os_obj vm_obj.identity = vm_id_obj vm_obj.storage_profile = MU::Cloud::Azure.compute(:StorageProfile).new vm_obj.storage_profile.image_reference = img_obj image_desc = MU::Cloud::Azure::Server.fetchImage(@config['image_id'].to_s, credentials: @config['credentials'], region: @config['region']) # XXX do this as a catch around instance creation so we don't waste API calls if image_desc.plan terms = MU::Cloud::Azure.marketplace(credentials: @credentials).marketplace_agreements.get(image_desc.plan.publisher, image_desc.plan.product, image_desc.plan.name) if !terms.accepted MU.log "Agreeing to licensing terms of #{terms.product}", MU::NOTICE begin # XXX this doesn't actually work as documented MU::Cloud::Azure.marketplace(credentials: @credentials).marketplace_agreements.sign(image_desc.plan.publisher, image_desc.plan.product, image_desc.plan.name) rescue StandardError => e MU.log e.message, MU::ERR vm_obj.plan = nil end end vm_obj.plan = image_desc.plan end if @config['storage'] vm_obj.storage_profile.data_disks = [] @config['storage'].each { |disk| lun = if disk['device'].is_a?(Integer) or disk['device'].match(/^\d+$/) disk['device'].to_i else disk['device'].match(/([a-z])[^a-z]*$/i) # map the last letter of the requested device to a numeric lun # so that a => 1, b => 2, and so on Regexp.last_match[1].downcase.encode("ASCII-8BIT").ord - 96 end disk_obj = MU::Cloud::Azure.compute(:DataDisk).new disk_obj.disk_size_gb = disk['size'] disk_obj.lun = lun disk_obj.name = @mu_name+disk['device'].to_s.gsub(/[^\w\-._]/, '_').upcase disk_obj.create_option = MU::Cloud::Azure.compute(:DiskCreateOptionTypes)::Empty vm_obj.storage_profile.data_disks << disk_obj } end if !@cloud_id # XXX actually guard this correctly MU.log "Creating VM #{@mu_name}", details: vm_obj begin vm = MU::Cloud::Azure.compute(credentials: @credentials).virtual_machines.create_or_update(@resource_group, @mu_name, vm_obj) @cloud_id = Id.new(vm.id) rescue ::MU::Cloud::Azure::APIError => e if e.message.match(/InvalidParameter: /) MU.log e.message, MU::ERR, details: vm_obj end raise e end end end