diff --git a/.doc_gen/metadata/ec2_metadata.yaml b/.doc_gen/metadata/ec2_metadata.yaml index f7560035726..80bbcf8e77b 100644 --- a/.doc_gen/metadata/ec2_metadata.yaml +++ b/.doc_gen/metadata/ec2_metadata.yaml @@ -470,6 +470,7 @@ ec2_StartInstances: - description: snippet_tags: - EC2.dotnetv3.StartInstances + - EC2.dotnetv3.WaitForInstanceState Kotlin: versions: - sdk_version: 1 @@ -569,6 +570,7 @@ ec2_StopInstances: - description: snippet_tags: - EC2.dotnetv3.StopInstances + - EC2.dotnetv3.WaitForInstanceState Kotlin: versions: - sdk_version: 1 @@ -1393,6 +1395,7 @@ ec2_TerminateInstances: - description: snippet_tags: - EC2.dotnetv3.TerminateInstances + - EC2.dotnetv3.WaitForInstanceState Kotlin: versions: - sdk_version: 1 @@ -1682,9 +1685,10 @@ ec2_RebootInstances: github: dotnetv3/EC2 sdkguide: excerpts: - - description: + - description: Reboot an instance by its Id. snippet_tags: - EC2.dotnetv3.RebootInstances + - EC2.dotnetv3.WaitForInstanceState - description: Replace the profile for an instance, reboot, and restart a web server. snippet_tags: - ResilientService.dotnetv3.Ec2.ReplaceInstanceProfile diff --git a/dotnetv3/AutoScaling/README.md b/dotnetv3/AutoScaling/README.md index 0c121f8c537..0a08d4e154a 100644 --- a/dotnetv3/AutoScaling/README.md +++ b/dotnetv3/AutoScaling/README.md @@ -45,9 +45,9 @@ Code examples that show you how to perform the essential operations within a ser Code excerpts that show you how to call individual service functions. -- [AttachLoadBalancerTargetGroups](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L770) +- [AttachLoadBalancerTargetGroups](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L890) - [CreateAutoScalingGroup](Actions/AutoScalingWrapper.cs#L28) -- [DeleteAutoScalingGroup](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L573) +- [DeleteAutoScalingGroup](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L693) - [DescribeAutoScalingGroups](Actions/AutoScalingWrapper.cs#L109) - [DescribeAutoScalingInstances](Actions/AutoScalingWrapper.cs#L109) - [DescribeScalingActivities](Actions/AutoScalingWrapper.cs#L86) diff --git a/dotnetv3/DotNetV3Examples.sln b/dotnetv3/DotNetV3Examples.sln index faa5da6d7e2..46a909e594f 100644 --- a/dotnetv3/DotNetV3Examples.sln +++ b/dotnetv3/DotNetV3Examples.sln @@ -183,18 +183,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EC2", "EC2", "{77D9BB30-99D EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VirtualPrivateCloudExamples", "VirtualPrivateCloudExamples", "{FD37C59A-C4EA-4AAD-85F7-66767E98C52A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCTests", "EC2\VirtualPrivateCloudExamples\CreateVPCTests\CreateVPCTests.csproj", "{775E67C5-BE91-4E4D-840A-380EF533AF27}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCforS3Example", "EC2\VirtualPrivateCloudExamples\CreateVPCforS3Example\CreateVPCforS3Example.csproj", "{567E2B6E-8961-454E-8E2E-F97097F1C581}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCExample", "EC2\VirtualPrivateCloudExamples\CreateVPCExample\CreateVPCExample.csproj", "{D05F6F17-3CCF-4D0F-8AE0-88373690C608}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC2Tests", "EC2\Tests\EC2Tests.csproj", "{6660A7CD-3AFB-4A99-995A-70DE9DADB81E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{327CF162-0F15-4E54-AFF2-3477410D7B34}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ec2_Basics", "EC2\Scenarios\EC2_Basics\Ec2_Basics.csproj", "{4D877A12-1D76-4E30-99AB-19F25A4C4FFD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC2Actions", "EC2\Actions\EC2Actions.csproj", "{C9A508B1-CEF1-45F5-BF1F-1DEB905E9F35}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBridge", "EventBridge", "{069EFCF0-C4CE-4308-9712-277C49BC4695}" @@ -751,6 +743,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BedrockRuntimeActions", "Be EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BedrockRuntimeTests", "Bedrock-runtime\Tests\BedrockRuntimeTests.csproj", "{A6BFA81D-F06F-4E4C-A7B9-CBE57D2C00C7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Basics", "EC2\Scenarios\EC2_Basics\Basics.csproj", "{95958FB6-4C90-4806-A6E7-B5CF92B19BAF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCExample", "EC2\Scenarios\CreateVPCExample\CreateVPCExample.csproj", "{94D97B37-7104-4B7C-BF73-F8F2EF8F7A04}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCforS3Example", "EC2\Scenarios\CreateVPCforS3Example\CreateVPCforS3Example.csproj", "{C4922A8E-29EE-4100-8102-8DC2C7EBE773}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -957,26 +955,10 @@ Global {8CE79AEB-9CA0-4746-97F3-A1E73B14B0BF}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CE79AEB-9CA0-4746-97F3-A1E73B14B0BF}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CE79AEB-9CA0-4746-97F3-A1E73B14B0BF}.Release|Any CPU.Build.0 = Release|Any CPU - {775E67C5-BE91-4E4D-840A-380EF533AF27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {775E67C5-BE91-4E4D-840A-380EF533AF27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {775E67C5-BE91-4E4D-840A-380EF533AF27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {775E67C5-BE91-4E4D-840A-380EF533AF27}.Release|Any CPU.Build.0 = Release|Any CPU - {567E2B6E-8961-454E-8E2E-F97097F1C581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {567E2B6E-8961-454E-8E2E-F97097F1C581}.Debug|Any CPU.Build.0 = Debug|Any CPU - {567E2B6E-8961-454E-8E2E-F97097F1C581}.Release|Any CPU.ActiveCfg = Release|Any CPU - {567E2B6E-8961-454E-8E2E-F97097F1C581}.Release|Any CPU.Build.0 = Release|Any CPU - {D05F6F17-3CCF-4D0F-8AE0-88373690C608}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D05F6F17-3CCF-4D0F-8AE0-88373690C608}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D05F6F17-3CCF-4D0F-8AE0-88373690C608}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D05F6F17-3CCF-4D0F-8AE0-88373690C608}.Release|Any CPU.Build.0 = Release|Any CPU {6660A7CD-3AFB-4A99-995A-70DE9DADB81E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6660A7CD-3AFB-4A99-995A-70DE9DADB81E}.Debug|Any CPU.Build.0 = Debug|Any CPU {6660A7CD-3AFB-4A99-995A-70DE9DADB81E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6660A7CD-3AFB-4A99-995A-70DE9DADB81E}.Release|Any CPU.Build.0 = Release|Any CPU - {4D877A12-1D76-4E30-99AB-19F25A4C4FFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D877A12-1D76-4E30-99AB-19F25A4C4FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D877A12-1D76-4E30-99AB-19F25A4C4FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D877A12-1D76-4E30-99AB-19F25A4C4FFD}.Release|Any CPU.Build.0 = Release|Any CPU {C9A508B1-CEF1-45F5-BF1F-1DEB905E9F35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C9A508B1-CEF1-45F5-BF1F-1DEB905E9F35}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9A508B1-CEF1-45F5-BF1F-1DEB905E9F35}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1705,6 +1687,18 @@ Global {A6BFA81D-F06F-4E4C-A7B9-CBE57D2C00C7}.Debug|Any CPU.Build.0 = Debug|Any CPU {A6BFA81D-F06F-4E4C-A7B9-CBE57D2C00C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6BFA81D-F06F-4E4C-A7B9-CBE57D2C00C7}.Release|Any CPU.Build.0 = Release|Any CPU + {95958FB6-4C90-4806-A6E7-B5CF92B19BAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95958FB6-4C90-4806-A6E7-B5CF92B19BAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95958FB6-4C90-4806-A6E7-B5CF92B19BAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95958FB6-4C90-4806-A6E7-B5CF92B19BAF}.Release|Any CPU.Build.0 = Release|Any CPU + {94D97B37-7104-4B7C-BF73-F8F2EF8F7A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94D97B37-7104-4B7C-BF73-F8F2EF8F7A04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94D97B37-7104-4B7C-BF73-F8F2EF8F7A04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94D97B37-7104-4B7C-BF73-F8F2EF8F7A04}.Release|Any CPU.Build.0 = Release|Any CPU + {C4922A8E-29EE-4100-8102-8DC2C7EBE773}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4922A8E-29EE-4100-8102-8DC2C7EBE773}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4922A8E-29EE-4100-8102-8DC2C7EBE773}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4922A8E-29EE-4100-8102-8DC2C7EBE773}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1790,12 +1784,8 @@ Global {81E5E7FC-5F25-48F5-A0F9-17C862B5A6AA} = {86F4D22B-B15A-46F4-8F30-8501D244841A} {8CE79AEB-9CA0-4746-97F3-A1E73B14B0BF} = {81E5E7FC-5F25-48F5-A0F9-17C862B5A6AA} {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} = {77D9BB30-99D7-47A3-B060-2D7CB3753162} - {775E67C5-BE91-4E4D-840A-380EF533AF27} = {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} - {567E2B6E-8961-454E-8E2E-F97097F1C581} = {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} - {D05F6F17-3CCF-4D0F-8AE0-88373690C608} = {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} {6660A7CD-3AFB-4A99-995A-70DE9DADB81E} = {77D9BB30-99D7-47A3-B060-2D7CB3753162} {327CF162-0F15-4E54-AFF2-3477410D7B34} = {77D9BB30-99D7-47A3-B060-2D7CB3753162} - {4D877A12-1D76-4E30-99AB-19F25A4C4FFD} = {327CF162-0F15-4E54-AFF2-3477410D7B34} {C9A508B1-CEF1-45F5-BF1F-1DEB905E9F35} = {77D9BB30-99D7-47A3-B060-2D7CB3753162} {08DD4B54-506C-4693-9AA9-61251BB4C1C3} = {069EFCF0-C4CE-4308-9712-277C49BC4695} {3F953A45-5531-464D-A3A5-59CFC8869053} = {069EFCF0-C4CE-4308-9712-277C49BC4695} @@ -2046,6 +2036,9 @@ Global {F0333DC4-6E86-4D69-8DFE-E837F9A049E6} = {5810E90E-DBA6-438A-8FB3-A1769B2E4ECA} {979D095E-987A-4A0C-BA76-3C716C16412B} = {68C08F40-075C-457A-9F7A-62B2CD525CDC} {A6BFA81D-F06F-4E4C-A7B9-CBE57D2C00C7} = {68C08F40-075C-457A-9F7A-62B2CD525CDC} + {95958FB6-4C90-4806-A6E7-B5CF92B19BAF} = {327CF162-0F15-4E54-AFF2-3477410D7B34} + {94D97B37-7104-4B7C-BF73-F8F2EF8F7A04} = {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} + {C4922A8E-29EE-4100-8102-8DC2C7EBE773} = {FD37C59A-C4EA-4AAD-85F7-66767E98C52A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA} diff --git a/dotnetv3/EC2/Actions/EC2Wrapper.cs b/dotnetv3/EC2/Actions/EC2Wrapper.cs index 9dd1cbb9ac8..7ae83baacae 100644 --- a/dotnetv3/EC2/Actions/EC2Wrapper.cs +++ b/dotnetv3/EC2/Actions/EC2Wrapper.cs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using Microsoft.Extensions.Logging; + namespace EC2Actions; // snippet-start:[EC2.dotnetv3.EC2WrapperClass] @@ -10,29 +12,60 @@ namespace EC2Actions; public class EC2Wrapper { private readonly IAmazonEC2 _amazonEC2; + private readonly ILogger _logger; - public EC2Wrapper(IAmazonEC2 amazonService) + /// + /// Constructor for the EC2Wrapper class. + /// + /// The injected EC2 client. + /// The injected logger. + public EC2Wrapper(IAmazonEC2 amazonService, ILogger logger) { _amazonEC2 = amazonService; + _logger = logger; } // snippet-start:[EC2.dotnetv3.AllocateAddress] /// - /// Allocate an Elastic IP address. + /// Allocates an Elastic IP address that can be associated with an Amazon EC2 + // instance. By using an Elastic IP address, you can keep the public IP address + // constant even when you restart the associated instance. /// - /// The allocation Id of the allocated address. - public async Task AllocateAddress() + /// The response object for the allocated address. + public async Task AllocateAddress() { var request = new AllocateAddressRequest(); - var response = await _amazonEC2.AllocateAddressAsync(request); - return response.AllocationId; + try + { + var response = await _amazonEC2.AllocateAddressAsync(request); + Console.WriteLine($"Allocated IP: {response.PublicIp} with allocation ID {response.AllocationId}."); + return response; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "AddressLimitExceeded") + { + // For more information on Elastic IP address quotas, see: + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html#using-instance-addressing-limit + _logger.LogError($"Unable to allocate Elastic IP, address limit exceeded. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError($"An error occurred while allocating Elastic IP.: {ex.Message}"); + throw; + } } // snippet-end:[EC2.dotnetv3.AllocateAddress] // snippet-start:[EC2.dotnetv3.AssociateAddress] /// - /// Associate an Elastic IP address to an EC2 instance. + /// Associates an Elastic IP address with an instance. When this association is + /// created, the Elastic IP's public IP address is immediately used as the public + /// IP address of the associated instance. /// /// The allocation Id of an Elastic IP address. /// The instance Id of the EC2 instance to @@ -41,14 +74,33 @@ public async Task AllocateAddress() /// the association of the Elastic IP address with an instance. public async Task AssociateAddress(string allocationId, string instanceId) { - var request = new AssociateAddressRequest + try { - AllocationId = allocationId, - InstanceId = instanceId - }; + var request = new AssociateAddressRequest + { + AllocationId = allocationId, + InstanceId = instanceId + }; - var response = await _amazonEC2.AssociateAddressAsync(request); - return response.AssociationId; + var response = await _amazonEC2.AssociateAddressAsync(request); + return response.AssociationId; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceId") + { + _logger.LogError( + $"InstanceId is invalid, unable to associate address. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while associating the Elastic IP.: {ex.Message}"); + throw; + } } // snippet-end:[EC2.dotnetv3.AssociateAddress] @@ -61,21 +113,41 @@ public async Task AssociateAddress(string allocationId, string instanceI /// A Boolean value indicating the success of the action. public async Task AuthorizeSecurityGroupIngress(string groupName) { - // Get the IP address for the local computer. - var ipAddress = await GetIpAddress(); - Console.WriteLine($"Your IP address is: {ipAddress}"); - var ipRanges = new List { new IpRange { CidrIp = $"{ipAddress}/32" } }; - var permission = new IpPermission - { - Ipv4Ranges = ipRanges, - IpProtocol = "tcp", - FromPort = 22, - ToPort = 22 - }; - var permissions = new List { permission }; - var response = await _amazonEC2.AuthorizeSecurityGroupIngressAsync( - new AuthorizeSecurityGroupIngressRequest(groupName, permissions)); - return response.HttpStatusCode == HttpStatusCode.OK; + try + { + // Get the IP address for the local computer. + var ipAddress = await GetIpAddress(); + Console.WriteLine($"Your IP address is: {ipAddress}"); + var ipRanges = + new List { new IpRange { CidrIp = $"{ipAddress}/32" } }; + var permission = new IpPermission + { + Ipv4Ranges = ipRanges, + IpProtocol = "tcp", + FromPort = 22, + ToPort = 22 + }; + var permissions = new List { permission }; + var response = await _amazonEC2.AuthorizeSecurityGroupIngressAsync( + new AuthorizeSecurityGroupIngressRequest(groupName, permissions)); + return response.HttpStatusCode == HttpStatusCode.OK; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidPermission.Duplicate") + { + _logger.LogError( + $"The ingress rule already exists. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while authorizing ingress.: {ex.Message}"); + throw; + } } /// @@ -97,28 +169,53 @@ private static async Task GetIpAddress() // snippet-start:[EC2.dotnetv3.CreateKeyPair] /// - /// Create an Amazon EC2 key pair. + /// Create an Amazon EC2 key pair with a specified name. /// /// The name for the new key pair. /// The Amazon EC2 key pair created. public async Task CreateKeyPair(string keyPairName) { - var request = new CreateKeyPairRequest + try { - KeyName = keyPairName, - }; + var request = new CreateKeyPairRequest { KeyName = keyPairName, }; - var response = await _amazonEC2.CreateKeyPairAsync(request); + var response = await _amazonEC2.CreateKeyPairAsync(request); - if (response.HttpStatusCode == HttpStatusCode.OK) - { var kp = response.KeyPair; - return kp; + // Return the key pair so it can be saved if needed. + + // Wait until the key pair exists. + int retries = 5; + while (retries-- > 0) + { + Console.WriteLine($"Checking for new KeyPair {keyPairName}..."); + var keyPairs = await DescribeKeyPairs(keyPairName); + if (keyPairs.Any()) + { + return kp; + } + + Thread.Sleep(5000); + retries--; + } + _logger.LogError($"Unable to find newly created KeyPair {keyPairName}."); + throw new DoesNotExistException("KeyPair not found"); } - else + catch (AmazonEC2Exception ec2Exception) { - Console.WriteLine("Could not create key pair."); - return null; + if (ec2Exception.ErrorCode == "InvalidKeyPair.Duplicate") + { + _logger.LogError( + $"A key pair called {keyPairName} already exists."); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while creating the key pair.: {ex.Message}"); + throw; } } @@ -144,17 +241,49 @@ public string SaveKeyPair(KeyPair keyPair) // snippet-start:[EC2.dotnetv3.CreateSecurityGroup] /// - /// Create an Amazon EC2 security group. + /// Create an Amazon EC2 security group with a specified name and description. /// /// The name for the new security group. /// A description of the new security group. /// The group Id of the new security group. public async Task CreateSecurityGroup(string groupName, string groupDescription) { - var response = await _amazonEC2.CreateSecurityGroupAsync( - new CreateSecurityGroupRequest(groupName, groupDescription)); + try + { + var response = await _amazonEC2.CreateSecurityGroupAsync( + new CreateSecurityGroupRequest(groupName, groupDescription)); - return response.GroupId; + // Wait until the security group exists. + int retries = 5; + while (retries-- > 0) + { + var groups = await DescribeSecurityGroups(response.GroupId); + if (groups.Any()) + { + return response.GroupId; + } + + Thread.Sleep(5000); + retries--; + } + _logger.LogError($"Unable to find newly created group {groupName}."); + throw new DoesNotExistException("security group not found"); + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "ResourceAlreadyExists") + { + _logger.LogError( + $"A security group with the name {groupName} already exists. {ec2Exception.Message}"); + } + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while creating the security group.: {ex.Message}"); + throw; + } } // snippet-end:[EC2.dotnetv3.CreateSecurityGroup] @@ -200,6 +329,15 @@ public async Task DeleteKeyPair(string keyPairName) await _amazonEC2.DeleteKeyPairAsync(new DeleteKeyPairRequest(keyPairName)).ConfigureAwait(false); return true; } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidKeyPair.NotFound") + { + _logger.LogError($"KeyPair {keyPairName} does not exist and cannot be deleted. Please verify the key pair name and try again."); + } + + return false; + } catch (Exception ex) { Console.WriteLine($"Couldn't delete the key pair because: {ex.Message}"); @@ -228,8 +366,28 @@ public void DeleteTempFile(string tempFileName) /// A Boolean value indicating the success of the action. public async Task DeleteSecurityGroup(string groupId) { - var response = await _amazonEC2.DeleteSecurityGroupAsync(new DeleteSecurityGroupRequest { GroupId = groupId }); - return response.HttpStatusCode == HttpStatusCode.OK; + try + { + var response = + await _amazonEC2.DeleteSecurityGroupAsync( + new DeleteSecurityGroupRequest { GroupId = groupId }); + return response.HttpStatusCode == HttpStatusCode.OK; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidGroup.NotFound") + { + _logger.LogError( + $"Security Group {groupId} does not exist and cannot be deleted. Please verify the ID and try again."); + } + + return false; + } + catch (Exception ex) + { + Console.WriteLine($"Couldn't delete the security group because: {ex.Message}"); + return false; + } } // snippet-end:[EC2.dotnetv3.DeleteSecurityGroup] @@ -315,79 +473,57 @@ public void DisplayInstanceInformation(Instance instance) // snippet-start:[EC2.dotnetv3.DescribeInstances] /// - /// Get information about existing EC2 images. - /// - /// Async task. - public async Task DescribeInstances() - { - // List all EC2 instances. - await GetInstanceDescriptions(); - - string tagName = "IncludeInList"; - string tagValue = "Yes"; - await GetInstanceDescriptionsFiltered(tagName, tagValue); - } - - /// - /// Get information for all existing Amazon EC2 instances. - /// - /// Async task. - public async Task GetInstanceDescriptions() - { - Console.WriteLine("Showing all instances:"); - var paginator = _amazonEC2.Paginators.DescribeInstances(new DescribeInstancesRequest()); - - await foreach (var response in paginator.Responses) - { - foreach (var reservation in response.Reservations) - { - foreach (var instance in reservation.Instances) - { - Console.Write($"Instance ID: {instance.InstanceId}"); - Console.WriteLine($"\tCurrent State: {instance.State.Name}"); - } - } - } - } - - /// - /// Get information about EC2 instances filtered by a tag name and value. + /// Get information about EC2 instances with a particular state. /// /// The name of the tag to filter on. /// The value of the tag to look for. - /// Async task. - public async Task GetInstanceDescriptionsFiltered(string tagName, string tagValue) + /// True if successful. + public async Task GetInstancesWithState(string state) { - // This tag filters the results of the instance list. - var filters = new List + try { - new Filter + // Filters the results of the instance list. + var filters = new List { - Name = $"tag:{tagName}", - Values = new List + new Filter { - tagValue, + Name = $"instance-state-name", + Values = new List { state, }, }, - }, - }; - var request = new DescribeInstancesRequest - { - Filters = filters, - }; + }; + var request = new DescribeInstancesRequest { Filters = filters, }; - Console.WriteLine("\nShowing instances with tag: \"IncludeInList\" set to \"Yes\"."); - var paginator = _amazonEC2.Paginators.DescribeInstances(request); + Console.WriteLine($"\nShowing instances with state {state}"); + var paginator = _amazonEC2.Paginators.DescribeInstances(request); - await foreach (var response in paginator.Responses) - { - foreach (var reservation in response.Reservations) + await foreach (var response in paginator.Responses) { - foreach (var instance in reservation.Instances) + foreach (var reservation in response.Reservations) { - Console.Write($"Instance ID: {instance.InstanceId} "); - Console.WriteLine($"\tCurrent State: {instance.State.Name}"); + foreach (var instance in reservation.Instances) + { + Console.Write($"Instance ID: {instance.InstanceId} "); + Console.WriteLine($"\tCurrent State: {instance.State.Name}"); + } } } + + return true; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidParameterValue") + { + _logger.LogError( + $"Invalid parameter value for filtering instances."); + } + + return false; + } + catch (Exception ex) + { + Console.WriteLine($"Couldn't list instances because: {ex.Message}"); + return false; } } // snippet-end:[EC2.dotnetv3.DescribeInstances] @@ -399,35 +535,45 @@ public async Task GetInstanceDescriptionsFiltered(string tagName, string tagValu /// A list of instance type information. public async Task> DescribeInstanceTypes(ArchitectureValues architecture) { - var request = new DescribeInstanceTypesRequest(); + try + { + var request = new DescribeInstanceTypesRequest(); + + var filters = new List + { + new Filter("processor-info.supported-architecture", + new List { architecture.ToString() }) + }; + filters.Add(new Filter("instance-type", new() { "*.micro", "*.small" })); - var filters = new List - { new Filter("processor-info.supported-architecture", new List { architecture.ToString() }) }; - filters.Add(new Filter("instance-type", new() { "*.micro", "*.small" })); + request.Filters = filters; + var instanceTypes = new List(); - request.Filters = filters; - var instanceTypes = new List(); + var paginator = _amazonEC2.Paginators.DescribeInstanceTypes(request); + await foreach (var instanceType in paginator.InstanceTypes) + { + instanceTypes.Add(instanceType); + } - var paginator = _amazonEC2.Paginators.DescribeInstanceTypes(request); - await foreach (var instanceType in paginator.InstanceTypes) - { - instanceTypes.Add(instanceType); + return instanceTypes; } - return instanceTypes; - } - // snippet-end:[EC2.dotnetv3.DescribeInstanceTypes] + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidParameterValue") + { + _logger.LogError( + $"Parameters are invalid. Ensure architecture and size strings conform to DescribeInstanceTypes API reference."); + } - /// - /// Display the instance type information returned by DescribeInstanceTypesAsync. - /// - /// The list of instance type information. - public void DisplayInstanceTypeInfo(List instanceTypes) - { - instanceTypes.ForEach(type => + throw; + } + catch (Exception ex) { - Console.WriteLine($"{type.InstanceType}\t{type.MemoryInfo}"); - }); + Console.WriteLine($"Couldn't delete the security group because: {ex.Message}"); + throw; + } } + // snippet-end:[EC2.dotnetv3.DescribeInstanceTypes] // snippet-start:[EC2.dotnetv3.DescribeKeyPairs] /// @@ -437,34 +583,86 @@ public void DisplayInstanceTypeInfo(List instanceTypes) /// A list of key pair information. public async Task> DescribeKeyPairs(string keyPairName) { - var request = new DescribeKeyPairsRequest(); - if (!string.IsNullOrEmpty(keyPairName)) + try { - request = new DescribeKeyPairsRequest + var request = new DescribeKeyPairsRequest(); + if (!string.IsNullOrEmpty(keyPairName)) { - KeyNames = new List { keyPairName } - }; + request = new DescribeKeyPairsRequest + { + KeyNames = new List { keyPairName } + }; + } + + var response = await _amazonEC2.DescribeKeyPairsAsync(request); + return response.KeyPairs.ToList(); + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidKeyPair.NotFound") + { + _logger.LogError( + $"A key pair called {keyPairName} does not exist."); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while describing the key pair.: {ex.Message}"); + throw; } - var response = await _amazonEC2.DescribeKeyPairsAsync(request); - return response.KeyPairs.ToList(); } // snippet-end:[EC2.dotnetv3.DescribeKeyPairs] // snippet-start:[EC2.dotnetv3.DescribeSecurityGroups] /// - /// Retrieve information for an Amazon EC2 security group. + /// Retrieve information for one or all Amazon EC2 security group. /// - /// The Id of the Amazon EC2 security group. + /// The optional Id of a specific Amazon EC2 security group. /// A list of security group information. public async Task> DescribeSecurityGroups(string groupId) { - var request = new DescribeSecurityGroupsRequest(); - var groupIds = new List { groupId }; - request.GroupIds = groupIds; + try + { + var securityGroups = new List(); + var request = new DescribeSecurityGroupsRequest(); + + if (!string.IsNullOrEmpty(groupId)) + { + var groupIds = new List { groupId }; + request.GroupIds = groupIds; + } + + var paginatorForSecurityGroups = + _amazonEC2.Paginators.DescribeSecurityGroups(request); + + await foreach (var securityGroup in paginatorForSecurityGroups.SecurityGroups) + { + securityGroups.Add(securityGroup); + } - var response = await _amazonEC2.DescribeSecurityGroupsAsync(request); - return response.SecurityGroups; + return securityGroups; + + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidGroup.NotFound") + { + _logger.LogError( + $"A security group {groupId} does not exist."); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while listing security groups. {ex.Message}"); + throw; + } } /// @@ -521,9 +719,28 @@ public void DisplaySecurityGroupInfoAsync(SecurityGroup securityGroup) /// A Boolean value indicating the success of the action. public async Task DisassociateIp(string associationId) { - var response = await _amazonEC2.DisassociateAddressAsync( - new DisassociateAddressRequest { AssociationId = associationId }); - return response.HttpStatusCode == HttpStatusCode.OK; + try + { + var response = await _amazonEC2.DisassociateAddressAsync( + new DisassociateAddressRequest { AssociationId = associationId }); + return response.HttpStatusCode == HttpStatusCode.OK; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidAssociationID.NotFound") + { + _logger.LogError( + $"AssociationId is invalid, unable to disassociate address. {ec2Exception.Message}"); + } + + return false; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while disassociating the Elastic IP.: {ex.Message}"); + return false; + } } // snippet-end:[EC2.dotnetv3.DisassociateAddress] @@ -543,44 +760,77 @@ public async Task> GetEC2AmiList() // snippet-start:[EC2.dotnetv3.RebootInstances] /// - /// Reboot EC2 instances. + /// Reboot a specific EC2 instance. /// - /// The instance Id of the instances that will be rebooted. - /// Async task. - public async Task RebootInstances(string ec2InstanceId) + /// The instance Id of the instance that will be rebooted. + /// Async Task. + public async Task RebootInstances(string ec2InstanceId) { - var request = new RebootInstancesRequest + try { - InstanceIds = new List { ec2InstanceId }, - }; + var request = new RebootInstancesRequest + { + InstanceIds = new List { ec2InstanceId }, + }; + + await _amazonEC2.RebootInstancesAsync(request); - var response = await _amazonEC2.RebootInstancesAsync(request); - if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) + // Wait for the instance to be running. + Console.Write("Waiting for the instance to start."); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Running); + + return true; + } + catch (AmazonEC2Exception ec2Exception) { - Console.WriteLine("Instances successfully rebooted."); + if (ec2Exception.ErrorCode == "InvalidInstanceId") + { + _logger.LogError( + $"InstanceId {ec2InstanceId} is invalid, unable to reboot. {ec2Exception.Message}"); + } + return false; } - else + catch (Exception ex) { - Console.WriteLine("Could not reboot one or more instances."); + _logger.LogError( + $"An error occurred while rebooting the instance {ec2InstanceId}.: {ex.Message}"); + return false; } } // snippet-end:[EC2.dotnetv3.RebootInstances] // snippet-start:[EC2.dotnetv3.ReleaseAddress] /// - /// Release an Elastic IP address. + /// Release an Elastic IP address. After the Elastic IP address is released, + /// it can no longer be used. /// /// The allocation Id of the Elastic IP address. - /// A Boolean value indicating the success of the action. + /// True if successful. public async Task ReleaseAddress(string allocationId) { - var request = new ReleaseAddressRequest + try { - AllocationId = allocationId - }; + var request = new ReleaseAddressRequest { AllocationId = allocationId }; + + var response = await _amazonEC2.ReleaseAddressAsync(request); + return response.HttpStatusCode == HttpStatusCode.OK; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidAllocationID.NotFound") + { + _logger.LogError( + $"AllocationId {allocationId} was not found. {ec2Exception.Message}"); + } - var response = await _amazonEC2.ReleaseAddressAsync(request); - return response.HttpStatusCode == HttpStatusCode.OK; + return false; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while releasing the AllocationId {allocationId}.: {ex.Message}"); + return false; + } } // snippet-end:[EC2.dotnetv3.ReleaseAddress] @@ -598,17 +848,41 @@ public async Task ReleaseAddress(string allocationId) /// The instance Id of the new EC2 instance. public async Task RunInstances(string imageId, string instanceType, string keyName, string groupId) { - var request = new RunInstancesRequest - { - ImageId = imageId, - InstanceType = instanceType, - KeyName = keyName, - MinCount = 1, - MaxCount = 1, - SecurityGroupIds = new List { groupId } - }; - var response = await _amazonEC2.RunInstancesAsync(request); - return response.Reservation.Instances[0].InstanceId; + try + { + var request = new RunInstancesRequest + { + ImageId = imageId, + InstanceType = instanceType, + KeyName = keyName, + MinCount = 1, + MaxCount = 1, + SecurityGroupIds = new List { groupId } + }; + var response = await _amazonEC2.RunInstancesAsync(request); + var instanceId = response.Reservation.Instances[0].InstanceId; + + Console.Write("Waiting for the instance to start."); + await WaitForInstanceState(instanceId, InstanceStateName.Running); + + return instanceId; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidGroupId.NotFound") + { + _logger.LogError( + $"GroupId {groupId} was not found. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while running the instance.: {ex.Message}"); + throw; + } } // snippet-end:[EC2.dotnetv3.RunInstances] @@ -622,20 +896,33 @@ public async Task RunInstances(string imageId, string instanceType, stri /// Async task. public async Task StartInstances(string ec2InstanceId) { - var request = new StartInstancesRequest + try { - InstanceIds = new List { ec2InstanceId }, - }; + var request = new StartInstancesRequest + { + InstanceIds = new List { ec2InstanceId }, + }; - var response = await _amazonEC2.StartInstancesAsync(request); + await _amazonEC2.StartInstancesAsync(request); - if (response.StartingInstances.Count > 0) + Console.Write("Waiting for instance to start. "); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Running); + } + catch (AmazonEC2Exception ec2Exception) { - var instances = response.StartingInstances; - instances.ForEach(i => + if (ec2Exception.ErrorCode == "InvalidInstanceId") { - Console.WriteLine($"Successfully started the EC2 instance with instance ID: {i.InstanceId}."); - }); + _logger.LogError( + $"InstanceId is invalid, unable to start. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while starting the instance.: {ex.Message}"); + throw; } } // snippet-end:[EC2.dotnetv3.StartInstances] @@ -649,30 +936,34 @@ public async Task StartInstances(string ec2InstanceId) /// Async task. public async Task StopInstances(string ec2InstanceId) { - // In addition to the list of instance Ids, the - // request can also include the following properties: - // Force When true, forces the instances to - // stop but you must check the integrity - // of the file system. Not recommended on - // Windows instances. - // Hibernate When true, hibernates the instance if the - // instance was enabled for hibernation when - // it was launched. - var request = new StopInstancesRequest - { - InstanceIds = new List { ec2InstanceId }, - }; + try + { + var request = new StopInstancesRequest + { + InstanceIds = new List { ec2InstanceId }, + }; - var response = await _amazonEC2.StopInstancesAsync(request); + await _amazonEC2.StopInstancesAsync(request); + Console.Write("Waiting for the instance to stop."); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Stopped); - if (response.StoppingInstances.Count > 0) + Console.WriteLine("\nThe instance has stopped."); + } + catch (AmazonEC2Exception ec2Exception) { - var instances = response.StoppingInstances; - instances.ForEach(i => + if (ec2Exception.ErrorCode == "InvalidInstanceId") { - Console.WriteLine($"Successfully stopped the EC2 Instance " + - $"with InstanceID: {i.InstanceId}."); - }); + _logger.LogError( + $"InstanceId is invalid, unable to stop. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while stopping the instance.: {ex.Message}"); + throw; } } // snippet-end:[EC2.dotnetv3.StopInstances] @@ -686,13 +977,36 @@ public async Task StopInstances(string ec2InstanceId) /// Async task. public async Task> TerminateInstances(string ec2InstanceId) { - var request = new TerminateInstancesRequest + try { - InstanceIds = new List { ec2InstanceId } - }; + var request = new TerminateInstancesRequest + { + InstanceIds = new List { ec2InstanceId } + }; - var response = await _amazonEC2.TerminateInstancesAsync(request); - return response.TerminatingInstances; + var response = await _amazonEC2.TerminateInstancesAsync(request); + Console.Write("Waiting for the instance to terminate."); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Terminated); + + Console.WriteLine($"\nThe instance {ec2InstanceId} has been terminated."); + return response.TerminatingInstances; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceId") + { + _logger.LogError( + $"InstanceId is invalid, unable to terminate. {ec2Exception.Message}"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while terminating the instance.: {ex.Message}"); + throw; + } } // snippet-end:[EC2.dotnetv3.TerminateInstances] @@ -710,7 +1024,7 @@ public async Task WaitForInstanceState(string instanceId, InstanceStateNam InstanceIds = new List { instanceId } }; - // Wait until the instance is running. + // Wait until the instance is in the specified state. var hasState = false; do { diff --git a/dotnetv3/EC2/Actions/HelloEC2.cs b/dotnetv3/EC2/Actions/HelloEC2.cs index 3c92a63d1d6..16a9f232d21 100644 --- a/dotnetv3/EC2/Actions/HelloEC2.cs +++ b/dotnetv3/EC2/Actions/HelloEC2.cs @@ -11,7 +11,7 @@ public class HelloEc2 /// HelloEc2 lists the existing security groups for the default users. /// /// Command line arguments - /// A Task object. + /// Async task. static async Task Main(string[] args) { // Set up dependency injection for Amazon Elastic Compute Cloud (Amazon EC2). @@ -25,22 +25,38 @@ static async Task Main(string[] args) // Now the client is available for injection. var ec2Client = host.Services.GetRequiredService(); - var request = new DescribeSecurityGroupsRequest + try { - MaxResults = 10, - }; - - - // Retrieve information about up to 10 Amazon EC2 security groups. - var response = await ec2Client.DescribeSecurityGroupsAsync(request); - - // Now print the security groups returned by the call to - // DescribeSecurityGroupsAsync. - Console.WriteLine("Security Groups:"); - response.SecurityGroups.ForEach(group => + // Retrieve information for up to 10 Amazon EC2 security groups. + var request = new DescribeSecurityGroupsRequest { MaxResults = 10, }; + var securityGroups = new List(); + + var paginatorForSecurityGroups = + ec2Client.Paginators.DescribeSecurityGroups(request); + + await foreach (var securityGroup in paginatorForSecurityGroups.SecurityGroups) + { + securityGroups.Add(securityGroup); + } + + // Now print the security groups returned by the call to + // DescribeSecurityGroupsAsync. + Console.WriteLine("Welcome to the EC2 Hello Service example. " + + "\nLet's list your Security Groups:"); + securityGroups.ForEach(group => + { + Console.WriteLine( + $"Security group: {group.GroupName} ID: {group.GroupId}"); + }); + } + catch (AmazonEC2Exception ex) + { + Console.WriteLine($"An Amazon EC2 service error occurred while listing security groups. {ex.Message}"); + } + catch (Exception ex) { - Console.WriteLine($"Security group: {group.GroupName} ID: {group.GroupId}"); - }); + Console.WriteLine($"An error occurred while listing security groups. {ex.Message}"); + } } } // snippet-end:[EC2.dotnetv3.HelloEc2] \ No newline at end of file diff --git a/dotnetv3/EC2/EC2Examples.sln b/dotnetv3/EC2/EC2Examples.sln index 998bb586838..aee7d5c12ce 100644 --- a/dotnetv3/EC2/EC2Examples.sln +++ b/dotnetv3/EC2/EC2Examples.sln @@ -5,13 +5,17 @@ VisualStudioVersion = 17.2.32630.192 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC2Actions", "Actions\EC2Actions.csproj", "{796910FA-6E94-460B-8CB4-97DF01B9ADC8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ec2_Basics", "Scenarios\EC2_Basics\Ec2_Basics.csproj", "{B1731AE1-381F-4044-BEBE-269FF7E24B1F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Basics", "Scenarios\EC2_Basics\Basics.csproj", "{B1731AE1-381F-4044-BEBE-269FF7E24B1F}" ProjectSection(ProjectDependencies) = postProject {796910FA-6E94-460B-8CB4-97DF01B9ADC8} = {796910FA-6E94-460B-8CB4-97DF01B9ADC8} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC2Tests", "Tests\EC2Tests.csproj", "{6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCExample", "Scenarios\CreateVPCExample\CreateVPCExample.csproj", "{A0B25DB2-E0BE-409F-950D-19E23FB76319}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCforS3Example", "Scenarios\CreateVPCforS3Example\CreateVPCforS3Example.csproj", "{CB5A94D4-1BA1-41CE-B5AE-7CE906B4CA7D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,6 +34,14 @@ Global {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Debug|Any CPU.Build.0 = Debug|Any CPU {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Release|Any CPU.ActiveCfg = Release|Any CPU {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Release|Any CPU.Build.0 = Release|Any CPU + {A0B25DB2-E0BE-409F-950D-19E23FB76319}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0B25DB2-E0BE-409F-950D-19E23FB76319}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0B25DB2-E0BE-409F-950D-19E23FB76319}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0B25DB2-E0BE-409F-950D-19E23FB76319}.Release|Any CPU.Build.0 = Release|Any CPU + {CB5A94D4-1BA1-41CE-B5AE-7CE906B4CA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB5A94D4-1BA1-41CE-B5AE-7CE906B4CA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB5A94D4-1BA1-41CE-B5AE-7CE906B4CA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB5A94D4-1BA1-41CE-B5AE-7CE906B4CA7D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dotnetv3/EC2/README.md b/dotnetv3/EC2/README.md index 8dc39763da6..06f267e087e 100644 --- a/dotnetv3/EC2/README.md +++ b/dotnetv3/EC2/README.md @@ -45,31 +45,31 @@ Code examples that show you how to perform the essential operations within a ser Code excerpts that show you how to call individual service functions. -- [AllocateAddress](Actions/EC2Wrapper.cs#L19) -- [AssociateAddress](Actions/EC2Wrapper.cs#L33) -- [AuthorizeSecurityGroupIngress](Actions/EC2Wrapper.cs#L55) -- [CreateKeyPair](Actions/EC2Wrapper.cs#L98) -- [CreateLaunchTemplate](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L260) -- [CreateSecurityGroup](Actions/EC2Wrapper.cs#L145) -- [DeleteKeyPair](Actions/EC2Wrapper.cs#L190) -- [DeleteLaunchTemplate](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L402) -- [DeleteSecurityGroup](Actions/EC2Wrapper.cs#L223) -- [DescribeAvailabilityZones](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L304) -- [DescribeIamInstanceProfileAssociations](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L495) -- [DescribeInstanceTypes](Actions/EC2Wrapper.cs#L395) -- [DescribeInstances](Actions/EC2Wrapper.cs#L316) -- [DescribeKeyPairs](Actions/EC2Wrapper.cs#L432) -- [DescribeSecurityGroups](Actions/EC2Wrapper.cs#L454) -- [DescribeSubnets](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L371) -- [DescribeVpcs](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L352) -- [DisassociateAddress](Actions/EC2Wrapper.cs#L516) -- [RebootInstances](Actions/EC2Wrapper.cs#L544) -- [ReleaseAddress](Actions/EC2Wrapper.cs#L569) -- [ReplaceIamInstanceProfileAssociation](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L515) -- [RunInstances](Actions/EC2Wrapper.cs#L587) -- [StartInstances](Actions/EC2Wrapper.cs#L616) -- [StopInstances](Actions/EC2Wrapper.cs#L643) -- [TerminateInstances](Actions/EC2Wrapper.cs#L680) +- [AllocateAddress](Actions/EC2Wrapper.cs#L28) +- [AssociateAddress](Actions/EC2Wrapper.cs#L64) +- [AuthorizeSecurityGroupIngress](Actions/EC2Wrapper.cs#L107) +- [CreateKeyPair](Actions/EC2Wrapper.cs#L170) +- [CreateLaunchTemplate](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L263) +- [CreateSecurityGroup](Actions/EC2Wrapper.cs#L242) +- [DeleteKeyPair](Actions/EC2Wrapper.cs#L319) +- [DeleteLaunchTemplate](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L470) +- [DeleteSecurityGroup](Actions/EC2Wrapper.cs#L361) +- [DescribeAvailabilityZones](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L325) +- [DescribeIamInstanceProfileAssociations](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L574) +- [DescribeInstanceTypes](Actions/EC2Wrapper.cs#L531) +- [DescribeInstances](Actions/EC2Wrapper.cs#L474) +- [DescribeKeyPairs](Actions/EC2Wrapper.cs#L578) +- [DescribeSecurityGroups](Actions/EC2Wrapper.cs#L620) +- [DescribeSubnets](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L422) +- [DescribeVpcs](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L386) +- [DisassociateAddress](Actions/EC2Wrapper.cs#L714) +- [RebootInstances](Actions/EC2Wrapper.cs#L761) +- [ReleaseAddress](Actions/EC2Wrapper.cs#L802) +- [ReplaceIamInstanceProfileAssociation](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L611) +- [RunInstances](Actions/EC2Wrapper.cs#L837) +- [StartInstances](Actions/EC2Wrapper.cs#L890) +- [StopInstances](Actions/EC2Wrapper.cs#L930) +- [TerminateInstances](Actions/EC2Wrapper.cs#L971) ### Scenarios diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample/CreateVPC.cs b/dotnetv3/EC2/Scenarios/CreateVPCExample/CreateVPC.cs similarity index 100% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample/CreateVPC.cs rename to dotnetv3/EC2/Scenarios/CreateVPCExample/CreateVPC.cs diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample/CreateVPCExample.csproj b/dotnetv3/EC2/Scenarios/CreateVPCExample/CreateVPCExample.csproj similarity index 100% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample/CreateVPCExample.csproj rename to dotnetv3/EC2/Scenarios/CreateVPCExample/CreateVPCExample.csproj diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/CreateVPCforS3.cs b/dotnetv3/EC2/Scenarios/CreateVPCforS3Example/CreateVPCforS3.cs similarity index 100% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/CreateVPCforS3.cs rename to dotnetv3/EC2/Scenarios/CreateVPCforS3Example/CreateVPCforS3.cs diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/CreateVPCforS3Example.csproj b/dotnetv3/EC2/Scenarios/CreateVPCforS3Example/CreateVPCforS3Example.csproj similarity index 100% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/CreateVPCforS3Example.csproj rename to dotnetv3/EC2/Scenarios/CreateVPCforS3Example/CreateVPCforS3Example.csproj diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/appsettings.json b/dotnetv3/EC2/Scenarios/CreateVPCforS3Example/appsettings.json similarity index 100% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCforS3Example/appsettings.json rename to dotnetv3/EC2/Scenarios/CreateVPCforS3Example/appsettings.json diff --git a/dotnetv3/EC2/Scenarios/EC2_Basics/Ec2_Basics.csproj b/dotnetv3/EC2/Scenarios/EC2_Basics/Basics.csproj similarity index 100% rename from dotnetv3/EC2/Scenarios/EC2_Basics/Ec2_Basics.csproj rename to dotnetv3/EC2/Scenarios/EC2_Basics/Basics.csproj diff --git a/dotnetv3/EC2/Scenarios/EC2_Basics/EC2Basics.cs b/dotnetv3/EC2/Scenarios/EC2_Basics/EC2Basics.cs index eae5ceb9b5e..c4e9e2af93d 100644 --- a/dotnetv3/EC2/Scenarios/EC2_Basics/EC2Basics.cs +++ b/dotnetv3/EC2/Scenarios/EC2_Basics/EC2Basics.cs @@ -1,7 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -namespace Ec2_Basics; +using Ec2_Basics; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Basics; // snippet-start:[EC2.dotnetv3.Main] /// @@ -9,15 +13,29 @@ namespace Ec2_Basics; /// public class EC2Basics { + public static ILogger _logger = null!; + public static EC2Wrapper _ec2Wrapper = null!; + public static SsmWrapper _ssmWrapper = null!; + public static UiMethods _uiMethods = null!; + + public static string associationId = null!; + public static string allocationId = null!; + public static string instanceId = null!; + public static string keyPairName = null!; + public static string groupName = null!; + public static string tempFileName = null!; + public static string secGroupId = null!; + public static bool isInteractive = true; + /// /// Perform the actions defined for the Amazon EC2 Basics scenario. /// /// Command line arguments. /// A Task object. - static async Task Main(string[] args) + public static async Task Main(string[] args) { // Set up dependency injection for Amazon EC2 and Amazon Simple Systems - // Management Service. + // Management (Amazon SSM) Service. using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureServices((_, services) => services.AddAWSService() @@ -27,288 +45,282 @@ static async Task Main(string[] args) ) .Build(); - // Now the client is available for injection. - var ec2Client = host.Services.GetRequiredService(); - var ec2Methods = new EC2Wrapper(ec2Client); - - var ssmClient = host.Services.GetRequiredService(); - var ssmMethods = new SsmWrapper(ssmClient); - var uiMethods = new UiMethods(); + SetUpServices(host); var uniqueName = Guid.NewGuid().ToString(); - var keyPairName = "mvp-example-key-pair" + uniqueName; - var groupName = "ec2-scenario-group" + uniqueName; + keyPairName = "mvp-example-key-pair" + uniqueName; + groupName = "ec2-scenario-group" + uniqueName; var groupDescription = "A security group created for the EC2 Basics scenario."; - // Start the scenario. - uiMethods.DisplayOverview(); - uiMethods.PressEnter(); - - // Create the key pair. - uiMethods.DisplayTitle("Create RSA key pair"); - Console.Write("Let's create an RSA key pair that you can be use to "); - Console.WriteLine("securely connect to your EC2 instance."); - var keyPair = await ec2Methods.CreateKeyPair(keyPairName); - - // Save key pair information to a temporary file. - var tempFileName = ec2Methods.SaveKeyPair(keyPair); - - Console.WriteLine($"Created the key pair: {keyPair.KeyName} and saved it to: {tempFileName}"); - string? answer; - do - { - Console.Write("Would you like to list your existing key pairs? "); - answer = Console.ReadLine(); - } while (answer!.ToLower() != "y" && answer.ToLower() != "n"); - - if (answer == "y") + try { - // List existing key pairs. - uiMethods.DisplayTitle("Existing key pairs"); - - // Passing an empty string to the DescribeKeyPairs method will return - // a list of all existing key pairs. - var keyPairs = await ec2Methods.DescribeKeyPairs(""); - keyPairs.ForEach(kp => + // Start the scenario. + _uiMethods.DisplayOverview(); + _uiMethods.PressEnter(isInteractive); + + // Create the key pair. + _uiMethods.DisplayTitle("Create RSA key pair"); + Console.Write("Let's create an RSA key pair that you can be use to "); + Console.WriteLine("securely connect to your EC2 instance."); + var keyPair = await _ec2Wrapper.CreateKeyPair(keyPairName); + + // Save key pair information to a temporary file. + tempFileName = _ec2Wrapper.SaveKeyPair(keyPair); + + Console.WriteLine( + $"Created the key pair: {keyPair.KeyName} and saved it to: {tempFileName}"); + string? answer = ""; + if (isInteractive) + { + do + { + Console.Write("Would you like to list your existing key pairs? "); + answer = Console.ReadLine(); + } while (answer!.ToLower() != "y" && answer.ToLower() != "n"); + } + + if (!isInteractive || answer == "y") + { + // List existing key pairs. + _uiMethods.DisplayTitle("Existing key pairs"); + + // Passing an empty string to the DescribeKeyPairs method will return + // a list of all existing key pairs. + var keyPairs = await _ec2Wrapper.DescribeKeyPairs(""); + keyPairs.ForEach(kp => + { + Console.WriteLine( + $"{kp.KeyName} created at: {kp.CreateTime} Fingerprint: {kp.KeyFingerprint}"); + }); + } + + _uiMethods.PressEnter(isInteractive); + + // Create the security group. + Console.WriteLine( + "Let's create a security group to manage access to your instance."); + secGroupId = await _ec2Wrapper.CreateSecurityGroup(groupName, groupDescription); + Console.WriteLine( + "Let's add rules to allow all HTTP and HTTPS inbound traffic and to allow SSH only from your current IP address."); + + _uiMethods.DisplayTitle("Security group information"); + var secGroups = await _ec2Wrapper.DescribeSecurityGroups(secGroupId); + + Console.WriteLine($"Created security group {groupName} in your default VPC."); + secGroups.ForEach(group => { - Console.WriteLine($"{kp.KeyName} created at: {kp.CreateTime} Fingerprint: {kp.KeyFingerprint}"); + _ec2Wrapper.DisplaySecurityGroupInfoAsync(group); }); - } - uiMethods.PressEnter(); - - // Create the security group. - Console.WriteLine("Let's create a security group to manage access to your instance."); - var secGroupId = await ec2Methods.CreateSecurityGroup(groupName, groupDescription); - Console.WriteLine("Let's add rules to allow all HTTP and HTTPS inbound traffic and to allow SSH only from your current IP address."); - - uiMethods.DisplayTitle("Security group information"); - var secGroups = await ec2Methods.DescribeSecurityGroups(secGroupId); - - Console.WriteLine($"Created security group {groupName} in your default VPC."); - secGroups.ForEach(group => - { - ec2Methods.DisplaySecurityGroupInfoAsync(group); - }); - uiMethods.PressEnter(); + _uiMethods.PressEnter(isInteractive); - Console.WriteLine("Now we'll authorize the security group we just created so that it can"); - Console.WriteLine("access the EC2 instances you create."); - var success = await ec2Methods.AuthorizeSecurityGroupIngress(groupName); + Console.WriteLine( + "Now we'll authorize the security group we just created so that it can"); + Console.WriteLine("access the EC2 instances you create."); + await _ec2Wrapper.AuthorizeSecurityGroupIngress(groupName); - secGroups = await ec2Methods.DescribeSecurityGroups(secGroupId); - Console.WriteLine($"Now let's look at the permissions again."); - secGroups.ForEach(group => - { - ec2Methods.DisplaySecurityGroupInfoAsync(group); - }); - uiMethods.PressEnter(); - - // Get list of available Amazon Linux 2 Amazon Machine Images (AMIs). - var parameters = await ssmMethods.GetParametersByPath("/aws/service/ami-amazon-linux-latest"); - - List imageIds = parameters.Select(param => param.Value).ToList(); - - var images = await ec2Methods.DescribeImages(imageIds); - - var i = 1; - images.ForEach(image => - { - Console.WriteLine($"\t{i++}\t{image.Description}"); - }); + secGroups = await _ec2Wrapper.DescribeSecurityGroups(secGroupId); + Console.WriteLine($"Now let's look at the permissions again."); + secGroups.ForEach(group => + { + _ec2Wrapper.DisplaySecurityGroupInfoAsync(group); + }); + _uiMethods.PressEnter(isInteractive); - int choice; - bool validNumber = false; + // Get list of available Amazon Linux 2 Amazon Machine Images (AMIs). + var parameters = + await _ssmWrapper.GetParametersByPath( + "/aws/service/ami-amazon-linux-latest"); - do - { - Console.Write("Please select an image: "); - var selImage = Console.ReadLine(); - validNumber = int.TryParse(selImage, out choice); - } while (!validNumber); + List imageIds = parameters.Select(param => param.Value).ToList(); - var selectedImage = images[choice - 1]; + var images = await _ec2Wrapper.DescribeImages(imageIds); - // Display available instance types. - uiMethods.DisplayTitle("Instance Types"); - var instanceTypes = await ec2Methods.DescribeInstanceTypes(selectedImage.Architecture); + var i = 1; + images.ForEach(image => + { + Console.WriteLine($"\t{i++}\t{image.Description}"); + }); - i = 1; - instanceTypes.ForEach(instanceType => - { - Console.WriteLine($"\t{i++}\t{instanceType.InstanceType}"); - }); + int choice = 1; + bool validNumber = false; + if (isInteractive) + { + do + { + Console.Write("Please select an image: "); + var selImage = Console.ReadLine(); + validNumber = int.TryParse(selImage, out choice); + } while (!validNumber); + } + + var selectedImage = images[choice - 1]; + + // Display available instance types. + _uiMethods.DisplayTitle("Instance Types"); + var instanceTypes = + await _ec2Wrapper.DescribeInstanceTypes(selectedImage.Architecture); + + i = 1; + instanceTypes.ForEach(instanceType => + { + Console.WriteLine($"\t{i++}\t{instanceType.InstanceType}"); + }); + if (isInteractive) + { + do + { + Console.Write("Please select an instance type: "); + var selImage = Console.ReadLine(); + validNumber = int.TryParse(selImage, out choice); + } while (!validNumber); + } - do - { - Console.Write("Please select an instance type: "); - var selImage = Console.ReadLine(); - validNumber = int.TryParse(selImage, out choice); - } while (!validNumber); - - var selectedInstanceType = instanceTypes[choice - 1].InstanceType; - - // Create an EC2 instance. - uiMethods.DisplayTitle("Creating an EC2 Instance"); - var instanceId = await ec2Methods.RunInstances(selectedImage.ImageId, selectedInstanceType, keyPairName, secGroupId); - Console.Write("Waiting for the instance to start."); - var isRunning = false; - do - { - isRunning = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Running); - } while (!isRunning); + var selectedInstanceType = instanceTypes[choice - 1].InstanceType; - uiMethods.PressEnter(); + // Create an EC2 instance. + _uiMethods.DisplayTitle("Creating an EC2 Instance"); + instanceId = await _ec2Wrapper.RunInstances(selectedImage.ImageId, + selectedInstanceType, keyPairName, secGroupId); - var instance = await ec2Methods.DescribeInstance(instanceId); - uiMethods.DisplayTitle("New Instance Information"); - ec2Methods.DisplayInstanceInformation(instance); + _uiMethods.PressEnter(isInteractive); - Console.WriteLine("\nYou can use SSH to connect to your instance. For example:"); - Console.WriteLine($"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + var instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("New Instance Information"); + _ec2Wrapper.DisplayInstanceInformation(instance); - uiMethods.PressEnter(); + Console.WriteLine( + "\nYou can use SSH to connect to your instance. For example:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); - Console.WriteLine("Now we'll stop the instance and then start it again to see what's changed."); + _uiMethods.PressEnter(isInteractive); - await ec2Methods.StopInstances(instanceId); - var hasStopped = false; - do - { - hasStopped = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Stopped); - } while (!hasStopped); + Console.WriteLine( + "Now we'll stop the instance and then start it again to see what's changed."); - Console.WriteLine("\nThe instance has stopped."); + await _ec2Wrapper.StopInstances(instanceId); - Console.WriteLine("Now let's start it up again."); - await ec2Methods.StartInstances(instanceId); - Console.Write("Waiting for instance to start. "); + Console.WriteLine("Now let's start it up again."); + await _ec2Wrapper.StartInstances(instanceId); - isRunning = false; - do - { - isRunning = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Running); - } while (!isRunning); + Console.WriteLine("\nLet's see what changed."); - Console.WriteLine("\nLet's see what changed."); + instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("New Instance Information"); + _ec2Wrapper.DisplayInstanceInformation(instance); - instance = await ec2Methods.DescribeInstance(instanceId); - uiMethods.DisplayTitle("New Instance Information"); - ec2Methods.DisplayInstanceInformation(instance); + Console.WriteLine("\nNotice the change in the SSH information:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); - Console.WriteLine("\nNotice the change in the SSH information:"); - Console.WriteLine($"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + _uiMethods.PressEnter(isInteractive); - uiMethods.PressEnter(); + Console.WriteLine( + "Now we will stop the instance again. Then we will create and associate an"); + Console.WriteLine("Elastic IP address to use with our instance."); - Console.WriteLine("Now we will stop the instance again. Then we will create and associate an"); - Console.WriteLine("Elastic IP address to use with our instance."); + await _ec2Wrapper.StopInstances(instanceId); + _uiMethods.PressEnter(isInteractive); - await ec2Methods.StopInstances(instanceId); - hasStopped = false; - do - { - hasStopped = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Stopped); - } while (!hasStopped); + _uiMethods.DisplayTitle("Allocate Elastic IP address"); + Console.WriteLine( + "You can allocate an Elastic IP address and associate it with your instance\nto keep a consistent IP address even when your instance restarts."); + var allocationResponse = await _ec2Wrapper.AllocateAddress(); + allocationId = allocationResponse.AllocationId; + Console.WriteLine( + "Now we will associate the Elastic IP address with our instance."); + associationId = await _ec2Wrapper.AssociateAddress(allocationId, instanceId); - Console.WriteLine("\nThe instance has stopped."); - uiMethods.PressEnter(); + // Start the instance again. + Console.WriteLine("Now let's start the instance again."); + await _ec2Wrapper.StartInstances(instanceId); - uiMethods.DisplayTitle("Allocate Elastic IP address"); - Console.WriteLine("You can allocate an Elastic IP address and associate it with your instance\nto keep a consistent IP address even when your instance restarts."); - var allocationId = await ec2Methods.AllocateAddress(); - Console.WriteLine("Now we will associate the Elastic IP address with our instance."); - var associationId = await ec2Methods.AssociateAddress(allocationId, instanceId); + Console.WriteLine("\nLet's see what changed."); - // Start the instance again. - Console.WriteLine("Now let's start the instance again."); - await ec2Methods.StartInstances(instanceId); - Console.Write("Waiting for instance to start. "); + instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("Instance information"); + _ec2Wrapper.DisplayInstanceInformation(instance); - isRunning = false; - do - { - isRunning = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Running); - } while (!isRunning); + Console.WriteLine("\nHere is the SSH information:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); - Console.WriteLine("\nLet's see what changed."); + Console.WriteLine("Let's stop and start the instance again."); + _uiMethods.PressEnter(isInteractive); - instance = await ec2Methods.DescribeInstance(instanceId); - uiMethods.DisplayTitle("Instance information"); - ec2Methods.DisplayInstanceInformation(instance); + await _ec2Wrapper.StopInstances(instanceId); - Console.WriteLine("\nHere is the SSH information:"); - Console.WriteLine($"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + Console.WriteLine("\nThe instance has stopped."); - Console.WriteLine("Let's stop and start the instance again."); - uiMethods.PressEnter(); + Console.WriteLine("Now let's start it up again."); + await _ec2Wrapper.StartInstances(instanceId); - await ec2Methods.StopInstances(instanceId); + instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("New Instance Information"); + _ec2Wrapper.DisplayInstanceInformation(instance); + Console.WriteLine("Note that the IP address did not change this time."); + _uiMethods.PressEnter(isInteractive); - hasStopped = false; - do + await Cleanup(); + } + catch (Exception ex) { - hasStopped = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Stopped); - } while (!hasStopped); - - Console.WriteLine("\nThe instance has stopped."); + _logger.LogError(ex, "There was a problem with the scenario, starting cleanup."); + await Cleanup(); + } - Console.WriteLine("Now let's start it up again."); - await ec2Methods.StartInstances(instanceId); - Console.Write("Waiting for instance to start. "); + _uiMethods.DisplayTitle("EC2 Basics Scenario completed."); + _uiMethods.PressEnter(isInteractive); + } - isRunning = false; - do + /// + /// Set up the services and logging. + /// + /// + public static void SetUpServices(IHost host) + { + var loggerFactory = LoggerFactory.Create(builder => { - isRunning = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Running); - } while (!isRunning); - - instance = await ec2Methods.DescribeInstance(instanceId); - uiMethods.DisplayTitle("New Instance Information"); - ec2Methods.DisplayInstanceInformation(instance); - Console.WriteLine("Note that the IP address did not change this time."); - uiMethods.PressEnter(); + builder.AddConsole(); + }); + _logger = new Logger(loggerFactory); - uiMethods.DisplayTitle("Clean up resources"); + // Now the client is available for injection. + _ec2Wrapper = host.Services.GetRequiredService(); + _ssmWrapper = host.Services.GetRequiredService(); + _uiMethods = new UiMethods(); + } + /// + /// Clean up any resources from the scenario. + /// + /// + public static async Task Cleanup() + { + _uiMethods.DisplayTitle("Clean up resources"); Console.WriteLine("Now let's clean up the resources we created."); - // Terminate the instance. - Console.WriteLine("Terminating the instance we created."); - var stateChange = await ec2Methods.TerminateInstances(instanceId); - - // Wait for the instance state to be terminated. - var hasTerminated = false; - do - { - hasTerminated = await ec2Methods.WaitForInstanceState(instanceId, InstanceStateName.Terminated); - } while (!hasTerminated); - - Console.WriteLine($"\nThe instance {instanceId} has been terminated."); - Console.WriteLine("Now we can disassociate the Elastic IP address and release it."); - + Console.WriteLine("Disassociate the Elastic IP address and release it."); // Disassociate the Elastic IP address. - var disassociated = ec2Methods.DisassociateIp(associationId); + await _ec2Wrapper.DisassociateIp(associationId); // Delete the Elastic IP address. - var released = ec2Methods.ReleaseAddress(allocationId); + await _ec2Wrapper.ReleaseAddress(allocationId); + + // Terminate the instance. + Console.WriteLine("Terminating the instance we created."); + await _ec2Wrapper.TerminateInstances(instanceId); // Delete the security group. Console.WriteLine($"Deleting the Security Group: {groupName}."); - success = await ec2Methods.DeleteSecurityGroup(secGroupId); - if (success) - { - Console.WriteLine($"Successfully deleted {groupName}."); - } + await _ec2Wrapper.DeleteSecurityGroup(secGroupId); // Delete the RSA key pair. Console.WriteLine($"Deleting the key pair: {keyPairName}"); - await ec2Methods.DeleteKeyPair(keyPairName); + await _ec2Wrapper.DeleteKeyPair(keyPairName); Console.WriteLine("Deleting the temporary file with the key information."); - ec2Methods.DeleteTempFile(tempFileName); - uiMethods.PressEnter(); - - uiMethods.DisplayTitle("EC2 Basics Scenario completed."); - uiMethods.PressEnter(); + _ec2Wrapper.DeleteTempFile(tempFileName); + _uiMethods.PressEnter(isInteractive); } } // snippet-end:[EC2.dotnetv3.Main] \ No newline at end of file diff --git a/dotnetv3/EC2/Scenarios/EC2_Basics/UIMethods.cs b/dotnetv3/EC2/Scenarios/EC2_Basics/UIMethods.cs index cdfdd3c9d49..2c3d7f7f882 100644 --- a/dotnetv3/EC2/Scenarios/EC2_Basics/UIMethods.cs +++ b/dotnetv3/EC2/Scenarios/EC2_Basics/UIMethods.cs @@ -5,14 +5,13 @@ namespace Ec2_Basics; public class UiMethods { - public readonly string SepBar = new string('-', Console.WindowWidth); + public readonly string SepBar = new string('-', 88); /// /// Show information about the scenario. /// public void DisplayOverview() { - Console.Clear(); DisplayTitle("Welcome to the Amazon Elastic Compute Cloud (Amazon EC2) get started with instances demo."); Console.WriteLine("This example application does the following:"); @@ -39,33 +38,22 @@ public void DisplayOverview() /// /// Display a message and wait until the user presses enter. /// - public void PressEnter() + public void PressEnter(bool interactive) { Console.Write("\nPlease press to continue. "); - _ = Console.ReadLine(); + if (interactive) + _ = Console.ReadLine(); } /// - /// Pad a string with spaces to center it on the console display. - /// - /// - /// - public string CenterString(string strToCenter) - { - var padAmount = (Console.WindowWidth - strToCenter.Length) / 2; - var leftPad = new string(' ', padAmount); - return $"{leftPad}{strToCenter}"; - } - - /// - /// Display a line of hyphens, the centered text of the title and another + /// Display a line of hyphens, the text of the title and another /// line of hyphens. /// /// The string to be displayed. public void DisplayTitle(string strTitle) { Console.WriteLine(SepBar); - Console.WriteLine(CenterString(strTitle)); + Console.WriteLine(strTitle); Console.WriteLine(SepBar); } } \ No newline at end of file diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCforS3Tests.cs b/dotnetv3/EC2/Tests/CreateVPCforS3Tests.cs similarity index 94% rename from dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCforS3Tests.cs rename to dotnetv3/EC2/Tests/CreateVPCforS3Tests.cs index 71b15c02b88..a3b9a2c5002 100644 --- a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCforS3Tests.cs +++ b/dotnetv3/EC2/Tests/CreateVPCforS3Tests.cs @@ -1,10 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -using Amazon.EC2; using CreateVPCforS3Example; -using Microsoft.Extensions.Configuration; -using Xunit.Extensions.Ordering; namespace CreateVPCTests; diff --git a/dotnetv3/EC2/Tests/EC2Tests.csproj b/dotnetv3/EC2/Tests/EC2Tests.csproj index cb6507fc23f..9e64ec50fc5 100644 --- a/dotnetv3/EC2/Tests/EC2Tests.csproj +++ b/dotnetv3/EC2/Tests/EC2Tests.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -9,9 +9,10 @@ - + + @@ -36,7 +37,8 @@ - + + diff --git a/dotnetv3/EC2/Tests/EC2WrapperTests.cs b/dotnetv3/EC2/Tests/EC2WrapperTests.cs index 346940e0046..6e69a53ded6 100644 --- a/dotnetv3/EC2/Tests/EC2WrapperTests.cs +++ b/dotnetv3/EC2/Tests/EC2WrapperTests.cs @@ -1,6 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using Basics; +using Microsoft.Extensions.Logging; +using Moq; + namespace EC2Tests; /// /// Integration tests for the Amazon Elastic Compute Cloud (Amazon EC2) @@ -9,403 +13,69 @@ namespace EC2Tests; public class EC2WrapperTests { private readonly IConfiguration _configuration; - private readonly AmazonEC2Client _client; - private readonly EC2Wrapper _ec2Wrapper; - private readonly SsmWrapper _ssmWrapper; - - private readonly string _ciderBlock = null!; - private readonly string _groupName; - private readonly string _groupDescription; - private readonly string _keyPairName; - - private static string? _allocationId; - private static ArchitectureValues? _architecture; - private static string? _associationId; - private static string? _ec2InstanceId; - private static List? _images; - private static string? _imageId; - private static List? _instanceTypes; - private static string? _instanceType; - private static KeyPair? _keyPair; - private static string? _secGroupId; - private static string? _tempFileName; - - /// - /// The test class constructor. - /// - public EC2WrapperTests() - { - _configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("testsettings.json") // Load test settings from .json file. - .AddJsonFile("testsettings.local.json", - true) // Optionally load local settings. - .Build(); - - _ciderBlock = _configuration["CidrBlock"]; - _groupName = _configuration["GroupName"]!; - _groupDescription = _configuration["GroupDescription"]!; - _instanceType = _configuration["InstanceType"]; - _keyPairName = _configuration["KeyPairName"]!; - - _client = new AmazonEC2Client(); - - _ec2Wrapper = new EC2Wrapper(_client); - _ssmWrapper = new SsmWrapper(new AmazonSimpleSystemsManagementClient()); - - } + private AmazonEC2Client _client; + private EC2Wrapper _ec2Wrapper; + private SsmWrapper _ssmWrapper; /// - /// Test the creation of an Amazon EC2 key pair. - /// - /// - [Fact()] - [Order(1)] - [Trait("Category", "Integration")] - public async Task CreateKeyPairTest() - { - _keyPair = await _ec2Wrapper.CreateKeyPair(_keyPairName); - Assert.NotNull(_keyPair); - } - - /// - /// Test the ability to save the values of an Amazon EC2 key pair to a - /// temporary file. - /// - [Fact()] - [Order(2)] - [Trait("Category", "Integration")] - public void SaveKeyPairTest() - { - _tempFileName = _ec2Wrapper.SaveKeyPair(_keyPair); - Assert.NotNull(_tempFileName); - - } - - /// - /// Test the ability to retrieve information about existing Amazon - /// EC2 key pairs. + /// Verifies the scenario with an integration test. No errors should be logged. /// /// Async task. - [Fact()] - [Order(3)] - [Trait("Category", "Integration")] - public async Task DescribeKeyPairsTest() - { - var keyPairs = await _ec2Wrapper.DescribeKeyPairs(_keyPairName); - Assert.NotEmpty(keyPairs); - } - - /// - /// Test the creation of an Amazon EC2 security group. - /// - /// Async task. - [Fact()] - [Order(4)] - [Trait("Category", "Integration")] - public async Task CreateSecurityGroupTest() - { - _secGroupId = await _ec2Wrapper.CreateSecurityGroup(_groupName, _groupDescription); - Assert.NotNull(_secGroupId); - } - - /// - /// Test the authorization of the local computer for ingress to an - /// EC2 instance. - /// - /// Async task. - [Fact()] - [Order(5)] - [Trait("Category", "Integration")] - public async Task AuthorizeSecurityGroupIngressTest() - { - var success = await _ec2Wrapper.AuthorizeSecurityGroupIngress(_groupName); - Assert.True(success, "Could not authorize the group for ingress."); - } - - /// - /// Test the ability to retrieve a list of existing Amazon EC2 security - /// groups. - /// - /// Async task. - [Fact()] - [Order(6)] - [Trait("Category", "Integration")] - public async Task DescribeSecurityGroupsTest() - { - var secGroups = await _ec2Wrapper.DescribeSecurityGroups(_secGroupId); - Assert.NotEmpty(secGroups); - } - - /// - /// Test the retrieval of information about an EC2 instance. - /// - /// Async task. - [Fact()] - [Order(7)] - [Trait("Category", "Integration")] - public async Task DescribeInstanceTest() - { - var instance = await _ec2Wrapper.DescribeInstance(_ec2InstanceId); - Assert.NotNull(instance); - } - - /// - /// Test the method which retrieves a list of images. - /// - /// Async task. - [Fact()] - [Order(8)] - [Trait("Category", "Integration")] - public async Task DescribeImagesTest() - { - // Get list of available Amazon Linux 2 Amazon Machine Images (AMIs). - var parameters = await _ssmWrapper.GetParametersByPath("/aws/service/ami-amazon-linux-latest"); - - List imageIds = parameters.Select(param => param.Value).ToList(); - - _images = await _ec2Wrapper.DescribeImages(imageIds); - _imageId = _images[0].ImageId; - _architecture = _images[0].Architecture; - Assert.NotEmpty(_images); - } - - /// - /// Test the ability to retrieve information about available instance - /// types. - /// - /// Async task. - [Fact()] - [Order(9)] - [Trait("Category", "Integration")] - public async Task DescribeInstanceTypesTest() - { - _instanceTypes = await _ec2Wrapper.DescribeInstanceTypes(_architecture); - Assert.NotEmpty(_instanceTypes); - } - - /// - /// Test the ability to run EC2 instances. - /// - /// Async task. - [Fact(Skip = "Long running test.")] - [Order(10)] - [Trait("Category", "Integration")] - public async Task RunInstancesTest() - { - _ec2InstanceId = await _ec2Wrapper.RunInstances(_imageId, _instanceType, _keyPairName, _secGroupId); - - // Wait for the instance state to be running. - var isRunning = false; - do - { - isRunning = await _ec2Wrapper.WaitForInstanceState(_ec2InstanceId, InstanceStateName.Running); - } while (!isRunning); - - Assert.NotNull(_ec2InstanceId); - } - - /// - /// Test the retrieval of information about multiple EC2 instances. - /// - /// Async task. - [Fact()] - [Order(11)] - [Trait("Category", "Integration")] - public async Task DescribeInstancesTest() - { - try - { - await _ec2Wrapper.DescribeInstances(); - } - catch (Exception ex) - { - Assert.True(false, $"Describe instances failed. Error: {ex.Message}"); - } - } - - /// - /// Test the allocation of an Elastic IP address. - /// - /// Async task. - [Fact()] - [Order(12)] - [Trait("Category", "Integration")] - public async Task AllocateAddressTest() - { - _allocationId = await _ec2Wrapper.AllocateAddress(); - Assert.NotNull(_allocationId); - } - - /// - /// Test the association of an elastic IP address to an instance. - /// - /// Async task. - [Fact(Skip = "Quarantined test.")] - [Order(13)] - [Trait("Category", "Integration")] - public async Task AssociateAddressTest() - { - _associationId = await _ec2Wrapper.AssociateAddress(_allocationId, _ec2InstanceId); - Assert.NotNull(_associationId); - } - - /// - /// Test the ability to stop a running EC2 instance. - /// - /// Async task. - [Fact(Skip = "Long running test.")] - [Order(14)] - [Trait("Category", "Integration")] - public async Task StopInstancesTest() - { - try - { - await _ec2Wrapper.StopInstances(_ec2InstanceId); - var hasStopped = false; - do - { - hasStopped = await _ec2Wrapper.WaitForInstanceState(_ec2InstanceId, InstanceStateName.Stopped); - } while (!hasStopped); - - Assert.True(hasStopped); - } - catch (Exception ex) - { - Assert.True(false, $"Could not stop the instance. Error: {ex.Message}"); - } - } - - /// - /// Test the ability to start an existing EC2 instance. - /// - /// Async task. - [Fact(Skip = "Long running test.")] - [Order(15)] - [Trait("Category", "Integration")] - public async Task StartInstancesTest() - { - try - { - await _ec2Wrapper.StartInstances(_ec2InstanceId); - - // Wait for the instance state to be running. - var isRunning = false; - do - { - isRunning = await _ec2Wrapper.WaitForInstanceState(_ec2InstanceId, InstanceStateName.Running); - } while (!isRunning); - - Assert.True(isRunning); - } - catch (Exception ex) - { - Assert.True(false, $"Could not start {_ec2InstanceId}. Error: {ex.Message}"); - } - } - - /// - /// Test the ability to disassociate an IP address from an Amazon EC2 - /// Elastic IP address. - /// - /// Async task. - [Fact(Skip = "Quarantined test.")] - [Order(16)] - [Trait("Category", "Integration")] - public async Task DisassociateIpTest() - { - var success = await _ec2Wrapper.DisassociateIp(_associationId); - Assert.True(success, "Could not disassociate IP address."); - } - - /// - /// Test the ability to release an Amazon EC2 Elastic IP address. - /// - /// Async task. - [Fact()] - [Order(17)] - [Trait("Category", "Integration")] - public async Task ReleaseAddressTest() - { - var success = await _ec2Wrapper.ReleaseAddress(_allocationId); - Assert.True(success, "Could not releases the address."); - } - - /// - /// Test the ability to terminate an existing EC2 instance. - /// - /// Async task. - [Fact(Skip = "Long running test.")] - [Order(18)] - [Trait("Category", "Integration")] - public async Task TerminateInstanceTest() - { - var stateChange = await _ec2Wrapper.TerminateInstances(_ec2InstanceId); - - // Wait for the instance state to be terminated. - var hasTerminated = false; - do - { - hasTerminated = await _ec2Wrapper.WaitForInstanceState(_ec2InstanceId, InstanceStateName.Terminated); - } while (!hasTerminated); - - Assert.True(hasTerminated); - } - - /// - /// Test the deletion of an Amazon EC2 security group. - /// - /// Async task. - [Fact()] - [Order(19)] + [Fact] + [Order(1)] [Trait("Category", "Integration")] - public async Task DeleteSecurityGroupTest() + public async Task TestScenario() { - var success = await _ec2Wrapper.DeleteSecurityGroup(_secGroupId); - Assert.True(success, "Couldn't delete the security group."); - } + // Arrange. + EC2Basics.isInteractive = false; + var loggerScenarioMock = new Mock>(); + var loggerWrapperMock = new Mock>(); - /// - /// Test the deletion of an Amazon EC2 key pair. - /// - /// Async task. - [Fact()] - [Order(20)] - [Trait("Category", "Integration")] - public async Task DeleteKeyPairTest() - { - var success = await _ec2Wrapper.DeleteKeyPair(_keyPair!.KeyName); - Assert.True(success, "Could not delete the key pair."); - } + _client = new AmazonEC2Client(); - /// - /// Test the deletion of the temporary PEM file. - /// - [Fact()] - [Order(21)] - [Trait("Category", "Integration")] - public void DeleteTempFileTest() - { - try - { - _ec2Wrapper.DeleteTempFile(_tempFileName); - } - catch (Exception ex) - { - Assert.True(false, $"Could not delete the temporary file. Error: {ex.Message}"); - } - } + _ec2Wrapper = new EC2Wrapper(_client, loggerWrapperMock.Object); + _ssmWrapper = new SsmWrapper(new AmazonSimpleSystemsManagementClient()); - /// - /// Test the ability to retrieve a list of Amazon EC2 AMIs. - /// - /// Async task. - [Fact()] - [Order(22)] - [Trait("Category", "Integration")] - public async Task GetEc2AmiListTest() - { - _images = await _ec2Wrapper.GetEC2AmiList(); - Assert.NotEmpty(_images); + EC2Basics._ec2Wrapper = _ec2Wrapper; + EC2Basics._ssmWrapper = _ssmWrapper; + EC2Basics._logger = loggerScenarioMock.Object; + + loggerScenarioMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + loggerWrapperMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + // Act. + await EC2Basics.Main(new string[] { "" }); + + // Assert no exceptions or errors logged. + loggerScenarioMock.Verify( + logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>()), + Times.Never); + + loggerWrapperMock.Verify( + logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>()), + Times.Never); } } \ No newline at end of file diff --git a/dotnetv3/EC2/Tests/Usings.cs b/dotnetv3/EC2/Tests/Usings.cs index 3556203b6fc..e66b5551b60 100644 --- a/dotnetv3/EC2/Tests/Usings.cs +++ b/dotnetv3/EC2/Tests/Usings.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 global using Amazon.EC2; -global using Amazon.EC2.Model; global using Amazon.SimpleSystemsManagement; global using EC2Actions; global using Microsoft.Extensions.Configuration; diff --git a/dotnetv3/EC2/Tests/testsettings.json b/dotnetv3/EC2/Tests/testsettings.json index 50c489e739a..ffeb7fbf1cd 100644 --- a/dotnetv3/EC2/Tests/testsettings.json +++ b/dotnetv3/EC2/Tests/testsettings.json @@ -1,8 +1,12 @@ { - "CidrBlock": "10.0.0.0/16\"\"", "GroupDescription": "This is a group created for integration tests.", "GroupName": "test-group-name", "ImageId": "ami-02dd04850de58599e", "InstanceType": "t2.micro", - "KeyPairName": "test-example-key-pair" + "KeyPairName": "test-example-key-pair", + "VpcId": "vpc-1a2b3c4d", + "BucketName": "", + "SubnetId": "subnet-012345678912345606", + "SecurityGroupId": "sg-012345678912345606", + "WaitTimeMilliseconds": 300000 } diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample.sln b/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample.sln deleted file mode 100644 index b9d23c42d6d..00000000000 --- a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCExample.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.4.33103.184 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCExample", "CreateVPCExample\CreateVPCExample.csproj", "{431D576C-1742-4144-9849-3D05D859D4EE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVPCforS3Example", "CreateVPCforS3Example\CreateVPCforS3Example.csproj", "{B74AEAAA-A5FC-41EC-8B2C-E06A03060B0B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreateVPCTests", "CreateVPCTests\CreateVPCTests.csproj", "{19B5B034-BD70-414D-B81C-97EA546841EB}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {431D576C-1742-4144-9849-3D05D859D4EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {431D576C-1742-4144-9849-3D05D859D4EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {431D576C-1742-4144-9849-3D05D859D4EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {431D576C-1742-4144-9849-3D05D859D4EE}.Release|Any CPU.Build.0 = Release|Any CPU - {B74AEAAA-A5FC-41EC-8B2C-E06A03060B0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B74AEAAA-A5FC-41EC-8B2C-E06A03060B0B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B74AEAAA-A5FC-41EC-8B2C-E06A03060B0B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B74AEAAA-A5FC-41EC-8B2C-E06A03060B0B}.Release|Any CPU.Build.0 = Release|Any CPU - {19B5B034-BD70-414D-B81C-97EA546841EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19B5B034-BD70-414D-B81C-97EA546841EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19B5B034-BD70-414D-B81C-97EA546841EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19B5B034-BD70-414D-B81C-97EA546841EB}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FB7400C3-87AD-432D-9AA9-1461ACEB06B0} - EndGlobalSection -EndGlobal diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCTests.csproj b/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCTests.csproj deleted file mode 100644 index eb015c447c2..00000000000 --- a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/CreateVPCTests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net6.0 - enable - enable - - false - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - PreserveNewest - - - PreserveNewest - testsettings.json - - - - - - - - diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/Usings.cs b/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/Usings.cs deleted file mode 100644 index 24f9d54e547..00000000000 --- a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/Usings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -global using Xunit; \ No newline at end of file diff --git a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/testsettings.json b/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/testsettings.json deleted file mode 100644 index 2972957e0a4..00000000000 --- a/dotnetv3/EC2/VirtualPrivateCloudExamples/CreateVPCTests/testsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "BucketName": "amzn-s3-demo-bucket1", - "VpcId": "vpc-1a2b3c4d", - "SubnetId": "subnet-012345678912345606", - "SecurityGroupId": "sg-012345678912345606", - "WaitTimeMilliseconds": 300000 -} - diff --git a/dotnetv3/IAM/README.md b/dotnetv3/IAM/README.md index 492fda02331..789f50a279b 100644 --- a/dotnetv3/IAM/README.md +++ b/dotnetv3/IAM/README.md @@ -49,7 +49,7 @@ Code excerpts that show you how to call individual service functions. - [AttachRolePolicy](Actions/IAMWrapper.cs#L42) - [CreateAccessKey](Actions/IAMWrapper.cs#L62) - [CreateGroup](Actions/IAMWrapper.cs#L82) -- [CreateInstanceProfile](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L83) +- [CreateInstanceProfile](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L86) - [CreatePolicy](Actions/IAMWrapper.cs#L96) - [CreateRole](Actions/IAMWrapper.cs#L116) - [CreateServiceLinkedRole](Actions/IAMWrapper.cs#L138) @@ -57,7 +57,7 @@ Code excerpts that show you how to call individual service functions. - [DeleteAccessKey](Actions/IAMWrapper.cs#L173) - [DeleteGroup](Actions/IAMWrapper.cs#L194) - [DeleteGroupPolicy](Actions/IAMWrapper.cs#L208) -- [DeleteInstanceProfile](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L425) +- [DeleteInstanceProfile](../cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs#L504) - [DeletePolicy](Actions/IAMWrapper.cs#L230) - [DeleteRole](Actions/IAMWrapper.cs#L245) - [DeleteRolePolicy](Actions/IAMWrapper.cs#L259) diff --git a/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerActions.csproj b/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerActions.csproj index 3a58d61f412..1568c1d9120 100644 --- a/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerActions.csproj +++ b/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerActions.csproj @@ -12,6 +12,7 @@ + diff --git a/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs b/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs index 3c6de99ff98..a2b64cdcd10 100644 --- a/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs +++ b/dotnetv3/cross-service/ResilientService/AutoScalerActions/AutoScalerWrapper.cs @@ -7,10 +7,10 @@ using Amazon.EC2.Model; using Amazon.IdentityManagement; using Amazon.IdentityManagement.Model; -using Amazon.Runtime; using Amazon.SimpleSystemsManagement; using Amazon.SimpleSystemsManagement.Model; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using AlreadyExistsException = Amazon.AutoScaling.Model.AlreadyExistsException; namespace AutoScalerActions; @@ -25,6 +25,7 @@ public class AutoScalerWrapper private readonly IAmazonEC2 _amazonEc2; private readonly IAmazonSimpleSystemsManagement _amazonSsm; private readonly IAmazonIdentityManagementService _amazonIam; + private readonly ILogger _logger; private readonly string _instanceType = ""; private readonly string _amiParam = ""; @@ -58,12 +59,14 @@ public AutoScalerWrapper( IAmazonEC2 amazonEc2, IAmazonSimpleSystemsManagement amazonSsm, IAmazonIdentityManagementService amazonIam, - IConfiguration configuration) + IConfiguration configuration, + ILogger logger) { _amazonAutoScaling = amazonAutoScaling; _amazonEc2 = amazonEc2; _amazonSsm = amazonSsm; _amazonIam = amazonIam; + _logger = logger; var prefix = configuration["resourcePrefix"]; _instanceType = configuration["instanceType"]; @@ -269,35 +272,53 @@ await _amazonEc2.DeleteKeyPairAsync( /// The template object. public async Task CreateTemplate(string startupScriptPath, string instancePolicyPath) { - await CreateKeyPair(_keyPairName); - await CreateInstanceProfileWithName(_instancePolicyName, _instanceRoleName, _instanceProfileName, instancePolicyPath); - - var startServerText = await File.ReadAllTextAsync(startupScriptPath); - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(startServerText); - - var amiLatest = await _amazonSsm.GetParameterAsync( - new GetParameterRequest() { Name = _amiParam }); - var amiId = amiLatest.Parameter.Value; - var launchTemplateResponse = await _amazonEc2.CreateLaunchTemplateAsync( - new CreateLaunchTemplateRequest() - { - LaunchTemplateName = _launchTemplateName, - LaunchTemplateData = new RequestLaunchTemplateData() + try + { + await CreateKeyPair(_keyPairName); + await CreateInstanceProfileWithName(_instancePolicyName, _instanceRoleName, + _instanceProfileName, instancePolicyPath); + + var startServerText = await File.ReadAllTextAsync(startupScriptPath); + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(startServerText); + + var amiLatest = await _amazonSsm.GetParameterAsync( + new GetParameterRequest() { Name = _amiParam }); + var amiId = amiLatest.Parameter.Value; + var launchTemplateResponse = await _amazonEc2.CreateLaunchTemplateAsync( + new CreateLaunchTemplateRequest() { - InstanceType = _instanceType, - ImageId = amiId, - IamInstanceProfile = - new - LaunchTemplateIamInstanceProfileSpecificationRequest() - { - Name = _instanceProfileName - }, - KeyName = _keyPairName, - UserData = System.Convert.ToBase64String(plainTextBytes) - } - }); - return launchTemplateResponse.LaunchTemplate; + LaunchTemplateName = _launchTemplateName, + LaunchTemplateData = new RequestLaunchTemplateData() + { + InstanceType = _instanceType, + ImageId = amiId, + IamInstanceProfile = + new + LaunchTemplateIamInstanceProfileSpecificationRequest() + { + Name = _instanceProfileName + }, + KeyName = _keyPairName, + UserData = System.Convert.ToBase64String(plainTextBytes) + } + }); + return launchTemplateResponse.LaunchTemplate; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidLaunchTemplateName.AlreadyExistsException") + { + _logger.LogError($"Could not create the template, the name {_launchTemplateName} already exists. " + + $"Please try again with a unique name."); + } + throw; + } + catch (Exception ex) + { + _logger.LogError($"An error occurred while creating the template.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.ec2.CreateLaunchTemplate] @@ -308,9 +329,22 @@ await _amazonEc2.DeleteKeyPairAsync( /// A list of availability zones. public async Task> DescribeAvailabilityZones() { - var zoneResponse = await _amazonEc2.DescribeAvailabilityZonesAsync( - new DescribeAvailabilityZonesRequest()); - return zoneResponse.AvailabilityZones.Select(z => z.ZoneName).ToList(); + try + { + var zoneResponse = await _amazonEc2.DescribeAvailabilityZonesAsync( + new DescribeAvailabilityZonesRequest()); + return zoneResponse.AvailabilityZones.Select(z => z.ZoneName).ToList(); + } + catch (AmazonEC2Exception ec2Exception) + { + _logger.LogError($"An Amazon EC2 error occurred while listing availability zones.: {ec2Exception.Message}"); + throw; + } + catch (Exception ex) + { + _logger.LogError($"An error occurred while listing availability zones.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.Ec2.DescribeAvailabilityZones] @@ -356,15 +390,32 @@ await _amazonAutoScaling.CreateAutoScalingGroupAsync( /// The default VPC object. public async Task GetDefaultVpc() { - var vpcResponse = await _amazonEc2.DescribeVpcsAsync( - new DescribeVpcsRequest() - { - Filters = new List() + try + { + var vpcResponse = await _amazonEc2.DescribeVpcsAsync( + new DescribeVpcsRequest() { - new ("is-default", new List() { "true" }) - } - }); - return vpcResponse.Vpcs[0]; + Filters = new List() + { + new("is-default", new List() { "true" }) + } + }); + return vpcResponse.Vpcs[0]; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "UnauthorizedOperation") + { + _logger.LogError(ec2Exception, $"You do not have the necessary permissions to describe VPCs."); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while describing the vpcs.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.Ec2.DescribeVpcs] @@ -377,25 +428,42 @@ public async Task GetDefaultVpc() /// The collection of subnet objects. public async Task> GetAllVpcSubnetsForZones(string vpcId, List availabilityZones) { - var subnets = new List(); - var subnetPaginator = _amazonEc2.Paginators.DescribeSubnets( - new DescribeSubnetsRequest() - { - Filters = new List() + try + { + var subnets = new List(); + var subnetPaginator = _amazonEc2.Paginators.DescribeSubnets( + new DescribeSubnetsRequest() { - new ("vpc-id", new List() { vpcId}), - new ("availability-zone", availabilityZones), - new ("default-for-az", new List() { "true" }) - } - }); + Filters = new List() + { + new("vpc-id", new List() { vpcId }), + new("availability-zone", availabilityZones), + new("default-for-az", new List() { "true" }) + } + }); - // Get the entire list using the paginator. - await foreach (var subnet in subnetPaginator.Subnets) - { - subnets.Add(subnet); + // Get the entire list using the paginator. + await foreach (var subnet in subnetPaginator.Subnets) + { + subnets.Add(subnet); + } + + return subnets; } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidVpcID.NotFound") + { + _logger.LogError(ec2Exception, $"The specified VPC ID {vpcId} does not exist."); + } - return subnets; + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while describing the subnets.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.Ec2.DescribeSubnets] @@ -415,9 +483,20 @@ await _amazonEc2.DeleteLaunchTemplateAsync( LaunchTemplateName = templateName }); } - catch (AmazonClientException) + catch (AmazonEC2Exception ec2Exception) { - Console.WriteLine($"Unable to delete template {templateName}."); + if (ec2Exception.ErrorCode == "InvalidLaunchTemplateName.NotFoundException") + { + _logger.LogError( + $"Could not delete the template, the name {_launchTemplateName} was not found."); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError($"An error occurred while deleting the template.: {ex.Message}"); + throw; } } // snippet-end:[ResilientService.dotnetv3.Ec2.DeleteLaunchTemplate] @@ -500,15 +579,32 @@ public async Task> GetInstancesByGroupName(string group) /// Instance profile associations data. public async Task GetInstanceProfile(string instanceId) { - var response = await _amazonEc2.DescribeIamInstanceProfileAssociationsAsync( - new DescribeIamInstanceProfileAssociationsRequest() - { - Filters = new List() + try + { + var response = await _amazonEc2.DescribeIamInstanceProfileAssociationsAsync( + new DescribeIamInstanceProfileAssociationsRequest() { - new ("instance-id", new List() { instanceId }) - }, - }); - return response.IamInstanceProfileAssociations[0]; + Filters = new List() + { + new("instance-id", new List() { instanceId }) + }, + }); + return response.IamInstanceProfileAssociations[0]; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceID.NotFound") + { + _logger.LogError(ec2Exception, $"Instance {instanceId} not found"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while creating the template.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.Ec2.GetInstanceProfile] @@ -524,7 +620,9 @@ public async Task GetInstanceProfile(string insta /// Async task. public async Task ReplaceInstanceProfile(string instanceId, string credsProfileName, string associationId) { - await _amazonEc2.ReplaceIamInstanceProfileAssociationAsync( + try + { + await _amazonEc2.ReplaceIamInstanceProfileAssociationAsync( new ReplaceIamInstanceProfileAssociationRequest() { AssociationId = associationId, @@ -533,40 +631,62 @@ await _amazonEc2.ReplaceIamInstanceProfileAssociationAsync( Name = credsProfileName } }); - // Allow time before resetting. - Thread.Sleep(25000); - var instanceReady = false; - var retries = 5; - while (retries-- > 0 && !instanceReady) - { + // Allow time before resetting. + Thread.Sleep(25000); + await _amazonEc2.RebootInstancesAsync( new RebootInstancesRequest(new List() { instanceId })); - Thread.Sleep(10000); - - var instancesPaginator = _amazonSsm.Paginators.DescribeInstanceInformation( - new DescribeInstanceInformationRequest()); - // Get the entire list using the paginator. - await foreach (var instance in instancesPaginator.InstanceInformationList) + Thread.Sleep(25000); + var instanceReady = false; + var retries = 5; + while (retries-- > 0 && !instanceReady) { - instanceReady = instance.InstanceId == instanceId; - if (instanceReady) + var instancesPaginator = + _amazonSsm.Paginators.DescribeInstanceInformation( + new DescribeInstanceInformationRequest()); + // Get the entire list using the paginator. + await foreach (var instance in instancesPaginator.InstanceInformationList) { - break; + instanceReady = instance.InstanceId == instanceId; + if (instanceReady) + { + break; + } } } + Console.WriteLine("Waiting for instance to be running."); + await WaitForInstanceState(instanceId, InstanceStateName.Running); + Console.WriteLine("Instance ready."); + Console.WriteLine($"Sending restart command to instance {instanceId}"); + await _amazonSsm.SendCommandAsync( + new SendCommandRequest() + { + InstanceIds = new List() { instanceId }, + DocumentName = "AWS-RunShellScript", + Parameters = new Dictionary>() + { + { + "commands", + new List() { "cd / && sudo python3 server.py 80" } + } + } + }); + Console.WriteLine($"Restarted the web server on instance {instanceId}"); } - Console.WriteLine($"Sending restart command to instance {instanceId}"); - await _amazonSsm.SendCommandAsync( - new SendCommandRequest() + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceID.NotFound") { - InstanceIds = new List() { instanceId }, - DocumentName = "AWS-RunShellScript", - Parameters = new Dictionary>() - { - {"commands", new List() { "cd / && sudo python3 server.py 80" }} - } - }); - Console.WriteLine($"Restarted the web server on instance {instanceId}"); + _logger.LogError(ec2Exception, $"Instance {instanceId} not found"); + } + + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while replacing the template.: {ex.Message}"); + throw; + } } // snippet-end:[ResilientService.dotnetv3.Ec2.ReplaceInstanceProfile] @@ -785,6 +905,36 @@ await _amazonAutoScaling.AttachLoadBalancerTargetGroupsAsync( }); } // snippet-end:[ResilientService.dotnetv3.AutoScaling.AttachLoadBalancer] + + /// + /// Wait until an EC2 instance is in a specified state. + /// + /// The instance Id. + /// The state to wait for. + /// A Boolean value indicating the success of the action. + public async Task WaitForInstanceState(string instanceId, InstanceStateName stateName) + { + var request = new DescribeInstancesRequest + { + InstanceIds = new List { instanceId } + }; + + // Wait until the instance is in the specified state. + var hasState = false; + do + { + // Wait 5 seconds. + Thread.Sleep(5000); + + // Check for the desired state. + var response = await _amazonEc2.DescribeInstancesAsync(request); + var instance = response.Reservations[0].Instances[0]; + hasState = instance.State.Name == stateName; + Console.Write(". "); + } while (!hasState); + + return hasState; + } } // snippet-end:[ResilientService.dotnetv3.AutoScalerWrapper] \ No newline at end of file diff --git a/dotnetv3/cross-service/ResilientService/Tests/ResilientServiceTests.cs b/dotnetv3/cross-service/ResilientService/Tests/ResilientServiceTests.cs index 8b55af69c54..506a1210dc2 100644 --- a/dotnetv3/cross-service/ResilientService/Tests/ResilientServiceTests.cs +++ b/dotnetv3/cross-service/ResilientService/Tests/ResilientServiceTests.cs @@ -10,10 +10,10 @@ using AutoScalerActions; using ElasticLoadBalancerActions; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using ParameterActions; using RecommendationService; using ResilientService; -using Xunit.Extensions.Ordering; namespace ResilientServiceTests; @@ -29,8 +29,6 @@ public class ResilientServiceTests private readonly Recommendations _recommendations = null!; private readonly SmParameterWrapper _smParameterWrapper = null!; - private readonly string _databaseName; - /// /// Constructor for the test class. /// @@ -43,7 +41,12 @@ public ResilientServiceTests() true) // Optionally, load local settings. .Build(); - _databaseName = _configuration["databaseName"]!; + + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + var _logger = new Logger(loggerFactory); _elasticLoadBalancerWrapper = new ElasticLoadBalancerWrapper( new AmazonElasticLoadBalancingV2Client(), @@ -53,7 +56,7 @@ public ResilientServiceTests() new AmazonEC2Client(), new AmazonSimpleSystemsManagementClient(), new AmazonIdentityManagementServiceClient(), - _configuration); + _configuration, _logger); _recommendations = new Recommendations(new AmazonDynamoDBClient(), _configuration); _smParameterWrapper =