AWSSDK.EC2 (for AWS EC2 )
Tags: cloud, csharp, aws, dotnet
Published: 2021-09-04 16:24:18

✦ The `AWS SDK` for `.NET` supports `Amazon EC2`, which is a web service that provides resizable computing capacity. You use this computing capacity to build and host your software systems.

First of all, let's have a brief about `Amazon EC2` in AWS...
#➽What is Amazon EC2 ?
☞ `Amazon Elastic Compute Cloud (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.
☞ Read more about [Amazon EC2](https://docs.aws.amazon.com/ec2/#amazon-ec2)
_____________________________________________________
#➽Installing AWSSDK.EC2 :
☞`AWSSDK.EC2` is installed mainly from [Nuget] (https://www.nuget.org/)
☞There is 3 ways to install `AWSSDK.EC2`, they are the same as installing `AWSSDK.S3` from [Part 1 of this series](https://dev.to/ahmedadel/aws-sdks-for-net-1-awssdk-s3-5e36)
☞let's use the easiest one, from Package Manager Console by using the Install-Package command. 
```csharp
PM> Install-Package AWSSDK.EC2
```
_____________________________________________________
🌟 Second step is to connect to our AWS account using __ Access keys (Access Key ID and Secret Access Key)__, this was explained before briefly in the [first article under (Get AWS Access keys) ](https://dev.to/ahmedadel/aws-sdks-for-net-1-awssdk-s3-5e36#%E2%9E%BDso-how-can-we-use-it-)
_________________________________________________________
__Let's now check what we can do with this SDK :__
# ☞ Working with Security Groups:
✦ A security group acts as a virtual firewall that controls the network traffic for one or more EC2 instances. By default, EC2 associates your instances with a security group that allows no inbound traffic. You can create a security group that allows your EC2 instances to accept certain traffic.
✦ Read more about security groups in the [EC2 user guide for Linux](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-network-security.html) and the [EC2 user guide for Windows](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/using-network-security.html).
__1- Creating security groups:__
```csharp
// Method to create a new security group (either EC2-Classic or EC2-VPC)
// If vpcID is empty, the security group will be for EC2-Classic
private static async Task<List<SecurityGroup>> CreateSecurityGroup(
  IAmazonEC2 ec2Client, string groupName, string vpcID)
{
  // See if one or more security groups with that name
  // already exist in the given VPC. If so, return the list of them.
  var securityGroups = await FindSecurityGroups(ec2Client, groupName, vpcID);
  if (securityGroups.Count > 0)
  {
    Console.WriteLine($"\n Security groups with name {groupName} already exists.\n");
    return securityGroups;
  }
  // If the security group doesn't already exists, create it.
  var createRequest = new CreateSecurityGroupRequest{ GroupName = groupName };
  if(string.IsNullOrEmpty(vpcID))
  {
    createRequest.Description = "My .NET example security group for EC2-Classic";
  }
  else
  {
    createRequest.VpcId = vpcID;
    createRequest.Description = "My .NET example security group for EC2-VPC";
  }
  CreateSecurityGroupResponse createResponse = await ec2Client.CreateSecurityGroupAsync(createRequest);
  // Return the new security group
  DescribeSecurityGroupsResponse describeResponse =
    await ec2Client.DescribeSecurityGroupsAsync(new DescribeSecurityGroupsRequest{
      GroupIds = new List<string>() { createResponse.GroupId }
    });
  return describeResponse.SecurityGroups;
}

// Method to determine if a security group with the specified name
// already exists in the VPC
private static async Task<List<SecurityGroup>> FindSecurityGroups(
  IAmazonEC2 ec2Client, string groupName, string vpcID)
{
  var request = new DescribeSecurityGroupsRequest();
  request.Filters.Add(new Filter{
    Name = "group-name",
    Values = new List<string>() { groupName }
  });
  if(!string.IsNullOrEmpty(vpcID))
    request.Filters.Add(new Filter{
      Name = "vpc-id",
      Values = new List<string>() { vpcID }
    });
  var response = await ec2Client.DescribeSecurityGroupsAsync(request);
  return response.SecurityGroups;
}
```

__2- Loop through your security groups:__
```csharp
// Method to enumerate the security groups
private static async Task EnumerateGroups(IAmazonEC2 ec2Client, string vpcID)
{
  // A request object, in case we need it.
  var request = new DescribeSecurityGroupsRequest();
  // Put together the properties, if needed
  if(!string.IsNullOrEmpty(vpcID))
  {
    // We have a VPC ID. Find the security groups for just that VPC.
    Console.WriteLine($"\nGetting security groups for VPC {vpcID}...\n");
    request.Filters.Add(new Filter
    {
      Name = "vpc-id",
      Values = new List<string>() { vpcID }
    });
  }
  // Get the list of security groups
  DescribeSecurityGroupsResponse response =
    await ec2Client.DescribeSecurityGroupsAsync(request);
  // Display the list of security groups.
  foreach (SecurityGroup item in response.SecurityGroups)
  {
    Console.WriteLine("Security group: " + item.GroupId);
    Console.WriteLine("\tGroupId: " + item.GroupId);
    Console.WriteLine("\tGroupName: " + item.GroupName);
    Console.WriteLine("\tVpcId: " + item.VpcId);
    Console.WriteLine();
  }
}
```
__3- Update security group (Add inbound rule):__
```csharp
// Method that adds a TCP ingress rule to a security group
private static async Task AddIngressRule(
  IAmazonEC2 eC2Client, string groupID, string ipAddress, int port)
{
  // Create an object to hold the request information for the rule.
  // It uses an IpPermission object to hold the IP information for the rule.
  var ingressRequest = new AuthorizeSecurityGroupIngressRequest{
    GroupId = groupID};
  ingressRequest.IpPermissions.Add(new IpPermission{
    IpProtocol = "tcp",
    FromPort = port,
    ToPort = port,
    Ipv4Ranges = new List<IpRange>() { new IpRange { CidrIp = ipAddress } }
  });
  // Create the inbound rule for the security group
  AuthorizeSecurityGroupIngressResponse responseIngress =
    await eC2Client.AuthorizeSecurityGroupIngressAsync(ingressRequest);
  Console.WriteLine($"\nNew RDP rule was written in {groupID} for {ipAddress}.");
  Console.WriteLine($"Result: {responseIngress.HttpStatusCode}");
}
```
____________________________________________________
# ☞ Working with Amazon EC2 key pairs:
✦ `Amazon EC2` uses __public–key cryptography__ to encrypt and decrypt login information. Public–key cryptography uses a public key to encrypt data, and then the recipient uses the private key to decrypt the data. The public and private keys are known as a key pair. 
✦ Read more about `Amazon EC2 key pairs` in the [EC2 user guide for Linux](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) or the [EC2 user guide for Windows](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-key-pairs.html).

__1- Create the key pair:__
```csharp
// Method to create a key pair and save the key material in a PEM file
private static async Task CreateKeyPair(
  IAmazonEC2 ec2Client, string keyPairName, string pemFileName)
{
  // Create the key pair
  CreateKeyPairResponse response =
    await ec2Client.CreateKeyPairAsync(new CreateKeyPairRequest{
      KeyName = keyPairName
    });
  Console.WriteLine($"\nCreated new key pair: {response.KeyPair.KeyName}");
  // Save the private key in a PEM file
  using (var s = new FileStream(pemFileName, FileMode.Create))
  using (var writer = new StreamWriter(s))
  {
    writer.WriteLine(response.KeyPair.KeyMaterial);
  }
}
```
__2- Display available key pairs:__
```csharp
// Method to show the key pairs that are available
private static async Task EnumerateKeyPairs(IAmazonEC2 ec2Client)
{
  DescribeKeyPairsResponse response = await ec2Client.DescribeKeyPairsAsync();
  Console.WriteLine("Available key pairs:");
  foreach (KeyPairInfo item in response.KeyPairs)
    Console.WriteLine($"  {item.KeyName}");
}
```
__3- Delete the key pair:__
```csharp
// Method to delete a key pair
private static async Task DeleteKeyPair(IAmazonEC2 ec2Client, string keyName)
{
  await ec2Client.DeleteKeyPairAsync(new DeleteKeyPairRequest{
    KeyName = keyName});
  Console.WriteLine($"\nKey pair {keyName} has been deleted (if it existed).");
}
```
_____________________________________________
# ☞ Seeing your Amazon EC2 Regions and Availability Zones:
✦ `Amazon EC2` is hosted in multiple locations worldwide. These locations are composed of Regions and Availability Zones. Each Region is a separate geographic area that has multiple, isolated locations known as __Availability Zones__.
✦ Read more about Regions and Availability Zones in the [EC2 user guide for Linux](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) or the [EC2 user guide for Windows](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/EC2Win_Infrastructure.html#EC2Win_Regions).

```csharp
using System;
using System.Threading.Tasks;
using Amazon.EC2;
using Amazon.EC2.Model;

namespace EC2RegionsAndZones
{
  class Program
  {
    static async Task Main(string[] args)
    {
      // Create the EC2 client
      var ec2Client = new AmazonEC2Client();

      // Display the Regions and Availability Zones
      await DescribeRegions(ec2Client);
      await DescribeAvailabilityZones(ec2Client);
    }

    // Method to display Regions
    private static async Task DescribeRegions(IAmazonEC2 ec2Client)
    {
      Console.WriteLine("\nRegions that are enabled for the EC2 client:");
      DescribeRegionsResponse response = await ec2Client.DescribeRegionsAsync();
      foreach (Region region in response.Regions)
        Console.WriteLine(region.RegionName);
    }


    //
    // Method to display Availability Zones
    private static async Task DescribeAvailabilityZones(IAmazonEC2 ec2Client)
    {
      Console.WriteLine("\nAvailability Zones for the EC2 client's region:");
      DescribeAvailabilityZonesResponse response = await ec2Client.DescribeAvailabilityZonesAsync();
      foreach (AvailabilityZone az in response.AvailabilityZones)
        Console.WriteLine(az.ZoneName);
    }
  }
}
```
_____________________________________________
# ☞ Working with Amazon EC2 instances:
__1- Launch an instance:__
```csharp
// Method to launch the instances
// Returns a list with the launched instance IDs
private static async Task<List<string>> LaunchInstances(
  IAmazonEC2 ec2Client, RunInstancesRequest requestLaunch)
{
  var instanceIds = new List<string>();
  RunInstancesResponse responseLaunch =
    await ec2Client.RunInstancesAsync(requestLaunch);
  Console.WriteLine("\nNew instances have been created.");
  foreach (Instance item in responseLaunch.Reservation.Instances)
  {
    instanceIds.Add(item.InstanceId);
    Console.WriteLine($"  New instance: {item.InstanceId}");
  }
  return instanceIds;
}
```
__2- Monitor the instance:__
```csharp
// Method to wait until the instances are running (or at least not pending)
private static async Task CheckState(IAmazonEC2 ec2Client, List<string> instanceIds)
{
  Console.WriteLine(
    "\nWaiting for the instances to start." +
    "\nPress any key to stop waiting. (Response might be slightly delayed.)");
  int numberRunning;
  DescribeInstancesResponse responseDescribe;
  var requestDescribe = new DescribeInstancesRequest{
    InstanceIds = instanceIds};
  // Check every couple of seconds
  int wait = 2000;
  while(true)
  {
    // Get and check the status for each of the instances to see if it's past pending.
    // Once all instances are past pending, break out.
    // (For this example, we are assuming that there is only one reservation.)
    Console.Write(".");
    numberRunning = 0;
    responseDescribe = await ec2Client.DescribeInstancesAsync(requestDescribe);
    foreach(Instance i in responseDescribe.Reservations[0].Instances)
    {
      // Check the lower byte of State.Code property
      // Code == 0 is the pending state
      if((i.State.Code & 255) > 0) numberRunning++;
    }
    if(numberRunning == responseDescribe.Reservations[0].Instances.Count)
      break;
    // Wait a bit and try again (unless the user wants to stop waiting)
    Thread.Sleep(wait);
    if(Console.KeyAvailable)
      break;
  }
  Console.WriteLine("\nNo more instances are pending.");
  foreach(Instance i in responseDescribe.Reservations[0].Instances)
  {
    Console.WriteLine($"For {i.InstanceId}:");
    Console.WriteLine($"  VPC ID: {i.VpcId}");
    Console.WriteLine($"  Instance state: {i.State.Name}");
    Console.WriteLine($"  Public IP address: {i.PublicIpAddress}");
    Console.WriteLine($"  Public DNS name: {i.PublicDnsName}");
    Console.WriteLine($"  Key pair name: {i.KeyName}");
  }
}
```
__3- Terminating an EC2 instance:__
```csharp
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.EC2;
using Amazon.EC2.Model;

namespace EC2TerminateInstance
{
  class Program
  {
    static async Task Main(string[] args)
    {
      if((args.Length == 1) && (args[0].StartsWith("i-")))
      {
        // Terminate the instance
        var ec2Client = new AmazonEC2Client();
        await TerminateInstance(ec2Client, args[0]);
      }
      else
      {
        Console.WriteLine("\nCommand-line argument missing or incorrect.");
        Console.WriteLine("\nUsage: EC2TerminateInstance instance-ID");
        Console.WriteLine("  instance-ID - The EC2 instance you want to terminate.");
        return;
      }
    }

    // Method to terminate an EC2 instance
    private static async Task TerminateInstance(IAmazonEC2 ec2Client, string instanceID)
    {
      var request = new TerminateInstancesRequest{
        InstanceIds = new List<string>() { instanceID }};
      TerminateInstancesResponse response =
        await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
          InstanceIds = new List<string>() { instanceID }
        });
      foreach (InstanceStateChange item in response.TerminatingInstances)
      {
        Console.WriteLine("Terminated instance: " + item.InstanceId);
        Console.WriteLine("Instance state: " + item.CurrentState.Name);
      }
    }
  }
}
```
_____________________________________________
# ☞ Amazon EC2 Spot Instance tutorial:
✦ `Spot Instances` enable you to request unused Amazon EC2 capacity for __less than the On-Demand price__. This can significantly lower your EC2 costs for applications that can be interrupted.
✦ Read more about `Spot Instances` in the [EC2 user guide for Linux](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html) or the [EC2 user guide for Windows](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/using-spot-instances.html).

__1- Creating a Spot Instance request:__
```csharp
// Method to create a Spot Instance request
private static async Task<SpotInstanceRequest> CreateSpotInstanceRequest(
  IAmazonEC2 ec2Client, string amiId, string securityGroupName,
  InstanceType instanceType, string spotPrice, int instanceCount)
{
  var launchSpecification = new LaunchSpecification{
    ImageId = amiId,
    InstanceType = instanceType
  };
  launchSpecification.SecurityGroups.Add(securityGroupName);
  var request = new RequestSpotInstancesRequest{
    SpotPrice = spotPrice,
    InstanceCount = instanceCount,
    LaunchSpecification = launchSpecification
  };
  RequestSpotInstancesResponse result = await ec2Client.RequestSpotInstancesAsync(request);
  return result.SpotInstanceRequests[0];
}
```
__2- Get status of your Spot Instance request:__
```csharp
// Method to get information about a Spot Instance request, including the status,
// instance ID, etc.
// It gets the information for a specific request (as opposed to all requests).
private static async Task<SpotInstanceRequest> GetSpotInstanceRequestInfo(
  IAmazonEC2 ec2Client, string requestId)
{
  var describeRequest = new DescribeSpotInstanceRequestsRequest();
  describeRequest.SpotInstanceRequestIds.Add(requestId);
  DescribeSpotInstanceRequestsResponse describeResponse =
    await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
  return describeResponse.SpotInstanceRequests[0];
}
```
__3- Clean up your Spot Instance requests:__
```csharp
// Method to cancel a Spot Instance request
private static async Task CancelSpotInstanceRequest(
  IAmazonEC2 ec2Client, string requestId)
{
  var cancelRequest = new CancelSpotInstanceRequestsRequest();
  cancelRequest.SpotInstanceRequestIds.Add(requestId);
  await ec2Client.CancelSpotInstanceRequestsAsync(cancelRequest);
}
```
__4- Clean up your Spot Instances:__
✦ To avoid unnecessary costs, it's important that you terminate any instances that were started from Spot Instance requests; simply canceling Spot Instance requests will not terminate your instances, which means that you'll continue to be charged for them.
```csharp
// Method to terminate a Spot Instance
private static async Task TerminateSpotInstance(
  IAmazonEC2 ec2Client, string requestId)
{
  var describeRequest = new DescribeSpotInstanceRequestsRequest();
  describeRequest.SpotInstanceRequestIds.Add(requestId);
  // Retrieve the Spot Instance request to check for running instances.
  DescribeSpotInstanceRequestsResponse describeResponse =
    await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
  // If there are any running instances, terminate them
  if(   (describeResponse.SpotInstanceRequests[0].Status.Code
          == "request-canceled-and-instance-running")
     || (describeResponse.SpotInstanceRequests[0].State == SpotInstanceState.Active))
  {
    TerminateInstancesResponse response =
      await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
        InstanceIds = new List<string>(){
          describeResponse.SpotInstanceRequests[0].InstanceId } });
    foreach (InstanceStateChange item in response.TerminatingInstances)
    {
      Console.WriteLine($"\n  Terminated instance: {item.InstanceId}");
      Console.WriteLine($"  Instance state: {item.CurrentState.Name}\n");
    }
  }
}
```
__References:__ [AWS official Documentation](https://docs.aws.amazon.com/index.html)