How to integrate AWS CodeBuild with Jira
Tags: jira, devops, lambda, codebuild
Published: 2023-07-21 15:01:50

## Introduction

This article will guide you to configure an automation flow that will update your **Jira** board to the right status. To implement this I assume you already have an **AWS** account and a pipeline deploy using **CodeBuild** in place. So you will be able to plug it into your code as the last part of it.

This piece of code will be added to the `buildspec.yml`, inside the `post-build` block. It means when the artifact is in fact deployed to the environment, then the automation will update the stories correctly.

For reference, check the `buildspec.yml` file [documentation](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html).

## Creating Lambda Function
To create a new Lambda function you new to go to Lambda's service:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fskvc8p4fobhgff5cipt.png)

Then, when creating the function, choose the `python3.9` runtime:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5k1ib2o8hxnr6zduml4u.png)

Once the new Lambda's **Python3.9** runtime does not support some dependencies we need to make it work, we will need to upload our package in a `zip` file. You can do this here:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qssd5dbva8kmmaho3hyu.png)

So you can use `docker` or `venv` to zip all the dependencies with the `python` file.

I'm using `docker` so the dependencies are in this path `/usr/local/lib/python3.12/site-packages`


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ha160ryxu9viyri6te0o.png)

Make sure the name of the lambda code file is `lambda_function.py` and it is at the same level as the dependencies so that **AWS** can validate it. 

## The Flow


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fluiqj39zjpu5tl8yr3a.png)

For instance, I'm using an **SCM** based on **Gitflow**, so I have the `develop` branch -- in which we will apply this -- and the `master` branch. This way allows me to compare those issues the automation should update the status.

By the way, another important thing is all of the commits should begin with the issue's `key`. Like this:
```bash
git commit -m "AE-28: <your-commit-message>"
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44dvxpg4zlmbe07qoxc2.png)

### CodeBuild

Every time the deploy is triggered manually, this snippet will be started as a `post-build` action.

Then it will verify on the remote repository if there were tickets deployed in the given job:
```yaml
- TICKETS_LIST=$(git log origin/develop --no-merges --pretty=format:"%B" --not "origin/master" | tr a-z A-Z | tr ' ,' '\n' | grep -o '[A-Z]\+-[0-9]\+' | sort --version-sort | uniq | tr '\n' ',')
```

The result of this command is:
```bash
AE-28,AE-27,AE-30,AE-34,AE-35,
```

After that, if the command's output is not `null`, **CodeBuild** will send it to the **Environment Variables** on **Lambda**, and then, send the **tickets list** to the **Environment Variables** on Lambda, and finally triggers the `jira_automation_tickets` function passing the status `3` which is the transition ID to move issues to the **QA** lane:
```yaml
- |
   if [ "$TICKETS_LIST" = "" ]; then 
       echo "No tickets deployed";
   else 
       aws lambda update-function-configuration --function-name jira_automation_tickets --environment "Variables={TICKETS_LIST='${TICKETS_LIST}'}"; 
       aws lambda invoke   \
       --function-name jira_automation_tickets  \
           --cli-binary-format raw-in-base64-out  \
               --payload '{"status": "3"}' output.txt; 
   fi
                
- cat output.txt
```

**Tip:** You can see that Environment Variable, on the **Lambda Service Dashboard**:

Configuration > Environment Variable


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bnig2e0pf5lwe444or9e.png)

**Tip:** To know what the `transition` id, use this command:
```bash
curl -u <jira-user>:<token-api> -X GET -H "Content-Type: application/json" "https://<your-domain-jira-board>.atlassian.net/rest/api/3/issue/<issue-number>/transitions" | jq '.[]' 
```
You will receive an array containing objects with all **transitions'** `id` as well as all **status'** `id`, like this one for **QA** which the **transition** `id` is `3` and the **status** `id` is `10004` -- I will use it here:

```json
{
    "id": "3",
    "name": "QA",
    "to": {
      "self": "https://<your-domain-jira-board>/rest/api/3/status/10004",
      "description": "",
      "iconUrl": "https://<your-domain-jira-board>/",
      "name": "QA",
      "id": "10004",
      "statusCategory": {
        "self": "https://<your-domain-jira-board>/rest/api/3/statuscategory/4",
        "id": 4,
        "key": "indeterminate",
        "colorName": "yellow",
        "name": "In Progress"
      }
    },
    "hasScreen": false,
    "isGlobal": true,
    "isInitial": false,
    "isAvailable": true,
    "isConditional": false,
    "isLooped": false
  }
```

### Lambda

We are using **Python 3.9** as the runtime for this Lambda function. To use **Jira SDK** we needed to import it and its dependencies with the Lambda code as a `zip` file.

The code is simple is a simple one and it's commented line-by-line. This code will be committed to our repository to maintain a history of it. And the zip file with the necessary dependencies is attached as well.

First of all, create the file `lambda_function.py` -- it should have this name to avoid AWS errors -- then, import all dependencies we will need:

```python
import boto3
import json
import os
import re
from jira import JIRA
from datetime import date
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
```

The main method must have to be named `lambda_handler`. So create this method and initialize the credentials variables, then use the **SDK** to authenticate:  

```python
def lambda_handler(event, context):
    # set credentials using basic authentication
    jira_url = 'https://<your-domain-jira-board>.atlassian.net'
    jira_username = '<user-name>'
    jira_api_token = '<api-token>'

    # Connect to Jira
    jira = JIRA(server=jira_url, basic_auth=(jira_username, jira_api_token))
```

Now use the `os` to get the **tickets list** from the **environment variables**:

```python
    # Get ticket list sent by CodeBuild and transform it in an Array/list
    ticket_list = re.split(r"\r\,|\,|\r", os.environ['TICKETS_LIST'])
```

Then, you can start to create the email template and initialize an empty list to control whether you should send an email or not. 

```python
    # Starts the email template
    email_body = """<p>List of tickets to be tested:</p>"""
    # Initiate an empty list/array to check its size later when it will send the email notification
    tickets_to_send_email = []
```

Now it's time to iterate that list and validate them. My criteria here was only to update the tickets that are not in the **QA** lane yet. In my case, the **QA** status id is `10004`. It will increment a line in the email body.

```python
    for ticket in ticket_list:
        if ticket != '':
            # instantiate Issue's object 
            issue = jira.issue(ticket)
            # get the current status
            current_status = issue.fields.status
            # 10004 is the id of the QA lane 
            if current_status.id != '10004':
                # increments the array to send notifications
                tickets_to_send_email.append(ticket)
                # include a issue link into the email template 
                email_body += f"""
                    <div>
                        <a href="{jira_url+'/browse/'+issue.key}">{issue.key} - {issue.fields.summary}</a>
                    </div>
                """
                # Change the status to status 3 -- QA
                jira.transition_issue(issue, event["status"]) 
```

Then, close the email body:

```python
    
    # closing the email template
    email_body += """<p>If you find an issue with the ticket reach out to the reporter.</p>
    """
```

After validating and updating the status of the issues on **Jira**, set a variable called `response_email` to receive the funtion's response. Also, you will need to format this email with the correct `MIME Type`, to send it through the **SES** service:

```python
    # setting default response to Lambda function
    response_email = "Email not sent"
    # checking if the array is populate to send email notification
    if len(tickets_to_send_email) > 0:
        # Compose the email subject and body
        subject = f'Tickets Available To Test In QA - {date.today()}'
        body = email_body
        # Create the email message
        email_message = MIMEMultipart()
        email_message['Subject'] = subject
        # Attach the HTML part
        html_part = MIMEText(body, 'html')
        email_message.attach(html_part)
        # Calling AWS SDK to send email
        ses_client = boto3.client('ses')
        response_email = ses_client.send_raw_email(
                Source='<authenticates-ses-email>',
                Destinations=['<email-1>, <email-2>'],
                RawMessage={'Data': email_message.as_string()}
            )
```

Finally, close your code returning a `JSON` with the `send_raw_email` method:

```python

    return {
        'statusCode': 200,
        'body': json.dumps(response_email)
    }
```

### Jira Board

Your board will be updated and those tickets that were merged in your development environment and are not in the QA lane already will be moved to this lane. This way the automation will assure the correct visibility.

### Simple Email Service

Once the Lambda code was finished and there are changes to notify, it will trigger the **SES** (Simple Email Service) to send an email notification to the emails you passed as the destination.

The email will contain instructions to QAs to access the ticket by the links that will be listed on the email body, like this:

> List of tickets to be tested:
> 
> AE-28: 'issue-title' 
> AE-27: 'issue-title' 
> AE-30: 'issue-title' 
> AE-34: 'issue-title' 
> AE-35: 'issue-title'
> 
> If you find an issue with the ticket reach out to the reporter.

## Conclusion

Make sure your **CodeBuild** job has permission to write in Lambda:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35quxpe97nxg9fcvvldv.png)

As well as the **Lambda** has permission to trigger the **SES** service:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrtcfqk0o600yesk4uap.png)

I'd be very happy if this article helps you anyhow. Feel free to collaborate with this solution. I'd like to see it getting as better as possible!