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
+}