Automation Using GitLab Issue Templates with approval process

Automation Using GitLab Issue Templates with approval process

Introduction

GitLab Issue Templates allow teams to standardize and automate issue creation, ensuring that all necessary information is collected upfront and that processes are consistently followed. Automating issue creation using these templates can lead to better collaboration, streamlined workflows, and faster resolution of issues.

1) Kubernetes Pod Restart 2) Kubernetes Deployment Image Patch

What Are GitLab Issue Templates? GitLab issue templates are pre-defined formats for issues that provide a structured way to report bugs, suggest features, or handle tasks. They help ensure that team members include all relevant information when creating issues, reducing miscommunication and the need for follow-up.

Why Use GitLab Issue Templates?

Consistency: Ensure that all issues follow a uniform structure.

Speed: Quickly generate issues by filling in pre-set fields.

Clarity: Include necessary details, such as steps to reproduce bugs, acceptance criteria, and links to relevant documentation.

Automation: Automate the issue creation process by embedding workflows into the template.

Automating Template to Restart Kubernetes Pod and Patch Deployment

Setting Up a Template in Your Repository Issue templates are stored in your repository and can be reused across multiple issues. To create a template, follow these steps:

Navigate to your GitLab repository. Go to the .gitlab or issue_templates folder in the root directory. If the folder doesn't exist, create one and add a new Markdown file (.md) for each issue template. For example, you can name it

k8s_deployment_patch.md

### Environment
environment=

### Namespace
namespace=

### Deployment Name
deployment=

### Container Name
container_name=

### Image Name
image=

### Justification

/label ~"type::issue" ~"action::deployment-patch"

k8s_deployments_restart.md

### Environment
environment=

### Namespace
namespace=

### Deployment Name
deployment=

### Justification

/label ~"type::issue" ~"action::pod-restart"

Merge the Code And Verify

Project --> Issues --> Create New Issue

enter image description here

Lets Create Automation Code(Using Python)

1) Code To List Created Issue

import gitlab
import re
import os
import subprocess
from common import check_namespace_exists, check_deployment_exists, close_issue, approval_verify,extract_info,approver_list



def init_gitlab_connection(private_token):
    return gitlab.Gitlab('https://gitlab.com', private_token=private_token)

def process_issues(issues, project):
    for issue in issues:
        labels = set(issue.labels)
        comment = (f" 🤖 Its a automated genetated note. Waiting for approval from @{', '.join(approver_list)}")
        notes = issue.notes.list(get_all=True)
        if not notes:  
          issue.notes.create({'body': comment})
        if 'action::pod-restart' in labels:
            namespace, deployment, env, img, cn = extract_info(issue.description)   
            if approval_verify(issue):
                result = subprocess.run(['python', 'deployment_restart.py', namespace, deployment, env], capture_output=True, text=True)
                res = result.returncode
                if res == 0:
                   comment = (f"Deployment '{deployment}' in Namespace '{namespace}' has been restarted 🚀")
                else:
                   comment = (f"Deployment '{deployment}' or Namespace '{namespace}' not found 😔")    
                close_issue(issue,comment,res)    

        elif 'action::deployment-patch' in labels:
            namespace, deployment, env, img, cn = extract_info(issue.description)
            if approval_verify(issue):
                result = subprocess.run(['python', 'deployment_patch.py', namespace, deployment, env, img, cn], capture_output=True, text=True)
                res = result.returncode
                if res == 0:
                   comment = (f"Deployment '{deployment}' in Namespace '{namespace}' Patched sucessfully with {img} 🚀")
                else:
                   comment = (f"Deployment '{deployment}' or Namespace '{namespace}' not found 😔")           
                close_issue(issue,comment,res)    


def main():
    private_token = os.getenv('gitlab_token')
    gitlab_project = os.getenv('gitlab_project')
    gl = init_gitlab_connection(private_token)
    project = gl.projects.get(gitlab_project)
    issues = project.issues.list(state='opened')
    if not issues:
        print("No issues found.")
    else:
        process_issues(issues, project)

if __name__ == '__main__':
    main()


2) Code for Common work

from kubernetes import client, config
import argparse
import datetime
import sys
import re
approver_list = ['champ25']

config.load_kube_config()

def close_issue(issue,comment,res):
    notes = issue.notes.list(get_all=True)
    issue.notes.create({'body': comment})
    if res == 0:
       issue.state_event = 'close'
       issue.save()

def approval_verify(issue):
    notes = issue.notes.list(get_all=True)
    for note in notes:
        if 'approved' in note.body.lower() and note.author['username'] in approver_list:
            new_comment =  f"🤖 Approval has been noted by @{note.author.get('username')}.🤖"
            issue.notes.create({'body': new_comment})
            return True
    return False

def extract_info(description):
    namespace_pattern = re.compile(r'namespace=\s*(\S+)')
    deployment_pattern = re.compile(r'deployment=\s*(\S+)')
    env_pattern = re.compile(r'environment=\s*(\S+)')
    cn_pattern = re.compile(r'container_name=\s*(\S+)')
    img_pattern = re.compile(r'image=\s*(\S+)')

    namespace_match = namespace_pattern.search(description)
    deployment_match = deployment_pattern.search(description)
    env_match = env_pattern.search(description)
    cn_match = cn_pattern.search(description)
    img_match = img_pattern.search(description)

    namespace = namespace_match.group(1) if namespace_match else None
    deployment = deployment_match.group(1) if deployment_match else None
    env = env_match.group(1) if env_match else None
    cn = cn_match.group(1) if cn_match else None
    img = img_match.group(1) if img_match else None
    return namespace, deployment, env, img, cn

def check_namespace_exists(namespace_name):
    print(namespace_name)
    v1 = client.CoreV1Api()
    try:
        v1.read_namespace(name=namespace_name)
        return True
    except client.exceptions.ApiException as e:
        if e.status == 404:
            print(f"Namespace '{namespace_name}' does not exist.")
            sys.exit(1)
        else:
            print(f"Error occurred: {e}")
            sys.exit(1)
        return False

def check_deployment_exists(deployment_name, namespace_name):
    apps_v1 = client.AppsV1Api()
    try:
        apps_v1.read_namespaced_deployment(name=deployment_name, namespace=namespace_name)
        return True
    except client.exceptions.ApiException as e:
        if e.status == 404:
            print(f"Deployment '{deployment_name}' does not exist in namespace '{namespace_name}'.")
            sys.exit(1) 
        else:
            print(f"Error occurred: {e}")
            sys.exit(1) 
        return False

3) Code for Restarting Pods

from kubernetes import client, config
import argparse
import datetime
import sys
from common import check_namespace_exists, check_deployment_exists, close_issue, approval_verify,extract_info
def restart_deployment(namespace, deployment_name, env):
    if check_namespace_exists(namespace):
        if check_deployment_exists(deployment_name, namespace):
           api_instance = client.AppsV1Api()
           try:
             patch = {
              "spec": {
                 "template": {
                     "metadata": {
                         "annotations": {
                             "kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow().isoformat() 
                                  }
                                }
                            }
                        }
                    }
        # Apply the updated deployment
             api_instance.patch_namespaced_deployment(deployment_name, namespace, body=patch)
           except client.exceptions.ApiException as e:
               print(f"Exception when calling AppsV1Api->patch_namespaced_deployment: {e}")
def main():
    # Define the namespace and deployment name
    config.load_kube_config()
    parser = argparse.ArgumentParser()
    parser.add_argument('namespace', type=str, help='Namespace of the deployment')
    parser.add_argument('deployment', type=str, help='Name of the deployment')
    parser.add_argument('env', type=str, help='Cluster Environment')
    args = parser.parse_args()

    # Restart the deployment
    comment = restart_deployment(args.namespace, args.deployment, args.env)

if __name__ == '__main__':
    main()

4) Create Code for Patching the Deployment

from kubernetes import client, config
import argparse
import datetime
import sys
from common import check_namespace_exists, check_deployment_exists, close_issue, approval_verify,extract_info
def patch_deployment(namespace, deployment_name, env, img, cn):
    if check_namespace_exists(namespace):
        if check_deployment_exists(deployment_name, namespace):
           print("Validation Sucessfull")
           api_instance = client.AppsV1Api()
           try:
             patch = {
                 "spec": {
                    "template": {
                       "spec": {
                          "containers": [
                              {
                                "name": cn,
                                "image": img
                             }
                          ]
                        }
                    }
                }
            }
        # Apply the updated deployment
             api_instance.patch_namespaced_deployment(deployment_name, namespace, body=patch)
           except client.exceptions.ApiException as e:
               print(f"Exception when calling AppsV1Api->patch_namespaced_deployment: {e}")
def main():
    # Define the namespace and deployment name
    config.load_kube_config()
    parser = argparse.ArgumentParser()
    parser.add_argument('namespace',help='Namespace of the deployment')
    parser.add_argument('deployment', help='Name of the deployment')
    parser.add_argument('env', help='Cluster Environment')
    parser.add_argument('img', help='Image to be patched')
    parser.add_argument('cn', help='Container Name to be patched')
    args = parser.parse_args()

    # Patch the deployment
    patch_deployment(args.namespace, args.deployment, args.env, args.img, args.cn)

if __name__ == '__main__':
    main()

6) Create Image to Use the above Code

# Use the official Python base image
FROM python:3.9-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    KUBECONFIG=/src/.kube/config

# Create necessary directories
RUN mkdir -p /src/.kube

# Set the working directory
WORKDIR /src

# Copy Kubernetes config and requirements
COPY config /src/.kube/
COPY requirments.txt /src/

# Debug step: List files to ensure `requirements.txt` is copied
RUN ls -l /src/

# Install Python dependencies
RUN pip install --upgrade pip && \
    pip install --no-cache-dir -r /src/requirments.txt || \
    (echo "Error occurred during pip install" && exit 1)

# Install Kubernetes client (if not already in requirements.txt)
RUN pip install --no-cache-dir kubernetes

requirments.txt file

kubernetes
python-gitlab
requests

Create Image and push it to your Repo.

Create Gitlab-ci.yaml file

######################## Default image #######################

default:
    image: omvedi25/devops-tool:v1.1
############################# Stages ######################################

stages:
 - devops-automation

 ########################### Templates ######################################
devops-automation:
    stage: devops-automation
    script:
      - cd src 
      - python issuelist.py

enter image description here

Now Lets See the Working

Create a new issue to restart the pod

enter image description here

Move to Pipeline and run it

enter image description here

enter image description here

Go to Issue and Verify

enter image description here

Waiting got the approval for the valid requester Add Approved in Comment login to valid user enter image description here

Lets See the Status of Pods enter image description here

Run the Pipeline Again enter image description here

enter image description here

Verify the Pods Status

enter image description here

Lets Patch the Deployment

Create a new Issue enter image description here

Verify the Existing Image assigned to Deployment enter image description here

Trigger the Pipeline

enter image description here

Verify the Pipeline enter image description here

Approve the Pipeline enter image description here

Run the Pipeline again and verify issue enter image description here

Verify the Deployment enter image description here

Note We can create a scheduler to run the pipeline every 5 min enter image description here

Conclusion GitLab Issue Templates are a powerful tool for standardizing and automating issue creation across your team. By incorporating these templates into your workflow and integrating them with automation tools like GitLab CI/CD or monitoring systems, you can improve consistency, save time, and ensure critical issues are addressed promptly.


Author: CloudOpsKube

New Blog By CloudOpsKube