From Python to Yaml

Image: Infra as Code

No dia a dia de um time de infraestrutura as vezes a gente se depara com situações que é necessário criar uma automação para automatizar um processo automático de automação, hãann?! É isso mesmo!! Eu explico 😀. Recentemente, tive uma necessidade de criar uma rotina de rollback automaticamente para o GitLab que desse a possibilidade aos desenvolvedores de fazer deploy das últimas 10 TAGs do projeto. Precisava atender a qualquer projeto.

Para resolver esse problema foi necessário gerar jobs dinamicamente para o GitLab, a solução que melhor atendeu foi criar um script em Python que lê algumas informações do GIT, transforma os registros, armazena em algum tipo de estrutura de dados, agrega informações aos registros e em seguida escreve um arquivo do tipo YAML como resultado final.

Curtiu?!? Então vamos ver como fica isso??!! Vamos passo a passo e no final a gente junta tudo!! Para validar o script e tornar a reprodução desses procedimentos factível eu vou usar o repositório público do projeto Pip, lá tem todas as informações e metadados que precisamos para “brincar”, veja como clonar.

1$ git clone https://github.com/pypa/pip.git
2$ >git_lab_job.py
3$ chmod +x git_lab_job.py
4$ vim git_lab_job.py

Lendo os dados do repositório GIT

Para fazer essa parte inicial, é preciso coletar algumas informações do repositório do GIT, informações tipo: URL do projeto, últimas 10 TAGs , hash do commit, vejamos como fazer isso no script abaixo.

 1#! /usr/bin/python3
 2
 3import git
 4from datetime import datetime
 5
 6
 7# Function: Get information from Git repo
 8def get_tag_info():
 9    repo = git.Repo(search_parent_directories=True)
10    tags = repo.tags
11    tag_info = []
12    for tag in tags:
13        tag_commit = tag.commit
14        tag_info.append({
15            "name": tag.name,
16            "date": tag_commit.committed_datetime,
17            "hash": tag_commit.hexsha,
18            "repo": tag_commit.repo.remote().url
19        })
20    return tag_info
21
22# Call the function to get information from Git repo
23tag_list = get_tag_info()
24
25$ ./git_lab_job.py
26
27Tag Name: 1.5.4
28Commit Date: 2014-02-21 07:10:46-05:00
29Commit Hash: 313b8d9f7d3c410412e596664788687e4741dba3
30Repo URL: https://github.com/pypa/pip.git
31
32Tag Name: 1.5.5
33Commit Date: 2014-05-03 01:13:28-04:00
34Commit Hash: 0e72f65bf4e5ea6b8fd17d02fe4cae0ba7da5113
35Repo URL: https://github.com/pypa/pip.git
36
37Tag Name: 1.5.6
38Commit Date: 2014-05-16 22:21:32-04:00
39Commit Hash: 81f21f2261d422d243f66b2dfeef0faae72ab119
40Repo URL: https://github.com/pypa/pip.git

Transformando os dados

Com os dados disponíveis podemos brincar um pouco e organizar de uma forma já pensando na saída final, no formato YAML. Escolhi armazenar numa estrutura de dados do tipo dicionário que por definição me permite usar uma chave e um valor ou valores para cada chave única. Usei a TAG como chave e as informações com: URL do projeto, ID do hash e data de commit como valores dessa chave e ordenei por data.

 1#! /usr/bin/python3
 2
 3import yaml
 4import git
 5from datetime import datetime
 6
 7rollback_jobs={}
 8
 9def get_tag_info():
10    repo = git.Repo(search_parent_directories=True)
11    tags = repo.tags
12    tag_info = []
13    for tag in tags:
14        tag_commit = tag.commit
15        tag_info.append({
16            "name": tag.name,
17            "date": tag_commit.committed_datetime,
18            "hash": tag_commit.hexsha,
19            "repo": tag_commit.repo.remote().url
20        })
21    return tag_info
22
23# Example usage
24tag_list = get_tag_info()
25
26# Sort the tag list by date
27sorted_tags = sorted(tag_list, key=lambda x: x["date"])
28
29# Print sorted tags
30for tag in sorted_tags:
31    print("Tag Name:", tag["name"])
32    print("Commit Date:", tag["date"])
33    print("Commit Hash:", tag["hash"])
34    print("Repo URL:", tag["repo"])
35
36    rollback_jobs.update({"rollback-"+ tag["name"]: { "stage": "rollback" , "description": "TAG Date " + tag["date"].strftime("%Y-%m-%d %H:%M:%S") + " - TAG ID " + tag["name"] + " - Hash ID " + tag["hash"] ,  "script": 'git clone ' +  tag["repo"]  +' &&  git fetch --all --tags && git checkout ' + tag["name"] } })
37    print()
38
39$ ./git_lab_job.py
40
41{'rollback-1.5.4': {'stage': 'rollback', 'description': 'TAG Date 2014-02-21 07:10:46 - TAG ID 1.5.4 - Hash ID 313b8d9f7d3c410412e596664788687e4741dba3', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 1.5.4'}, 'rollback-1.5.5': {'stage': 'rollback', 'description': 'TAG Date 2014-05-03 01:13:28 - TAG ID 1.5.5 - Hash ID 0e72f65bf4e5ea6b8fd17d02fe4cae0ba7da5113', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 1.5.5'}, 'rollback-1.5.6': {'stage': 'rollback', 'description': 'TAG Date 2014-05-16 22:21:32 - TAG ID 1.5.6 - Hash ID 81f21f2261d422d243f66b2dfeef0faae72ab119', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 1.5.6'}, 'rollback-6.0': {'stage': 'rollback', 'description': 'TAG Date 2014-12-22 11:09:39 - TAG ID 6.0 - Hash ID 071decf82b8a51a550dbc1268dfda967fd6685c9', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 6.0'}, 'rollback-6.0.1': {'stage': 'rollback', 'description': 'TAG Date 2014-12-22 17:50:28 - TAG ID 6.0.1 - Hash ID 99f67cbed859d5b432e23f4bc096faacc205f1db', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 6.0.1'}, 'rollback-6.0.2': {'stage': 'rollback', 'description': 'TAG Date 2014-12-23 08:07:39 - TAG ID 6.0.2 - Hash ID a25d6609a43163535f8e18561eca475dd3e76508', 'script': 'git clone https://github.com/pypa/pip.git &&  git fetch --all --tags && git checkout 6.0.2'}

Listando as últimas 10 TAGs

Só para não esquecermos, o objetivo final deste projeto é criar automaticamente um stage chamado rollback baseado nas últimas 10 tags “deployadas”. No passo anterior foi possível listar todas as TAGs e ordená-las por data. Agora fica fácil extrair as últimas TAGs e criar os 10 jobs de rollback com as últimas 10 TAGs do projeto, vejamos como fazer isso.

 1#! /usr/bin/python3
 2
 3import git
 4from datetime import datetime
 5
 6# Cria uma estrutura de dados para armazenar os dados dos últimos 10 jobs
 7rollback_jobs={}
 8
 9def get_tag_info():
10    repo = git.Repo(search_parent_directories=True)
11    tags = repo.tags
12    tag_info = []
13    for tag in tags:
14        tag_commit = tag.commit
15        tag_info.append({
16            "name": tag.name,
17            "date": tag_commit.committed_datetime,
18            "hash": tag_commit.hexsha,
19            "repo": tag_commit.repo.remote().url
20        })
21    return tag_info
22
23# Example usage
24tag_list = get_tag_info()
25
26# Sort the tag list by date
27sorted_tags = sorted(tag_list, key=lambda x: x["date"])
28
29# Criar uma lista com últimos 10 registros
30last_ten_tags = sorted_tags[-10:]
31
32# Print sorted tags
33for tag in last_ten_tags:
34    print("Tag Name:", tag["name"])
35    print("Commit Date:", tag["date"])
36    print("Commit Hash:", tag["hash"])
37    print("Repo URL:", tag["repo"])
38
39# Atualiza todos os jobs em um dicionário
40    rollback_jobs.update({"rollback-"+ tag["name"]: { "stage": "rollback" , "description": "TAG Date " + tag["date"].strftime("%Y-%m-%d %H:%M:%S") + " - TAG ID " + tag["name"] + " - Hash ID " + tag["hash"] ,  "script": 'git clone ' +  tag["repo"]  +' &&  git fetch --all --tags && git checkout ' + tag["name"] } })
41    print()
42
43
44print (rollback_jobs)

Criando o arquivo YAML

Como etapa final, após ler o repositório GIT, listar todas as TAG, buscar as últimas 10 TAGs e armazená-las em uma estrutura de dados do tipo dicionário, agora chegou vez de criar o arquivo YAML, vamos ver como é possível fazer isso. Veja o script completo para realizar todas as tarefas.

 1#! /usr/bin/python3
 2
 3import yaml
 4import git
 5from datetime import datetime
 6
 7
 8# Cria de uma estrutura de dados tipo dicionário
 9rollback_jobs={}
10
11# Função que busca no repo GIT os metadados
12def get_tag_info():
13    repo = git.Repo(search_parent_directories=True)
14    tags = repo.tags
15    tag_info = []
16    for tag in tags:
17        tag_commit = tag.commit
18        tag_info.append({
19            "name": tag.name,
20            "date": tag_commit.committed_datetime,
21            "hash": tag_commit.hexsha,
22            "repo": tag_commit.repo.remote().url
23        })
24    return tag_info
25
26# Usando a função para buscar os dados
27tag_list = get_tag_info()
28
29# Organiza o resultado por datas
30sorted_tags = sorted(tag_list, key=lambda x: x["date"])
31
32# Busca os 10 registros mai recentes
33last_ten_tags = sorted_tags[-10:]
34
35# Imprime os 10 registros mai recentes ( para validação de saídas, pode ser removido )
36# e atualiza os registros em um dicionário
37for tag in last_ten_tags:
38    print("Tag Name:", tag["name"])
39    print("Commit Date:", tag["date"])
40    print("Commit Hash:", tag["hash"])
41    print("Repo URL:", tag["repo"])
42    # Atualiza os registros em um dicionário
43    rollback_jobs.update({"rollback-"+ tag["name"]: { "stage": "rollback" , "description": " Date " + tag["date"].strftime("%Y-%m-%d %H:%M:%S") + " - TAG ID " + tag["name"] + " - Hash ID " + tag["hash"],  "script": 'git clone ' +  tag["repo"]  +' &&  git fetch --all --tags && git checkout ' + tag["name"] + ' && deploy command'  } })
44    print()
45
46
47print (rollback_jobs)
48
49# Criação do arquivo YAML
50with open(r'rollback_job.yaml', 'w') as file:
51    documents = yaml.dump(rollback_jobs, file)

Arquivo YAML com o resultado completo.

 1rollback-22.2:
 2  description: ' Date 2022-07-21 10:28:01 - TAG ID 22.2 - Hash ID 8e7e76e60f4e115ea1201bee2f176377a718fce1'
 3  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
 4    git checkout 22.2 && deploy command
 5  stage: rollback
 6rollback-22.2.1:
 7  description: ' Date 2022-07-27 19:18:11 - TAG ID 22.2.1 - Hash ID 61bdbe0d66ad472372f67c7bce05629027bdfc2b'
 8  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
 9    git checkout 22.2.1 && deploy command
10  stage: rollback
11rollback-22.2.2:
12  description: ' Date 2022-08-03 19:58:26 - TAG ID 22.2.2 - Hash ID 10902ac52d49ec3f5a03a4ed67c4dee533bf8bc4'
13  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
14    git checkout 22.2.2 && deploy command
15  stage: rollback
16rollback-22.3:
17  description: ' Date 2022-10-15 11:59:14 - TAG ID 22.3 - Hash ID 0a76da3a94130fad58b086e331c3d3e1b02a89eb'
18  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
19    git checkout 22.3 && deploy command
20  stage: rollback
21rollback-22.3.1:
22  description: ' Date 2022-11-05 15:25:43 - TAG ID 22.3.1 - Hash ID 1463081f10de6bfad81afe0d68272e7c3bedbadf'
23  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
24    git checkout 22.3.1 && deploy command
25  stage: rollback
26rollback-23.0:
27  description: ' Date 2023-01-30 15:13:08 - TAG ID 23.0 - Hash ID 368c7b4c557e673b05b0f8cffc967d3e333eee19'
28  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
29    git checkout 23.0 && deploy command
30  stage: rollback
31rollback-23.0.1:
32  description: ' Date 2023-02-17 18:15:15 - TAG ID 23.0.1 - Hash ID 3817aef07f4c8a0cb1c43bb9a73f1bb624fc263b'
33  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
34    git checkout 23.0.1 && deploy command
35  stage: rollback
36rollback-23.1:
37  description: ' Date 2023-04-15 10:44:40 - TAG ID 23.1 - Hash ID 6424ac4600265490462015c2fc7f9a402dba9ed8'
38  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
39    git checkout 23.1 && deploy command
40  stage: rollback
41rollback-23.1.1:
42  description: ' Date 2023-04-22 10:13:28 - TAG ID 23.1.1 - Hash ID ee40d71817df8346af3d96051a298db8f22e52f1'
43  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
44    git checkout 23.1.1 && deploy command
45  stage: rollback
46rollback-23.1.2:
47  description: ' Date 2023-04-26 10:18:28 - TAG ID 23.1.2 - Hash ID 3fe7e54fceac7a03bcb88ce26cfd0937acfe5e40'
48  script: git clone https://github.com/pypa/pip.git &&  git fetch --all --tags &&
49    git checkout 23.1.2 && deploy command
50  stage: rollback

Resumo

É jovem!!! Tem dias que só escrevendo algum código para chegar a uma solução de um problema, nem sempre utilizar um framework de automação resolve seus problemas. Desafios como esses vão aparecer aos montes no seu dia a dia e com mais e mais frequência à medida que você vai evoluindo na sua carreira e os problemas vão ficando mais complexos, tipo vídeo game. Linguagens de programação como Python, Shell, Go precisam fazer parte do seu cinto de utilidades!! Se quiser uma dica, de graça!! comece a estudar hoje!!

Uma outra dica, aproveite o script para brincar com transfomações de YAML pra Python e de Python pata YAML, é diversão garantida!!

Abraços!

Vida longa e próspera a todos!!

Referências


Eu adoraria ouvir suas outras histórias e situações semelhantes ao que acabei de escrever neste post, você pode me encontrar em @infraascode_br ou linkedin.com/in/leonardoml/ .

Te convido a ver os outros posts do blog Infra-as-Code garanto que tem coisas legais lá!!


--- --- IMPORTANTE --- ---
As opiniões aqui expressas são pessoais e de responsabilidade única e exclusiva do autor, elas não refletem necessariamente a posição das empresas que eu trabalho(ei) e/ou presto(ei) serviço.