From c8d82096ec38a330cb98bf4189363a89de6c07c2 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:27:42 -0600 Subject: [PATCH] .NET v4 preview EC2 folder (#7206) --- dotnetv4/DotNetV4Examples.sln | 26 + dotnetv4/EC2/Actions/EC2Actions.csproj | 19 + dotnetv4/EC2/Actions/EC2Wrapper.cs | 883 ++++++++++++++++++ dotnetv4/EC2/Actions/HelloEC2.cs | 62 ++ dotnetv4/EC2/Actions/SsmWrapper.cs | 47 + dotnetv4/EC2/Actions/Usings.cs | 9 + dotnetv4/EC2/EC2Examples.sln | 40 + dotnetv4/EC2/README.md | 142 +++ .../EC2/Scenarios/EC2_Basics/Basics.csproj | 21 + .../EC2/Scenarios/EC2_Basics/EC2Basics.cs | 326 +++++++ .../EC2/Scenarios/EC2_Basics/UIMethods.cs | 59 ++ dotnetv4/EC2/Scenarios/EC2_Basics/Usings.cs | 7 + dotnetv4/EC2/Tests/EC2Tests.csproj | 43 + dotnetv4/EC2/Tests/EC2WrapperTests.cs | 79 ++ dotnetv4/EC2/Tests/Usings.cs | 10 + dotnetv4/EC2/Tests/testsettings.json | 12 + 16 files changed, 1785 insertions(+) create mode 100644 dotnetv4/EC2/Actions/EC2Actions.csproj create mode 100644 dotnetv4/EC2/Actions/EC2Wrapper.cs create mode 100644 dotnetv4/EC2/Actions/HelloEC2.cs create mode 100644 dotnetv4/EC2/Actions/SsmWrapper.cs create mode 100644 dotnetv4/EC2/Actions/Usings.cs create mode 100644 dotnetv4/EC2/EC2Examples.sln create mode 100644 dotnetv4/EC2/README.md create mode 100644 dotnetv4/EC2/Scenarios/EC2_Basics/Basics.csproj create mode 100644 dotnetv4/EC2/Scenarios/EC2_Basics/EC2Basics.cs create mode 100644 dotnetv4/EC2/Scenarios/EC2_Basics/UIMethods.cs create mode 100644 dotnetv4/EC2/Scenarios/EC2_Basics/Usings.cs create mode 100644 dotnetv4/EC2/Tests/EC2Tests.csproj create mode 100644 dotnetv4/EC2/Tests/EC2WrapperTests.cs create mode 100644 dotnetv4/EC2/Tests/Usings.cs create mode 100644 dotnetv4/EC2/Tests/testsettings.json diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index da9f1814a05..ab7be69d4d9 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -109,6 +109,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudWatchScenario", "Cloud EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudWatchActions", "CloudWatch\Actions\CloudWatchActions.csproj", "{EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EC2", "EC2", "{9424FB14-B6DE-44CE-B675-AC2B57EC1E69}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EC2Tests", "EC2\Tests\EC2Tests.csproj", "{C99A0F7C-9477-4985-90F6-8EED38ECAC10}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{6C167F25-F97F-4854-8CD8-A2D446B6799B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "EC2\Scenarios\EC2_Basics\Basics.csproj", "{D95519CA-BD27-45AE-B83B-3FB02E7AE445}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EC2Actions", "EC2\Actions\EC2Actions.csproj", "{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -271,6 +281,18 @@ Global {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|Any CPU.Build.0 = Release|Any CPU + {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|Any CPU.Build.0 = Release|Any CPU + {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|Any CPU.Build.0 = Release|Any CPU + {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -323,6 +345,10 @@ Global {106FBE12-6FF7-40DC-9B3C-E5F67F335B32} = {CED87D19-7F82-4D67-8A30-3EE085D07E45} {565A9701-3D9C-49F8-86B7-D256A1D9E074} = {CED87D19-7F82-4D67-8A30-3EE085D07E45} {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3} = {CED87D19-7F82-4D67-8A30-3EE085D07E45} + {C99A0F7C-9477-4985-90F6-8EED38ECAC10} = {9424FB14-B6DE-44CE-B675-AC2B57EC1E69} + {6C167F25-F97F-4854-8CD8-A2D446B6799B} = {9424FB14-B6DE-44CE-B675-AC2B57EC1E69} + {D95519CA-BD27-45AE-B83B-3FB02E7AE445} = {6C167F25-F97F-4854-8CD8-A2D446B6799B} + {0633CB2B-3508-48E5-A8C2-427A83A5CA6E} = {9424FB14-B6DE-44CE-B675-AC2B57EC1E69} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA} diff --git a/dotnetv4/EC2/Actions/EC2Actions.csproj b/dotnetv4/EC2/Actions/EC2Actions.csproj new file mode 100644 index 00000000000..6840737b6e5 --- /dev/null +++ b/dotnetv4/EC2/Actions/EC2Actions.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/dotnetv4/EC2/Actions/EC2Wrapper.cs b/dotnetv4/EC2/Actions/EC2Wrapper.cs new file mode 100644 index 00000000000..125a6fe0a6f --- /dev/null +++ b/dotnetv4/EC2/Actions/EC2Wrapper.cs @@ -0,0 +1,883 @@ +// 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.dotnetv4.EC2WrapperClass] +/// +/// Methods of this class perform Amazon Elastic Compute Cloud (Amazon EC2). +/// +public class EC2Wrapper +{ + private readonly IAmazonEC2 _amazonEC2; + private readonly ILogger _logger; + + /// + /// 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.dotnetv4.AllocateAddress] + /// + /// 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 response object for the allocated address. + public async Task AllocateAddress() + { + var request = new AllocateAddressRequest(); + + 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.dotnetv4.AllocateAddress] + + // snippet-start:[EC2.dotnetv4.AssociateAddress] + /// + /// 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 + /// associate the address with. + /// The association Id that represents + /// the association of the Elastic IP address with an instance. + public async Task AssociateAddress(string allocationId, string instanceId) + { + try + { + var request = new AssociateAddressRequest + { + AllocationId = allocationId, + InstanceId = instanceId + }; + + 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.dotnetv4.AssociateAddress] + + // snippet-start:[EC2.dotnetv4.AuthorizeSecurityGroupIngress] + /// + /// Authorize the local computer ingress to EC2 instances associated + /// with the virtual private cloud (VPC) security group. + /// + /// The name of the security group. + /// A Boolean value indicating the success of the action. + public async Task AuthorizeSecurityGroupIngress(string groupName) + { + 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; + } + } + + /// + /// Authorize the local computer for ingress to + /// the Amazon EC2 SecurityGroup. + /// + /// The IPv4 address of the computer running the scenario. + private static async Task GetIpAddress() + { + var httpClient = new HttpClient(); + var ipString = await httpClient.GetStringAsync("https://checkip.amazonaws.com"); + + // The IP address is returned with a new line + // character on the end. Trim off the whitespace and + // return the value to the caller. + return ipString.Trim(); + } + // snippet-end:[EC2.dotnetv4.AuthorizeSecurityGroupIngress] + + // snippet-start:[EC2.dotnetv4.CreateKeyPair] + /// + /// 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) + { + try + { + var request = new CreateKeyPairRequest { KeyName = keyPairName, }; + + var response = await _amazonEC2.CreateKeyPairAsync(request); + + var kp = response.KeyPair; + // 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"); + } + catch (AmazonEC2Exception ec2Exception) + { + 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; + } + } + + /// + /// Save KeyPair information to a temporary file. + /// + /// The name of the key pair. + /// The full path to the temporary file. + public string SaveKeyPair(KeyPair keyPair) + { + var tempPath = Path.GetTempPath(); + var tempFileName = $"{tempPath}\\{Path.GetRandomFileName()}"; + var pemFileName = Path.ChangeExtension(tempFileName, "pem"); + + // Save the key pair to a file in a temporary folder. + using var stream = new FileStream(pemFileName, FileMode.Create); + using var writer = new StreamWriter(stream); + writer.WriteLine(keyPair.KeyMaterial); + + return pemFileName; + } + // snippet-end:[EC2.dotnetv4.CreateKeyPair] + + // snippet-start:[EC2.dotnetv4.CreateSecurityGroup] + /// + /// 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) + { + try + { + var response = await _amazonEC2.CreateSecurityGroupAsync( + new CreateSecurityGroupRequest(groupName, groupDescription)); + + // 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.dotnetv4.CreateSecurityGroup] + + // snippet-start:[EC2.dotnetv4.DeleteKeyPair] + /// + /// Delete an Amazon EC2 key pair. + /// + /// The name of the key pair to delete. + /// A Boolean value indicating the success of the action. + public async Task DeleteKeyPair(string keyPairName) + { + try + { + 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}"); + return false; + } + } + + /// + /// Delete the temporary file where the key pair information was saved. + /// + /// The path to the temporary file. + public void DeleteTempFile(string tempFileName) + { + if (File.Exists(tempFileName)) + { + File.Delete(tempFileName); + } + } + // snippet-end:[EC2.dotnetv4.DeleteKeyPair] + + // snippet-start:[EC2.dotnetv4.DeleteSecurityGroup] + /// + /// Delete an Amazon EC2 security group. + /// + /// The name of the group to delete. + /// A Boolean value indicating the success of the action. + public async Task DeleteSecurityGroup(string groupId) + { + 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.dotnetv4.DeleteSecurityGroup] + + // snippet-start:[EC2.dotnetv4.DescribeImages] + /// + /// Get information about existing Amazon EC2 images. + /// + /// A list of image information. + public async Task> DescribeImages(List? imageIds) + { + var request = new DescribeImagesRequest(); + if (imageIds is not null) + { + // If the imageIds list is not null, add the list + // to the request object. + request.ImageIds = imageIds; + } + + var response = await _amazonEC2.DescribeImagesAsync(request); + return response.Images; + } + // snippet-end:[EC2.dotnetv4.DescribeImages] + + // snippet-start:[EC2.dotnetv4.DescribeInstance] + /// + /// Get information about an Amazon EC2 instance. + /// + /// The instance Id of the EC2 instance. + /// An EC2 instance. + public async Task DescribeInstance(string instanceId) + { + var response = await _amazonEC2.DescribeInstancesAsync( + new DescribeInstancesRequest { InstanceIds = new List { instanceId } }); + return response.Reservations[0].Instances[0]; + } + + /// + /// Display EC2 instance information. + /// + /// The instance Id of the EC2 instance. + public void DisplayInstanceInformation(Instance instance) + { + Console.WriteLine($"ID: {instance.InstanceId}"); + Console.WriteLine($"Image ID: {instance.ImageId}"); + Console.WriteLine($"{instance.InstanceType}"); + Console.WriteLine($"Key Name: {instance.KeyName}"); + Console.WriteLine($"VPC ID: {instance.VpcId}"); + Console.WriteLine($"Public IP: {instance.PublicIpAddress}"); + Console.WriteLine($"State: {instance.State.Name}"); + } + // snippet-end:[EC2.dotnetv4.DescribeInstance] + + + // snippet-start:[EC2.dotnetv4.DescribeInstanceTypes] + /// + /// Describe the instance types available. + /// + /// A list of instance type information. + public async Task> DescribeInstanceTypes(ArchitectureValues architecture) + { + 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" })); + + request.Filters = filters; + var instanceTypes = new List(); + + var paginator = _amazonEC2.Paginators.DescribeInstanceTypes(request); + await foreach (var instanceType in paginator.InstanceTypes) + { + instanceTypes.Add(instanceType); + } + + return instanceTypes; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidParameterValue") + { + _logger.LogError( + $"Parameters are invalid. Ensure architecture and size strings conform to DescribeInstanceTypes API reference."); + } + + throw; + } + catch (Exception ex) + { + Console.WriteLine($"Couldn't delete the security group because: {ex.Message}"); + throw; + } + } + // snippet-end:[EC2.dotnetv4.DescribeInstanceTypes] + + // snippet-start:[EC2.dotnetv4.DescribeKeyPairs] + /// + /// Get information about an Amazon EC2 key pair. + /// + /// The name of the key pair. + /// A list of key pair information. + public async Task> DescribeKeyPairs(string keyPairName) + { + try + { + var pairs = new List(); + var request = new DescribeKeyPairsRequest(); + if (!string.IsNullOrEmpty(keyPairName)) + { + request = new DescribeKeyPairsRequest + { + KeyNames = new List { keyPairName } + }; + } + + var response = await _amazonEC2.DescribeKeyPairsAsync(request); + if (response.KeyPairs != null) + { + pairs = response.KeyPairs.ToList(); + } + return pairs; + + } + 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; + } + } + + // snippet-end:[EC2.dotnetv4.DescribeKeyPairs] + + // snippet-start:[EC2.dotnetv4.DescribeSecurityGroups] + /// + /// Retrieve information for one or all 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) + { + 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); + } + + 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; + } + } + + /// + /// Display the information returned by the call to + /// DescribeSecurityGroupsAsync. + /// + /// A list of security group information. + public void DisplaySecurityGroupInfoAsync(SecurityGroup securityGroup) + { + Console.WriteLine($"{securityGroup.GroupName}"); + Console.WriteLine("Ingress permissions:"); + securityGroup.IpPermissions?.ForEach(permission => + { + Console.WriteLine($"\tFromPort: {permission.FromPort}"); + Console.WriteLine($"\tIpProtocol: {permission.IpProtocol}"); + + Console.Write($"\tIpv4Ranges: "); + permission.Ipv4Ranges?.ForEach(range => { Console.Write($"{range.CidrIp} "); }); + + Console.WriteLine($"\n\tIpv6Ranges:"); + permission.Ipv6Ranges?.ForEach(range => { Console.Write($"{range.CidrIpv6} "); }); + + Console.Write($"\n\tPrefixListIds: "); + permission.PrefixListIds?.ForEach(id => Console.Write($"{id.Id} ")); + + Console.WriteLine($"\n\tTo Port: {permission.ToPort}"); + }); + Console.WriteLine("Egress permissions:"); + securityGroup.IpPermissionsEgress?.ForEach(permission => + { + Console.WriteLine($"\tFromPort: {permission.FromPort}"); + Console.WriteLine($"\tIpProtocol: {permission.IpProtocol}"); + + Console.Write($"\tIpv4Ranges: "); + permission.Ipv4Ranges?.ForEach(range => { Console.Write($"{range.CidrIp} "); }); + + Console.WriteLine($"\n\tIpv6Ranges:"); + permission.Ipv6Ranges?.ForEach(range => { Console.Write($"{range.CidrIpv6} "); }); + + Console.Write($"\n\tPrefixListIds: "); + permission.PrefixListIds?.ForEach(id => Console.Write($"{id.Id} ")); + + Console.WriteLine($"\n\tTo Port: {permission.ToPort}"); + }); + } + + // snippet-end:[EC2.dotnetv4.DescribeSecurityGroups] + + // snippet-start:[EC2.dotnetv4.DisassociateAddress] + /// + /// Disassociate an Elastic IP address from an EC2 instance. + /// + /// The association Id. + /// A Boolean value indicating the success of the action. + public async Task DisassociateIp(string associationId) + { + 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.dotnetv4.DisassociateAddress] + + + // snippet-start:[EC2.dotnetv4.ReleaseAddress] + /// + /// 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. + /// True if successful. + public async Task ReleaseAddress(string allocationId) + { + try + { + var request = new ReleaseAddressRequest { AllocationId = allocationId }; + + await _amazonEC2.ReleaseAddressAsync(request); + return true; + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidAllocationID.NotFound") + { + _logger.LogError( + $"AllocationId {allocationId} was not found. {ec2Exception.Message}"); + } + + return false; + } + catch (Exception ex) + { + _logger.LogError( + $"An error occurred while releasing the AllocationId {allocationId}.: {ex.Message}"); + return false; + } + } + // snippet-end:[EC2.dotnetv4.ReleaseAddress] + + // snippet-start:[EC2.dotnetv4.RunInstances] + /// + /// Create and run an EC2 instance. + /// + /// The image Id of the image used as a basis for the + /// EC2 instance. + /// The instance type of the EC2 instance to create. + /// The name of the key pair to associate with the + /// instance. + /// The Id of the Amazon EC2 security group that will be + /// allowed to interact with the new EC2 instance. + /// The instance Id of the new EC2 instance. + public async Task RunInstances(string imageId, string instanceType, string keyName, string groupId) + { + 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.dotnetv4.RunInstances] + + // snippet-start:[EC2.dotnetv4.StartInstances] + /// + /// Start an EC2 instance. + /// + /// The instance Id of the Amazon EC2 instance + /// to start. + /// Async task. + public async Task StartInstances(string ec2InstanceId) + { + try + { + var request = new StartInstancesRequest + { + InstanceIds = new List { ec2InstanceId }, + }; + + await _amazonEC2.StartInstancesAsync(request); + + Console.Write("Waiting for instance to start. "); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Running); + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceId") + { + _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.dotnetv4.StartInstances] + + // snippet-start:[EC2.dotnetv4.StopInstances] + /// + /// Stop an EC2 instance. + /// + /// The instance Id of the EC2 instance to + /// stop. + /// Async task. + public async Task StopInstances(string ec2InstanceId) + { + try + { + var request = new StopInstancesRequest + { + InstanceIds = new List { ec2InstanceId }, + }; + + await _amazonEC2.StopInstancesAsync(request); + Console.Write("Waiting for the instance to stop."); + await WaitForInstanceState(ec2InstanceId, InstanceStateName.Stopped); + + Console.WriteLine("\nThe instance has stopped."); + } + catch (AmazonEC2Exception ec2Exception) + { + if (ec2Exception.ErrorCode == "InvalidInstanceId") + { + _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.dotnetv4.StopInstances] + + // snippet-start:[EC2.dotnetv4.TerminateInstances] + /// + /// Terminate an EC2 instance. + /// + /// The instance Id of the EC2 instance + /// to terminate. + /// Async task. + public async Task> TerminateInstances(string ec2InstanceId) + { + try + { + var request = new TerminateInstancesRequest + { + InstanceIds = new List { ec2InstanceId } + }; + + 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.dotnetv4.TerminateInstances] + + // snippet-start:[EC2.dotnetv4.WaitForInstanceState] + /// + /// 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:[EC2.dotnetv4.WaitForInstanceState] +} +// snippet-end:[EC2.dotnetv4.EC2WrapperClass] \ No newline at end of file diff --git a/dotnetv4/EC2/Actions/HelloEC2.cs b/dotnetv4/EC2/Actions/HelloEC2.cs new file mode 100644 index 00000000000..39ef7fc12ac --- /dev/null +++ b/dotnetv4/EC2/Actions/HelloEC2.cs @@ -0,0 +1,62 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[EC2.dotnetv4.HelloEc2] + +namespace EC2Actions; + +public class HelloEc2 +{ + /// + /// HelloEc2 lists the existing security groups for the default users. + /// + /// Command line arguments + /// Async task. + static async Task Main(string[] args) + { + // Set up dependency injection for Amazon Elastic Compute Cloud (Amazon EC2). + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Now the client is available for injection. + var ec2Client = host.Services.GetRequiredService(); + + try + { + // 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($"An error occurred while listing security groups. {ex.Message}"); + } + } +} +// snippet-end:[EC2.dotnetv4.HelloEc2] \ No newline at end of file diff --git a/dotnetv4/EC2/Actions/SsmWrapper.cs b/dotnetv4/EC2/Actions/SsmWrapper.cs new file mode 100644 index 00000000000..fe36855451a --- /dev/null +++ b/dotnetv4/EC2/Actions/SsmWrapper.cs @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace EC2Actions; + +// snippet-start:[SSM.dotnetv4.SSMActionsClass] +/// +/// Methods that perform actions from the Simple Systems Management Service. +/// +public class SsmWrapper +{ + private readonly IAmazonSimpleSystemsManagement _amazonSSM; + + public SsmWrapper(IAmazonSimpleSystemsManagement amazonService) + { + _amazonSSM = amazonService; + } + + /// + /// Get a list of parameter values based on the service path. + /// + /// The path used to retrieve parameters. + /// Async task. + public async Task> GetParametersByPath(string path) + { + var parameters = new List(); + var request = new GetParametersByPathRequest { Path = path }; + + // Get the whole list with a paginator. + var paginatedParametersByPath = _amazonSSM.Paginators.GetParametersByPath(request); + + await foreach (var parametersPage in paginatedParametersByPath.Responses) + { + parameters.AddRange(parametersPage.Parameters); + } + + // Filter out everything except items that + // have "amzn2" in the name property. + var paramList = + from parameter in parameters + where parameter.Name.Contains("amzn2") + select parameter; + return paramList.ToList(); + } +} + +// snippet-end:[SSM.dotnetv4.SSMActionsClass] \ No newline at end of file diff --git a/dotnetv4/EC2/Actions/Usings.cs b/dotnetv4/EC2/Actions/Usings.cs new file mode 100644 index 00000000000..3125ba51a46 --- /dev/null +++ b/dotnetv4/EC2/Actions/Usings.cs @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using System.Net; +global using Amazon.EC2; +global using Amazon.EC2.Model; +global using Amazon.SimpleSystemsManagement; +global using Amazon.SimpleSystemsManagement.Model; +global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/dotnetv4/EC2/EC2Examples.sln b/dotnetv4/EC2/EC2Examples.sln new file mode 100644 index 00000000000..c089758acd4 --- /dev/null +++ b/dotnetv4/EC2/EC2Examples.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 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}") = "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 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {796910FA-6E94-460B-8CB4-97DF01B9ADC8}.Release|Any CPU.Build.0 = Release|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1731AE1-381F-4044-BEBE-269FF7E24B1F}.Release|Any CPU.Build.0 = Release|Any CPU + {6046A2FC-6A39-4C2D-8DD9-AA3740B17B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {870D888D-5C8B-4057-8722-F73ECF38E513} + EndGlobalSection +EndGlobal diff --git a/dotnetv4/EC2/README.md b/dotnetv4/EC2/README.md new file mode 100644 index 00000000000..45de83bb0bd --- /dev/null +++ b/dotnetv4/EC2/README.md @@ -0,0 +1,142 @@ +# Amazon EC2 code examples for the SDK for .NET + +## Overview + +Shows how to use the AWS SDK for .NET to work with Amazon Elastic Compute Cloud (Amazon EC2). + + + + +_Amazon EC2 is a web service that provides resizable computing capacity—literally, servers in Amazon's data centers—that you use to build and host your software systems._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv3` folder. + + + + + +### Get started + +- [Hello Amazon EC2](Actions/HelloEC2.cs#L4) (`DescribeSecurityGroups`) + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](Scenarios/EC2_Basics/EC2Basics.cs) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [AllocateAddress](Actions/EC2Wrapper.cs#L28) +- [AssociateAddress](Actions/EC2Wrapper.cs#L64) +- [AuthorizeSecurityGroupIngress](Actions/EC2Wrapper.cs#L107) +- [CreateKeyPair](Actions/EC2Wrapper.cs#L170) +- [CreateSecurityGroup](Actions/EC2Wrapper.cs#L242) +- [DeleteKeyPair](Actions/EC2Wrapper.cs#L319) +- [DeleteSecurityGroup](Actions/EC2Wrapper.cs#L361) +- [DescribeInstanceTypes](Actions/EC2Wrapper.cs#L531) +- [DescribeInstances](Actions/EC2Wrapper.cs#L474) +- [DescribeKeyPairs](Actions/EC2Wrapper.cs#L578) +- [DescribeSecurityGroups](Actions/EC2Wrapper.cs#L620) +- [DisassociateAddress](Actions/EC2Wrapper.cs#L714) +- [ReleaseAddress](Actions/EC2Wrapper.cs#L802) +- [RunInstances](Actions/EC2Wrapper.cs#L837) +- [StartInstances](Actions/EC2Wrapper.cs#L890) +- [StopInstances](Actions/EC2Wrapper.cs#L930) +- [TerminateInstances](Actions/EC2Wrapper.cs#L971) + + + + +## Run the examples + +### Instructions + +For general instructions to run the examples, see the +[README](../README.md#building-and-running-the-code-examples) in the `dotnetv4` folder. + +Some projects might include a settings.json file. Before compiling the project, +you can change these values to match your own account and resources. Alternatively, +add a settings.local.json file with your local settings, which will be loaded automatically +when the application runs. + +After the example compiles, you can run it from the command line. To do so, navigate to +the folder that contains the .csproj file and run the following command: + +``` +dotnet run +``` + +Alternatively, you can run the example from within your IDE. + + + + + +#### Hello Amazon EC2 + +This example shows you how to get started using Amazon EC2. + + +#### Learn the basics + +This example shows you how to do the following: + +- Create a key pair and security group. +- Select an Amazon Machine Image (AMI) and compatible instance type, then create an instance. +- Stop and restart the instance. +- Associate an Elastic IP address with your instance. +- Connect to your instance with SSH, then clean up resources. + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../README.md#Tests) +in the `dotnetv4` folder. + + + + + + +## Additional resources + +- [Amazon EC2 User Guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html) +- [Amazon EC2 API Reference](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Welcome.html) +- [SDK for .NET Amazon EC2 reference](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/NEC2.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/dotnetv4/EC2/Scenarios/EC2_Basics/Basics.csproj b/dotnetv4/EC2/Scenarios/EC2_Basics/Basics.csproj new file mode 100644 index 00000000000..e6364057c95 --- /dev/null +++ b/dotnetv4/EC2/Scenarios/EC2_Basics/Basics.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/dotnetv4/EC2/Scenarios/EC2_Basics/EC2Basics.cs b/dotnetv4/EC2/Scenarios/EC2_Basics/EC2Basics.cs new file mode 100644 index 00000000000..f17d062d5af --- /dev/null +++ b/dotnetv4/EC2/Scenarios/EC2_Basics/EC2Basics.cs @@ -0,0 +1,326 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +using Ec2_Basics; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Basics; + +// snippet-start:[EC2.dotnetv4.Main] +/// +/// Show Amazon Elastic Compute Cloud (Amazon EC2) Basics actions. +/// +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. + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon EC2 and Amazon Simple Systems + // Management (Amazon SSM) Service. + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddAWSService() + .AddTransient() + .AddTransient() + ) + .Build(); + + SetUpServices(host); + + var uniqueName = Guid.NewGuid().ToString(); + keyPairName = "mvp-example-key-pair" + uniqueName; + groupName = "ec2-scenario-group" + uniqueName; + var groupDescription = "A security group created for the EC2 Basics scenario."; + + try + { + // 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 => + { + _ec2Wrapper.DisplaySecurityGroupInfoAsync(group); + }); + _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."); + await _ec2Wrapper.AuthorizeSecurityGroupIngress(groupName); + + secGroups = await _ec2Wrapper.DescribeSecurityGroups(secGroupId); + Console.WriteLine($"Now let's look at the permissions again."); + secGroups.ForEach(group => + { + _ec2Wrapper.DisplaySecurityGroupInfoAsync(group); + }); + _uiMethods.PressEnter(isInteractive); + + // 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(); + + var images = await _ec2Wrapper.DescribeImages(imageIds); + + var i = 1; + images.ForEach(image => + { + Console.WriteLine($"\t{i++}\t{image.Description}"); + }); + + 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); + } + + var selectedInstanceType = instanceTypes[choice - 1].InstanceType; + + // Create an EC2 instance. + _uiMethods.DisplayTitle("Creating an EC2 Instance"); + instanceId = await _ec2Wrapper.RunInstances(selectedImage.ImageId, + selectedInstanceType, keyPairName, secGroupId); + + _uiMethods.PressEnter(isInteractive); + + var instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("New Instance Information"); + _ec2Wrapper.DisplayInstanceInformation(instance); + + Console.WriteLine( + "\nYou can use SSH to connect to your instance. For example:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + + _uiMethods.PressEnter(isInteractive); + + Console.WriteLine( + "Now we'll stop the instance and then start it again to see what's changed."); + + await _ec2Wrapper.StopInstances(instanceId); + + Console.WriteLine("Now let's start it up again."); + await _ec2Wrapper.StartInstances(instanceId); + + Console.WriteLine("\nLet's see what changed."); + + instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("New Instance Information"); + _ec2Wrapper.DisplayInstanceInformation(instance); + + Console.WriteLine("\nNotice the change in the SSH information:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + + _uiMethods.PressEnter(isInteractive); + + 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); + + _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); + + // Start the instance again. + Console.WriteLine("Now let's start the instance again."); + await _ec2Wrapper.StartInstances(instanceId); + + Console.WriteLine("\nLet's see what changed."); + + instance = await _ec2Wrapper.DescribeInstance(instanceId); + _uiMethods.DisplayTitle("Instance information"); + _ec2Wrapper.DisplayInstanceInformation(instance); + + Console.WriteLine("\nHere is the SSH information:"); + Console.WriteLine( + $"\tssh -i {tempFileName} ec2-user@{instance.PublicIpAddress}"); + + Console.WriteLine("Let's stop and start the instance again."); + _uiMethods.PressEnter(isInteractive); + + await _ec2Wrapper.StopInstances(instanceId); + + Console.WriteLine("\nThe instance has stopped."); + + Console.WriteLine("Now let's start it up again."); + await _ec2Wrapper.StartInstances(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); + + await Cleanup(); + } + catch (Exception ex) + { + _logger.LogError(ex, "There was a problem with the scenario, starting cleanup."); + await Cleanup(); + } + + _uiMethods.DisplayTitle("EC2 Basics Scenario completed."); + _uiMethods.PressEnter(isInteractive); + } + + /// + /// Set up the services and logging. + /// + /// + public static void SetUpServices(IHost host) + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + _logger = new Logger(loggerFactory); + + // 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."); + + Console.WriteLine("Disassociate the Elastic IP address and release it."); + // Disassociate the Elastic IP address. + await _ec2Wrapper.DisassociateIp(associationId); + + // Delete the Elastic IP address. + 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}."); + await _ec2Wrapper.DeleteSecurityGroup(secGroupId); + + // Delete the RSA key pair. + Console.WriteLine($"Deleting the key pair: {keyPairName}"); + await _ec2Wrapper.DeleteKeyPair(keyPairName); + Console.WriteLine("Deleting the temporary file with the key information."); + _ec2Wrapper.DeleteTempFile(tempFileName); + _uiMethods.PressEnter(isInteractive); + } +} +// snippet-end:[EC2.dotnetv4.Main] \ No newline at end of file diff --git a/dotnetv4/EC2/Scenarios/EC2_Basics/UIMethods.cs b/dotnetv4/EC2/Scenarios/EC2_Basics/UIMethods.cs new file mode 100644 index 00000000000..2c3d7f7f882 --- /dev/null +++ b/dotnetv4/EC2/Scenarios/EC2_Basics/UIMethods.cs @@ -0,0 +1,59 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace Ec2_Basics; + +public class UiMethods +{ + public readonly string SepBar = new string('-', 88); + + /// + /// Show information about the scenario. + /// + public void DisplayOverview() + { + DisplayTitle("Welcome to the Amazon Elastic Compute Cloud (Amazon EC2) get started with instances demo."); + + Console.WriteLine("This example application does the following:"); + Console.WriteLine("\t 1. Creates an RSA key pair."); + Console.WriteLine("\t 2. Saves the key pair to a file in a temporary folder."); + Console.WriteLine("\t 3. Creates a security group with an inbound rule allowing this computer to SSH to the security group."); + Console.WriteLine("\t 4. Displays information for the security group."); + Console.WriteLine("\t 5. Gets a list of Amazon Linux 2 Amazon Machine Images (AMIs)\n\t\tand selects one."); + Console.WriteLine("\t 6. Gets a list of instance types and selects one."); + Console.WriteLine("\t 8. Creates an EC2 instance."); + Console.WriteLine("\t 9. Waits for the instance to be ready and displays its information."); + Console.WriteLine("\t10. Displays the SSH connection information."); + Console.WriteLine("\t11. Stops the instance."); + Console.WriteLine("\t12. Starts the instance again."); + Console.WriteLine("\t13. Displays the SSH connection information again to show that it has changed."); + Console.WriteLine("\t14. Allocates an Elastic IP and associates it to the instance."); + Console.WriteLine("\t15. Displays the SSH connection information for the instance."); + Console.WriteLine("\t16. Disassociates the Elastic IP and deletes it."); + Console.WriteLine("\t17. Terminates the instance and waits for termination to be complete."); + Console.WriteLine("\t18. Deletes the security group."); + Console.WriteLine("\t19. Deletes the key pair."); + } + + /// + /// Display a message and wait until the user presses enter. + /// + public void PressEnter(bool interactive) + { + Console.Write("\nPlease press to continue. "); + if (interactive) + _ = Console.ReadLine(); + } + + /// + /// 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(strTitle); + Console.WriteLine(SepBar); + } +} \ No newline at end of file diff --git a/dotnetv4/EC2/Scenarios/EC2_Basics/Usings.cs b/dotnetv4/EC2/Scenarios/EC2_Basics/Usings.cs new file mode 100644 index 00000000000..900533b5c09 --- /dev/null +++ b/dotnetv4/EC2/Scenarios/EC2_Basics/Usings.cs @@ -0,0 +1,7 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using Amazon.EC2; +global using Amazon.SimpleSystemsManagement; +global using EC2Actions; +global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/dotnetv4/EC2/Tests/EC2Tests.csproj b/dotnetv4/EC2/Tests/EC2Tests.csproj new file mode 100644 index 00000000000..0f1dcab08e3 --- /dev/null +++ b/dotnetv4/EC2/Tests/EC2Tests.csproj @@ -0,0 +1,43 @@ + + + + net8.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/dotnetv4/EC2/Tests/EC2WrapperTests.cs b/dotnetv4/EC2/Tests/EC2WrapperTests.cs new file mode 100644 index 00000000000..f915cebc8c8 --- /dev/null +++ b/dotnetv4/EC2/Tests/EC2WrapperTests.cs @@ -0,0 +1,79 @@ +// 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) +/// Basics scenario. +/// +public class EC2WrapperTests +{ + private AmazonEC2Client _client = null!; + private EC2Wrapper _ec2Wrapper = null!; + private SsmWrapper _ssmWrapper = null!; + + /// + /// Verifies the scenario with an integration test. No errors should be logged. + /// + /// Async task. + [Fact] + [Trait("Category", "Integration")] + public async Task TestScenario() + { + // Arrange. + EC2Basics.isInteractive = false; + var loggerScenarioMock = new Mock>(); + var loggerWrapperMock = new Mock>(); + + _client = new AmazonEC2Client(); + + _ec2Wrapper = new EC2Wrapper(_client, loggerWrapperMock.Object); + _ssmWrapper = new SsmWrapper(new AmazonSimpleSystemsManagementClient()); + + 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/dotnetv4/EC2/Tests/Usings.cs b/dotnetv4/EC2/Tests/Usings.cs new file mode 100644 index 00000000000..ca8cbead821 --- /dev/null +++ b/dotnetv4/EC2/Tests/Usings.cs @@ -0,0 +1,10 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using Amazon.EC2; +global using Amazon.SimpleSystemsManagement; +global using EC2Actions; +global using Xunit; + +// Optional. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/dotnetv4/EC2/Tests/testsettings.json b/dotnetv4/EC2/Tests/testsettings.json new file mode 100644 index 00000000000..ffeb7fbf1cd --- /dev/null +++ b/dotnetv4/EC2/Tests/testsettings.json @@ -0,0 +1,12 @@ +{ + "GroupDescription": "This is a group created for integration tests.", + "GroupName": "test-group-name", + "ImageId": "ami-02dd04850de58599e", + "InstanceType": "t2.micro", + "KeyPairName": "test-example-key-pair", + "VpcId": "vpc-1a2b3c4d", + "BucketName": "", + "SubnetId": "subnet-012345678912345606", + "SecurityGroupId": "sg-012345678912345606", + "WaitTimeMilliseconds": 300000 +}