From 2953f8adf75bb17eafe42756e2c3598015999665 Mon Sep 17 00:00:00 2001 From: Jared Kauppila Date: Wed, 13 Apr 2022 09:48:35 -0500 Subject: [PATCH 1/4] Move the fetching of the Windows admin password into the `wait_until_ready` function to speed up readiness detection of Windows instances --- lib/kitchen/driver/ec2.rb | 82 +++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/lib/kitchen/driver/ec2.rb b/lib/kitchen/driver/ec2.rb index 3e9db3f4..f8b0f368 100644 --- a/lib/kitchen/driver/ec2.rb +++ b/lib/kitchen/driver/ec2.rb @@ -536,12 +536,20 @@ def wait_until_ready(server, state) state[:hostname] = hostname(aws_instance, nil) end if ready && windows_os? - output = server.console_output.output - unless output.nil? - output = Base64.decode64(output) - debug "Console output: --- \n#{output}" + if windows_os? && + instance.transport[:username] =~ /administrator/i && + instance.transport[:password].nil? + # If we're logging into the administrator user and a password isn't + # supplied, try to fetch it from the AWS instance + fetch_windows_admin_password(server, state) + else + output = server.console_output.output + unless output.nil? + output = Base64.decode64(output) + debug "Console output: --- \n#{output}" + end + ready = !!(output =~ /Windows is Ready to use/) end - ready = !!(output =~ /Windows is Ready to use/) end ready end @@ -750,38 +758,38 @@ def create_security_group(state) # Work out which VPC, if any, we are creating in. vpc_id = if config[:subnet_id] - # Get the VPC ID for the subnet. - subnets = ec2.client.describe_subnets(filters: [{ name: "subnet-id", values: [config[:subnet_id]] }]).subnets - raise "Subnet #{config[:subnet_id]} not found during security group creation" if subnets.empty? - - subnets.first.vpc_id - elsif config[:subnet_filter] - filters = [config[:subnet_filter]].flatten - - r = { filters: [] } - filters.each do |subnet_filter| - r[:filters] << { - name: "tag:#{subnet_filter[:tag]}", - values: [subnet_filter[:value]], - } - end - - subnets = ec2.client.describe_subnets(r).subnets - - raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? - - subnets.first.vpc_id - else - # Try to check for a default VPC. - vpcs = ec2.client.describe_vpcs(filters: [{ name: "isDefault", values: ["true"] }]).vpcs - if vpcs.empty? - # No default VPC so assume EC2-Classic ¯\_(ツ)_/¯ - nil - else - # I don't actually know if you can have more than one default VPC? - vpcs.first.vpc_id - end - end + # Get the VPC ID for the subnet. + subnets = ec2.client.describe_subnets(filters: [{ name: "subnet-id", values: [config[:subnet_id]] }]).subnets + raise "Subnet #{config[:subnet_id]} not found during security group creation" if subnets.empty? + + subnets.first.vpc_id + elsif config[:subnet_filter] + filters = [config[:subnet_filter]].flatten + + r = { filters: [] } + filters.each do |subnet_filter| + r[:filters] << { + name: "tag:#{subnet_filter[:tag]}", + values: [subnet_filter[:value]], + } + end + + subnets = ec2.client.describe_subnets(r).subnets + + raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? + + subnets.first.vpc_id + else + # Try to check for a default VPC. + vpcs = ec2.client.describe_vpcs(filters: [{ name: "isDefault", values: ["true"] }]).vpcs + if vpcs.empty? + # No default VPC so assume EC2-Classic ¯\_(ツ)_/¯ + nil + else + # I don't actually know if you can have more than one default VPC? + vpcs.first.vpc_id + end + end # Create the SG. params = { group_name: "kitchen-#{Array.new(8) { rand(36).to_s(36) }.join}", From 704b21250323d206961b9fc1f49bb65844e87117 Mon Sep 17 00:00:00 2001 From: Jared Kauppila Date: Wed, 13 Apr 2022 10:25:02 -0500 Subject: [PATCH 2/4] Fix syntax issues --- lib/kitchen/driver/ec2.rb | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/kitchen/driver/ec2.rb b/lib/kitchen/driver/ec2.rb index f8b0f368..bf293608 100644 --- a/lib/kitchen/driver/ec2.rb +++ b/lib/kitchen/driver/ec2.rb @@ -537,8 +537,8 @@ def wait_until_ready(server, state) end if ready && windows_os? if windows_os? && - instance.transport[:username] =~ /administrator/i && - instance.transport[:password].nil? + instance.transport[:username] =~ /administrator/i && + instance.transport[:password].nil? # If we're logging into the administrator user and a password isn't # supplied, try to fetch it from the AWS instance fetch_windows_admin_password(server, state) @@ -758,38 +758,38 @@ def create_security_group(state) # Work out which VPC, if any, we are creating in. vpc_id = if config[:subnet_id] - # Get the VPC ID for the subnet. - subnets = ec2.client.describe_subnets(filters: [{ name: "subnet-id", values: [config[:subnet_id]] }]).subnets - raise "Subnet #{config[:subnet_id]} not found during security group creation" if subnets.empty? - - subnets.first.vpc_id - elsif config[:subnet_filter] - filters = [config[:subnet_filter]].flatten - - r = { filters: [] } - filters.each do |subnet_filter| - r[:filters] << { - name: "tag:#{subnet_filter[:tag]}", - values: [subnet_filter[:value]], - } - end - - subnets = ec2.client.describe_subnets(r).subnets - - raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? - - subnets.first.vpc_id - else - # Try to check for a default VPC. - vpcs = ec2.client.describe_vpcs(filters: [{ name: "isDefault", values: ["true"] }]).vpcs - if vpcs.empty? - # No default VPC so assume EC2-Classic ¯\_(ツ)_/¯ - nil - else - # I don't actually know if you can have more than one default VPC? - vpcs.first.vpc_id - end - end + # Get the VPC ID for the subnet. + subnets = ec2.client.describe_subnets(filters: [{ name: "subnet-id", values: [config[:subnet_id]] }]).subnets + raise "Subnet #{config[:subnet_id]} not found during security group creation" if subnets.empty? + + subnets.first.vpc_id + elsif config[:subnet_filter] + filters = [config[:subnet_filter]].flatten + + r = { filters: [] } + filters.each do |subnet_filter| + r[:filters] << { + name: "tag:#{subnet_filter[:tag]}", + values: [subnet_filter[:value]], + } + end + + subnets = ec2.client.describe_subnets(r).subnets + + raise "Subnets with tags '#{filters}' not found during security group creation" if subnets.empty? + + subnets.first.vpc_id + else + # Try to check for a default VPC. + vpcs = ec2.client.describe_vpcs(filters: [{ name: "isDefault", values: ["true"] }]).vpcs + if vpcs.empty? + # No default VPC so assume EC2-Classic ¯\_(ツ)_/¯ + nil + else + # I don't actually know if you can have more than one default VPC? + vpcs.first.vpc_id + end + end # Create the SG. params = { group_name: "kitchen-#{Array.new(8) { rand(36).to_s(36) }.join}", From 2df22a6d9e203de45cc7aebefd84d44de9a3d5e1 Mon Sep 17 00:00:00 2001 From: Jared Kauppila Date: Thu, 14 Apr 2022 10:20:05 -0500 Subject: [PATCH 3/4] Forgot to remove the block of code that was moved Remove duplicate `windows_os?` check --- lib/kitchen/driver/ec2.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/kitchen/driver/ec2.rb b/lib/kitchen/driver/ec2.rb index bf293608..ec1e46f7 100644 --- a/lib/kitchen/driver/ec2.rb +++ b/lib/kitchen/driver/ec2.rb @@ -251,14 +251,6 @@ def create(state) wait_until_ready(server, state) end - if windows_os? && - instance.transport[:username] =~ /administrator/i && - instance.transport[:password].nil? - # If we're logging into the administrator user and a password isn't - # supplied, try to fetch it from the AWS instance - fetch_windows_admin_password(server, state) - end - info("EC2 instance <#{state[:server_id]}> ready (hostname: #{state[:hostname]}).") instance.transport.connection(state).wait_until_ready attach_network_interface(state) unless config[:elastic_network_interface_id].nil? @@ -536,8 +528,7 @@ def wait_until_ready(server, state) state[:hostname] = hostname(aws_instance, nil) end if ready && windows_os? - if windows_os? && - instance.transport[:username] =~ /administrator/i && + if instance.transport[:username] =~ /administrator/i && instance.transport[:password].nil? # If we're logging into the administrator user and a password isn't # supplied, try to fetch it from the AWS instance From 3bd685e472c5d52af87c8d6219f31a74d814199b Mon Sep 17 00:00:00 2001 From: Jared Kauppila Date: Sat, 16 Apr 2022 09:34:09 -0500 Subject: [PATCH 4/4] Adds/fixes tests --- spec/kitchen/driver/ec2_spec.rb | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/spec/kitchen/driver/ec2_spec.rb b/spec/kitchen/driver/ec2_spec.rb index c29c2630..c16e71a2 100644 --- a/spec/kitchen/driver/ec2_spec.rb +++ b/spec/kitchen/driver/ec2_spec.rb @@ -311,6 +311,46 @@ expect(driver.wait_until_ready(server, state)).to eq(true) end end + + context "when windows instance" do + let(:hostname) { "windows" } + + before do + expect(aws_instance).to receive(:exists?).and_return(true) + expect(aws_instance).to receive_message_chain("state.name").and_return("running") + expect(driver).to receive(:windows_os?).and_return(true) + end + + context "does define a username/password" do + before do + expect(transport).to receive(:[]).with(:username).and_return("foo") + end + + context "console output is ready" do + it "returns true" do + # base64 encoded `Windows is Ready to use` + expect(server).to receive_message_chain("console_output.output").and_return("V2luZG93cyBpcyBSZWFkeSB0byB1c2U=") + expect(driver.wait_until_ready(server, state)).to eq(true) + end + end + + context "console output is not ready" do + it "returns false" do + expect(server).to receive_message_chain("console_output.output").and_return("") + expect(driver.wait_until_ready(server, state)).to eq(false) + end + end + end + + context "does not define a username/password" do + it "returns true" do + expect(transport).to receive(:[]).with(:username).and_return("administrator") + expect(transport).to receive(:[]).with(:password).and_return(nil) + expect(driver).to receive(:fetch_windows_admin_password).with(server, state) + expect(driver.wait_until_ready(server, state)).to eq(true) + end + end + end end describe "#fetch_windows_admin_password" do @@ -482,11 +522,7 @@ context "instance is a windows machine" do before do - expect(driver).to receive(:windows_os?).and_return(true) - expect(transport).to receive(:[]).with(:username).and_return("administrator") - expect(transport).to receive(:[]).with(:password).and_return(nil) expect(driver).to receive(:submit_server).and_return(server) - expect(driver).to receive(:fetch_windows_admin_password).with(server, state) end include_examples "common create"