Integração Python do Gitlab, Jira e Confluence para automatizar as compilações de versões

Recentemente, em um stand-up, um colega fez uma proposta racional : automatizar a montagem de releases, tomando como base práticas já prontas para interagir com o Jira, escritas em Python.

O processo de implantação é o seguinte: quando um número suficiente de tarefas que foram testadas se acumulam, eles liberam um candidato a candidato (RC) em cada projeto afetado pelas tarefas, então as tarefas são testadas como parte do RC. Depois disso, o RC é derramado no servidor intermediário, onde, próximo ao ambiente de combate, ele ainda é testado e uma regressão completa é realizada. E, depois das ações de implantação necessárias, a nova versão é lançada no mestre.

Até recentemente, todo o processo de montagem era realizado manualmente por um dos desenvolvedores. O que levou uma hora, duas ou mais horas e não me parece uma ocupação muito interessante. Agora, quando quase tudo estiver pronto, o lançamento de 20 tarefas, afetando 5 projetos, vai demorar menos de um minuto. Resta, é claro, a resolução de conflitos, a execução de testes perdidos e muito mais, mas mesmo levando em conta isso, o tempo de desenvolvedores e testadores, que precisam esperar até que alguém seja o primeiro a se libertar e criar RC, é economizado muito.

Em geral, comecei a tarefa, que acabou sendo muito interessante e fascinante. E o que mais é necessário para o prazer do trabalho, se não projetos emocionantes?

Estudei o legado: acabou por usar a API Jira diretamente e foi escrito, me pareceu, não ideal. Por exemplo, a lista de tarefas de release foi obtida da seguinte maneira: todos os releases existentes foram baixados do Jira e, em seguida, cada um deles por nome foi comparado ao nome de nosso release até que o desejado fosse encontrado:

def get_release_info(config):

   try:

       release_input = sys.argv[1]

   except IndexError:

       raise Exception('Enter release name')

   releases_json = requests.get(url=RELEASES_LIST_URL, auth=(login, jira_password).json()

   for release in releases_json:

       if release['name'] == release_input:

                ...


Em geral, a interação direta com a API leva a um código não muito legível. E eu não queria inventar uma bicicleta. Minha primeira pesquisa no Github me levou à JIRA Python Library, uma biblioteca bastante simples e poderosa. Inicialmente, planejei criar solicitações de Marge usando a biblioteca GitPython, também encontrada no Github. Mas um estudo mais aprofundado da questão levou imediatamente à ideia de encontrar algo relacionado não ao git, mas imediatamente ao Gitlab. Como resultado, decidi pela solução mais famosa: Python GitLab.

Comecei obtendo uma lista de tarefas com o status apropriado para o lançamento . Decidi não alterar fortemente a solução anterior, mas torná-la mais eficaz solicitando imediatamente a API da tarefa da versão desejada:

fix_issues = jira.search_issues(f'fixVersion={release_input}')

fix_id = jira.issue(fix_issues.iterable[0]).fields.fixVersions[0].id

Embora, provavelmente, a mesma coisa aconteça sob o capô, no entanto, acabou sendo mais bonito e ideal, mas ainda não muito legível. Além disso, a partir das tarefas recebidas, é necessário coletar links para mesclar solicitações. Decidi armazenar as solicitações de mesclagem encontradas em namedtuple, elas são ótimas para isso:

Merge_request = namedtuple('Merge_request', ['url', 'iid', 'project', 'issue'])

Solicitações de mesclagem também foram recebidas usando a API Jira:

projects = set()

links_json = requests.get(url=REMOTE_LINK.format(issue_number),

                            auth=login,jira_password).json()

for link in links_json:

   url_parts = link['object']['url'].split('/')

   project = f'{url_parts[4]}'

   iid = url_parts[6]

   projects.add(project)   

Depois disso, decidi onde poderia usar as bibliotecas encontradas . Então, talvez, refatorarei essas peças.

Em seguida, é necessário verificar, de repente, as ramificações RC corretas já existem; se já houve tentativas de construção, elas precisam ser excluídas e novas. Eu já fiz isso usando a biblioteca Python GitLab:

gl = gitlab.Gitlab('https://gitlab...ru/', private_token=GITLAB_PRIVATE_TOKEN)

pr = gl.projects.get(project)

try:

   rc = pr.branches.get(f'{RC_name}')

   rc.delete()

   pr.branches.create({'branch': f'{RC_name}', 'ref': 'master'})

except gitlab.GitlabError:

   pr.branches.create({'branch': f'{RC_name}', 'ref': 'master'})

Depois disso, você pode começar a preencher a tabela na tarefa de montagem Jira . As informações na tabela estão contidas nas seguintes colunas: Não, Tarefa, Prioridade, Solicitações de mesclagem da tarefa no RC, Status da solicitação de mesclagem (se os testes aprovados no Gitlab, com ou sem conflitos, são lançados / não lançados).

Nesta etapa, encontrei uma falha desagradável do Gitlab: se as alterações foram congeladas anteriormente no ramo de destino da solicitação de mesclagem, a API do Gitlab, ao solicitar o status da solicitação de mesclagem, fornece uma resposta sobre a presença de conflitos. Em princípio, isso pode ser entendido: se não há nada para derramar, então não vai funcionar, mas por que dizer que existe um conflito neste local? Os fóruns do Gitlab perguntam às pessoas há vários anos, mas ainda não há respostas.

No meu caso, isso leva a um status de conflito falso e à verificação manual da solicitação de mesclagem. Ainda não criei nada.

A lógica ao criar solicitações de mesclagem é a seguinte: se os testes não são aprovados ou há um conflito, as solicitações de mesclagem são criadas, mas não fluem para o RC, os status correspondentes são colocados na tabela.

Para verificar a execução dos testes, procuramos uma lista de pipelines adequados. O Gitlab os emite classificados por data em ordem decrescente, exatamente como precisamos. Tomamos o primeiro - será o necessário:

pipelines = project.pipelines.list(ref=f'{issue}')

if pipelines:

   pipelines = pipelines[0]

   if pipelines.attributes['status'] != 'success':

       status = '(x)   !, '

Em seguida, crie os próprios pedidos de mesclagem . Verificamos se há espaços abertos, caso essa não seja a primeira tentativa de construção. Na ausência da solicitação de mesclagem necessária, crie este:

mr = project.mergerequests.list(

 state='opened',

source_branch=source_branch,         target_branch=target_branch)

if mr:

   mr = mr[0]

else:

   mr = project.mergerequests.create(

{'source_branch': source_branch,

           'target_branch': target_branch,

           'title': f"{(MR.issue).replace('-', '_')} -> {RC_name}",

           'target_project_id': PROJECTS_NAMES[MR.project],})

status = mr.attributes['merge_status']

url = mr.attributes['web_url']

return status, url, mr

MR.issue.replace ('-', '_') - altere o nome da tarefa para se livrar dos comentários não informativos de Gitlaba sobre a tarefa em Jira.

A seguir, examinamos o status da solicitação de mesclagem recebida : se não houver conflito, a despejamos no RC. Se houver um conflito, anote os status apropriados e deixe para verificação manual.

Além disso, por analogia, criamos solicitações de mesclagem de RC para armazenamento temporário e de armazenamento temporário para mestre para todos os projetos. Eles serão derramados após a verificação de toda a compilação do release nas ramificações de RC.

Dois campos foram adicionados ao modelo de tarefa Jira: para ações pré e pós-pós-pós-post. Para todas as tarefas envolvidas, o algoritmo coleta uma lista dessas ações e entra na tarefa de montagem para não esquecer nada.

E, finalmente, a saída em Jira é a criação de uma tarefa de montagem :

existing_issue = jira.search_issues(

f'project=PROJ AND summary ~ " {release_name}"')

if existing_issue:

   existing_issue = existing_issue[0]

   existing_issue.update(fields={

       'description': message,

       'customfield_xxx': message_before_deploy,

       'customfield_yyy': message_post_deploy,})

else:

   issue_dict = {

       "fixVersions": [{"name": release_name,}],

       'project': {'key': 'PROJ'},

       'summary': f" {release_name}",

       'description': message,

       'issuetype': {'name': 'RC'},  #      

       'customfield_xxx': message_before_deploy,

       'customfield_yyy': message_post_deploy,}

   new_issue = jira.create_issue(fields=issue_dict)

Pensamentos adicionais são os seguintes : executar um script no Gitlab com um gancho da Web do Jira. Por exemplo, você pode criar um webhook para criar uma tarefa de montagem (criar um tipo especial de tarefa para ela) ou criar uma tarefa com a palavra "Montagem" no título. E o Gitlab para esse gancho executará um script bash que inicia todo o processo ou aumentará uma imagem do Docker com Python e executará um script. Embora a segunda opção já seja muito complicada. Sim, e precisa de contas técnicas no Jira e no Gitlab. Em geral, ainda não há decisão final.

Após criar ramificações RC, você também pode implantar uma bancada de testes nas ramificações necessárias e executar testes de regressão. Jenkins pode lidar com isso. Mas isso também está nos planos para agora.

Isso foi tudo para acelerar o trabalho dos testadores e libertar os desenvolvedores do trabalho de rotina. No entanto, o sentido econômico aqui também é bastante específico: considere um desenvolvedor médio (no vácuo) com um salário hipotético de 150.000 rublos por 8 horas de tempo de trabalho por dia. Por dois anos, tivemos cerca de 700 lançamentos - trata-se do lançamento por dia. Um pouco mais, outro menos, mas acho que em média, pelo menos uma hora do tempo do desenvolvedor para compilar o lançamento se foi. Ou seja, a automação desse processo economiza para a empresa um mínimo de 150.000 / 8 = 18.750 rublos por mês.

Ao longo do caminho, criei um script separado para mim, mostrando estatísticas sobre o próximo lançamento: quantas tarefas, quais projetos são afetados etc. Como se eu não tiver status de desenvolvedor em nenhum dos projetos no Gitlab, haverá uma negação de acesso ao criar solicitações de mesclagem. Além disso, é conveniente conhecer antecipadamente as ações de implantação ou capturar uma tarefa que caiu nesta versão por engano. A distribuição das notificações de liberação foi resolvida usando os módulos SMTP e email.

Fiquei satisfeito com a solução para esse problema: é sempre interessante aprender algo novo e colocá-lo em prática. Será bom se essa experiência for útil para alguém.

All Articles