Advancements of CICD pipelines using gitlab

# GITLABにアクセスしてPipelineの一覧を取得、操作するアプリを作成

# 方針
・環境変数を設定
・pythonプログラムを書く

# 環境変数を設定
環境変数の設定ファイルはこちら、以前のGITLABの設定より
```shell:.envrc
export  GITLAB_USERNAME="root"
export  GITLAB_PASSWORD="bKORzwipG166LWNQmj5uQMHxfVR18Q7fVDwxP9F8Ha4="
```
# pythonプログラムを書く
```python:gitlab_api.py
import os
import requests
import argparse
from urllib3 import disable_warnings
from prettytable import PrettyTable

disable_warnings()

GITLAB_URL = "https://localhost"
HEADERS = {"Content-Type":"application/json","Accept":"application/json"}

class GitLab_Auth():
    def __init__(self):
        payload = {
            "username":os.environ["GITLAB_USERNAME"],
            "password":os.environ["GITLAB_PASSWORD"],
            "grant_type":"password"
        }
        
        auth_response = requests.post(
            url=f"{GITLAB_URL}/oauth/token",json=payload,headers=HEADERS,verify=False
        )
        
        auth_response.raise_for_status()
        self.token = auth_response.json()["access_token"]
        
def gitlab(ctx):
    if "GITLAB_USERNAME" not in os.environ or "GITLAB_PASSWORD" not in os.environ:
        print("ERROR:You must specify GITLAB_USERNAME and GITLAB_PASSWORD as environment variables")
        exit(1)
    ctx.obj = GitLab_Auth()
    
def projects(gitlab_obj,all):
    table = PrettyTable()
    headers = HEADERS.copy()
    headers["Authorization"] = f"Bearer {gitlab_obj.token}"
    
    try:
        project_list = requests.get(
            url = f"{GITLAB_URL}/api/v4/projects?owned=True",headers=headers, verify=False
        )
    except Exception as e:
        print(f"ERROR: Unable to get Gitlab Project: {e}")
        exit(1)
        
    project_list_json = project_list.json()
    table.field_names = ["ID","Name","Group","URL","Pipeline Status"]
    table.align = "l"
    
    for proj in project_list_json:
        add_row = True
        row = [
            proj["id"],
            proj["name"],
            proj["name_with_namespace".split("/")[0].strip()],
            proj["web_url"]
        ]
        
        try:
            pipeline_status_check = requests.get(
                url = f"{GITLAB_URL}/api/v4/projects/{proj['id']}/pipelines",headers=headers,verify=False
            )
            pipeline_status_check.raise_for_status()
        except Exception as e:
            print(f"WARNING: Unable to get pipeline list for project {proj['id']}: {e}")
            continue
        
        pipeline_list = pipeline_status_check.json()
        
        if len(pipeline_list) > 0 :
            row.append(pipeline_list[0]["status"])
        else:
            row.append("N/A")
            
        if not all:
            if len(pipeline_list) == 0:
                add_row = False
        
        if add_row:
            table.add_row(row)
            
    print(table)

def list_pipelines(gitlab_obj,all,project_id):
    table = PrettyTable()
    headers = HEADERS.copy()
    headers["Authorization"] = f"Bearer {gitlab_obj.token}"
    
    try:
        pipeline_list = requests.get(
            url = f"{GITLAB_URL}/api/v4/projects/{project_id}/pipelines",
            headers= headers, verify=False
        )
    except Exception as e:
        print(f"ERROR: Failed to load pipelines for {project_id}: {e}")
        exit(1)
    
    table.field_names = ["ID","Branch","Started At","Finished At","URL","Status"]
    table.align = "l"
    
    pipeline_list_json = pipeline_list.json()
    
    for pipeline in pipeline_list_json:
        try:
            response = requests.get(
                url = f"{GITLAB_URL}/api/v4/projects/{project_id}/pipelines/{pipeline['id']}",
                headers=headers,verify=False
            )
            response.raise_for_status()
        except Exception as e:
            print(f"Warning: Unable to get pipeline detail for {pipeline['id']}: {e}")
            continue
        
        pipeline_detail = response.json()
        pipeline_low = [
                pipeline_detail['id'],
                pipeline_detail['ref'],
                pipeline_detail['started_at'],
                pipeline_detail['finished_at'],
                pipeline_detail['web_url'],
                pipeline_detail['status']
            ]
        table.add_row(pipeline_low)

        
        if not all:
            break
    print(table)
        
def retry_pipeline(gitlab_obj,project_id,pipeline_id):
    headers = HEADERS.copy()
    headers["Authorization"] = f"Bearer {gitlab_obj.token}"
    
    try:
        retry_pipeline_query = requests.post(
            url = f"{GITLAB_URL}/api/v4/projects/{project_id}/pipelines/{pipeline_id}/retry",
            headers=headers, verify=False
        )
        retry_pipeline_query.raise_for_status()
    except Exception as e:
        print(f"ERROR: Failed to retry the pipeline {pipeline_id} for project {pipeline_id} : {e}")
        exit(1)
        
    print(f"INFO: Pipeline {pipeline_id} in the project {project_id} has been successfully retried")

def main():
    parser = argparse.ArgumentParser(description="Gitlab Project and Pipeline Management Tool")
    subparsers = parser.add_subparsers(dest = "command")
    
    projects_parser = subparsers.add_parser("projects",help="Displays a list of projects in tabular format")
    projects_parser.add_argument('--all',action='store_true',help="Display all projects with or without pipeline")
    
    pipelines_parser = subparsers.add_parser("pipelines", help= "List/Retry a pipeine from a given project")
    pipelines_subparsers = pipelines_parser.add_subparsers(dest="pipeline_command")
    
    list_pipelines_parser = pipelines_subparsers.add_parser('list',help="List pipelines for a given project")
    list_pipelines_parser.add_argument('--all',action='store_true',help="Show all pipelines")
    list_pipelines_parser.add_argument('--project_id',type=int, required=True, help='PROJECT ID')
    
    retry_pipelines_parser = pipelines_subparsers.add_parser('retry',help="Retry pipelines for a given project")
    retry_pipelines_parser.add_argument('--project_id',type=int,required=True,help="PROJECT ID")
    retry_pipelines_parser.add_argument('--pipeline_id',type=int,required=True,help='PIPELINE_ID')
    
    args = parser.parse_args() 
    
    gitlab_obj = GitLab_Auth()
    if args.command == "projects":
        projects(gitlab_obj,args.all)
    elif args.command == "pipelines":
        if args.pipeline_command == "list":
            list_pipelines(gitlab_obj,args.all,args.project_id)
        elif args.pipeline_command == "retry":
            retry_pipeline(gitlab_obj,args.project_id,args.pipeline_id)
    
if __name__ =="__main__":
    main()
        
        
```

# 実行
```shell-session
$pip3 install prettytable
$source .envrc
$ python gitlab_api.py --help
usage: gitlab_api.py [-h] {projects,pipelines} ...

Gitlab Project and Pipeline Management Tool

positional arguments:
  {projects,pipelines}
    projects            Displays a list of projects in tabular format
    pipelines           List/Retry a pipeine from a given project

options:
  -h, --help            show this help message and exit
$ python gitlab_api.py projects
+----+-------------------+-----------------------------------+------------------------------------------+-----------------+
| ID | Name              | Group                             | URL                                      | Pipeline Status |
+----+-------------------+-----------------------------------+------------------------------------------+-----------------+
| 2  | MDP_OSPF_RESTCONF | Administrator / MDP_OSPF_RESTCONF | https://127.0.0.1/root/MDP_OSPF_RESTCONF | failed          |
+----+-------------------+-----------------------------------+------------------------------------------+-----------------+
$ python gitlab_api.py pipelines list --project_id 2 
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+--------+
| ID | Branch | Started At               | Finished At              | URL                                                     | Status |
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+--------+
| 16 | master | 2024-11-26T22:52:13.349Z | 2024-11-26T23:06:24.623Z | https://127.0.0.1/root/MDP_OSPF_RESTCONF/-/pipelines/16 | failed |
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+--------+

$ python gitlab_api.py pipelines retry --project_id 2 --pipeline_id 16

$ python gitlab_api.py pipelines list --project_id 2 
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+---------+
| ID | Branch | Started At               | Finished At              | URL                                                     | Status  |
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+---------+
| 16 | master | 2024-11-26T22:52:13.349Z | 2024-11-26T23:06:24.623Z | https://127.0.0.1/root/MDP_OSPF_RESTCONF/-/pipelines/16 | running |
+----+--------+--------------------------+--------------------------+---------------------------------------------------------+---------+

```

:::note info
課題
argparseによってコマンドアプリが簡単に作れるが使い方がいまいちよく分からない。練習が必要。