AWS: Lambda -  copy EC2 tags to its EBS, part 2  -  create a Lambda function
Tags: aws, serverless, devops, tutorial
Published: 2021-10-14 09:44:37

---
title: AWS: Lambda -  copy EC2 tags to its EBS, part 2  -  create a Lambda function
published: true
date: 2021-10-13 05:52:27 UTC
tags: aws,serverless,devops,tutorial
canonical_url: https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/
---

### AWS: Lambda — copy EC2 tags to its EBS, part 2 — create a Lambda function

![](https://cdn-images-1.medium.com/max/365/1*6dXEkU-sfm3IrnC9WoL1QA.png)

let’s proceed in our journey of the AWS Lambda function, which will copy an EC2’s AWS Tags to all EBS volumes, attached to it.

In the first part, [AWS: Lambda — copy EC2 tags to its EBS, part 1 — Python and boto3](https://itnext.io/aws-lambda-copy-ec2-tags-to-its-ebs-part-1-python-and-boto3-73d7bf171aef?source=friends_link&sk=eef97d0c9832a983409359e4ca4b1b8c), we wrote a Python script that can get all EC2 instances in an AWS Region, then for every EC2 it grabs its EBS volumes, and then will copy all AWS Tags from the EC2 to all its EBS plus will add one additional.

In this part, we will create an [AWS Lambda](https://rtfm.co.ua/en/aws-lambda-functions-and-overview-and-integration-with-aws-api-gateway/) function that will be triggered with the AWS CloudWatch Events.

### Contents

- [Creating an AWS Lambda function](https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/#Creating_an_AWS_Lambda_function)
- [Adding an Amazon EventBridge (CloudWatch Events) trigger](https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/#Adding_an_Amazon_EventBridge_CloudWatch_Events_trigger)
- [AWS Lambda: UnauthorizedOperation](https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/#AWS_Lambda_UnauthorizedOperation)
- [Parsing a Lambda event](https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/#Parsing_a_Lambda_event)

### Creating an AWS Lambda function

Go to the AWS Lambda, create a new function from the ”_Author from scratch_”, with the Runtime set as Python 3.8:

![](https://cdn-images-1.medium.com/max/659/0*AncZhURUJ3XDcJq7.png)

In the _Execution role_ leave the “_Create a new role_” — later, we will add additional permissions to it with an IAM Policy.

Paste the script’s code to the function, at the end of the code update the `lambda_hanlder()` execution and in its arguments instead of the "_0, 0"_ from the previous post, set the `event, context`:

![](https://cdn-images-1.medium.com/max/739/0*dY7mfGq-JTb5GrgA.png)

To check the content of the `event` object, which we will use later to get an EC2 ID, add its output to the function's log.

Add `import json` and `print("CONTEXT: " + json.dumps(event))`:

![](https://cdn-images-1.medium.com/max/388/0*rhVJwLtzoC77pte9.png)

Also, for the `ec2 = boto3.resource()` call remove AWS keys, and leave the only type of the resource - "_ec2_":

![](https://cdn-images-1.medium.com/max/370/0*SfZLB9qeOt13CUNW.png)

Do not forget to press the _Deploy_ button to apply your changes to the AWS Lambda.

#### Adding an Amazon EventBridge (CloudWatch Events) trigger

Next, we need to run this function each time when a new EC2 is launched in a region.

Here, we can use the Amazon EventBridge (former CloudWatch Events). Go to the _CloudWatch Events > Rules_, click on the _Create rule_:

![](https://cdn-images-1.medium.com/max/387/0*buFO9aviAoUP-HzO.png)

In the _Event Pattern_ choose the _Service Name == EC2,_ in the _Event Type_ choose the “_EC2 Instance State-change Notification_”, and in the _Specific state(s)_ choose _running_:

![](https://cdn-images-1.medium.com/max/1024/0*_lnRwmeKgfGXq3va.png)

On the right side in the _Add target_ choose the AWS Lambda function that we’ve created above:

![](https://cdn-images-1.medium.com/max/1024/0*gt1mLEcBM04vYbqO.png)

At the end of the page, in the _Show sample event(s),_ we can see an example of the `event` object that will be passed to the function to get an EC2 ID:

![](https://cdn-images-1.medium.com/max/431/0*Ip1w0nyY3kxVKXSq.png)

Save the new rule:

![](https://cdn-images-1.medium.com/max/1024/0*APiBtiFvLoVxqL58.png)

Check if it was added to the Lambda function as a trigger:

![](https://cdn-images-1.medium.com/max/796/0*ezPDRkytxdsyHJNP.png)

And let’s check how this will work.

In the CloudWatch Rules open the Rule’s monitoring:

![](https://cdn-images-1.medium.com/max/578/0*f5d650iwwz5m29km.png)

Spin up a new EC2, for example by triggering an AutoScale Group:

![](https://cdn-images-1.medium.com/max/586/0*CLfif0R78VujO5nB.png)

On the CloudWatch graph we can see, that the Rule was triggered:

![](https://cdn-images-1.medium.com/max/1024/0*EDf5cZmA6JxyOqU_.png)

Check the Lambda function’s logs:

![](https://cdn-images-1.medium.com/max/944/0*NHvftqQ5QQlP_Ybp.png)

#### AWS Lambda: UnauthorizedOperation

In the logs, we are interested in two records.

The first one  -  is the `event` content, that was saved to the log from the `print("CONTEXT: " + json.dumps(event))`:

![](https://cdn-images-1.medium.com/max/635/0*YWBt-rtVm9Loauqs.png)

And the second one is the **_“ClientError: An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation”_** error:

![](https://cdn-images-1.medium.com/max/1024/0*wqeYfb_uB-tQBQCP.png)

The issue here is obvious enough: our Lambda function now is using an IAM Role, that was created during the function’s creation, and it has no permissions for API calls for the EC2 actions:

![](https://cdn-images-1.medium.com/max/1024/0*r_rjV5DuTvNdpH6O.png)

Go to the AWS IAM, find the Role, click on the _Attach policies_:

![](https://cdn-images-1.medium.com/max/576/0*ZHj4GRp489k2vjSv.png)

Create a new policy:

![](https://cdn-images-1.medium.com/max/372/0*cClhxP5o8lumvYG7.png)

Describe EC2 permissions here:

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DeleteTags",
                "ec2:CreateTags",
                "ec2:DescribeVolumes"
            ],
            "Resource": "*"
        }
    ]
}
```

Save it:

![](https://cdn-images-1.medium.com/max/1024/0*d3E8aKfhlpXDbbK9.png)

Go back to the Lambda function’s IAM Role, and add a new Policy:

![](https://cdn-images-1.medium.com/max/643/0*EHTosbK9Y35R1zy4.png)

Repeat a new EC2 launch and check the function’s logs again:

![](https://cdn-images-1.medium.com/max/1024/0*h_6zuWUSlkXifeXy.png)

Yay! “ **_It works!_** ” ©

Next, need to update the code to get the `instance-id` from the `event` object.

#### Parsing a Lambda event

We’ve already seen an example of the event when we've created the CloudWatch Rule, and we know that it will be passed to the `lambda_handler()` function as a python-dictionary with a set of keys:

```
{
    "version": "0",
    "id": "a99597e5-90a5-5ac3-3aba-da3ecd51452a",
    "detail-type": "EC2 Instance State-change Notification",
    "source": "aws.ec2",
    "account": "534***385",
    "time": "2021-10-11T09:02:09Z",
    "region": "eu-west-3",
    "resources": [
        "arn:aws:ec2:eu-west-3:534***385:instance/i-0cc24729109ba61e5"
    ],
    "detail": {
        "instance-id": "i-0cc24729109ba61e5",
        "state": "running"
    }
}
```

And from here we need to grab a value of the detail.instance-id element.

Add a new variable to the script, let’s call it `instance_id`, and it will keep a value from the `event["detail"]["instance-id"]`, and then by using this ID, we are creating a new object of the `ec2.Instance` class, the `ec2.Instance(instance_id)`:

![](https://cdn-images-1.medium.com/max/505/0*-0OqVvWyRZRBZqY1.png)

Instead of running a new EC2 manually, we can use the _Test_ for our Lambda by passing an `event` object to it.

Create a new test event:

![](https://cdn-images-1.medium.com/max/314/0*19iFUfbnU2aThznT.png)

Add a data similar to that we will get from the CloudWatch:

![](https://cdn-images-1.medium.com/max/591/0*JCLEKB_hp44XQVTw.png)

```
{
    "version": "0",
    "id": "a99597e5-90a5-5ac3-3aba-da3ecd51452a",
    "detail-type": "EC2 Instance State-change Notification",
    "source": "aws.ec2",
    "account": "534***385",
    "time": "2021-10-11T09:02:09Z",
    "region": "eu-west-3",
    "resources": [
        "arn:aws:ec2:eu-west-3:534***385:instance/i-0cc24729109ba61e5"
    ],
    "detail": {
        "instance-id": "i-0cc24729109ba61e5",
        "state": "running"
    }
}
```

Run the test:

![](https://cdn-images-1.medium.com/max/793/0*AiaNomfoVNnxcddx.png)

**_“It works!”_** _ ©_

And now let’s spin up a common EC2 to check the whole scheme including CloudWatch Event, Lambda’s trigger, and its execution results.

Trigger an AutoScaling Group:

![](https://cdn-images-1.medium.com/max/634/0*NxQ_Yblez-fXQDAM.png)

A new EC2 _i-051f2e1f1bc8d332b_ was created, check Lambda’s logs:

![](https://cdn-images-1.medium.com/max/1024/0*aIv9AK6HkfWWHyuT.png)

Find an EBS of this EC2:

![](https://cdn-images-1.medium.com/max/712/0*i_PkEzSnfLycTbFd.png)

And check its Tags:

![](https://cdn-images-1.medium.com/max/678/0*1p36vU2lo91tiDCB.png)

And the function’s code now is the next:

```
#!/usr/bin/env python

import os
import json
import boto3

def lambda_handler(event, context):

    ec2 = boto3.resource('ec2')
    instance_id = event["detail"]["instance-id"]
    instance = ec2.Instance(instance_id)

    print("[DEBUG] EC2\n\t\tID: " + str(instance))
    print("\tEBS")

    for vol in instance.volumes.all():

        vol_id = str(vol)
        print("VOLUME: " + str(vol))

        device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')"
        print("\t\tID: " + vol_id + "\n\t\tDev: " + device_id)

        role_tag = vol.create_tags(Tags=set_role_tag(vol))
        ec2_tags = vol.create_tags(Tags=copy_ec2_tags(instance))
        print("\t\tTags set:\n\t\t\t" + str(role_tag) + "\n\t\t\t" + str(ec2_tags) + "\n")

def is_pvc(vol): 

    try:
        for tag in vol.tags:
            if tag['Key'] == 'kubernetes.io/created-for/pvc/name':
                return True
                break
    except TypeError:
            return False

def set_role_tag(vol):
    
    device = vol.attachments[0]['Device']
    tags_list = []
    values = {}
        
    if is_pvc(vol):
        values['Key'] = "Role"
        values['Value'] = "PvcDisk"
        tags_list.append(values)
    elif device == "/dev/xvda":
        values['Key'] = "Role"
        values['Value'] = "RootDisk"
        tags_list.append(values)
    else:   
        values['Key'] = "Role"
        values['Value'] = "DataDisk"
        tags_list.append(values)

    return tags_list

    
def copy_ec2_tags(instance):
            
    tags_list = []
    values = {} 
        
    for instance_tag in instance.tags:

        if instance_tag['Key'] == 'Env':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'Tier':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'DataClass':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'JiraTicket':
            tags_list.append(instance_tag)

    return tags_list

if __name__ == " __main__":
    lambda_handler(event, context)
```

Done.

_Originally published at_ [_RTFM: Linux, DevOps, and system administration_](https://rtfm.co.ua/en/aws-lambda-copy-ec2-tags-to-its-ebs-part-2-create-a-lambda-function/)_._

* * *