Intégration Python de Gitlab, Jira et Confluence pour automatiser les versions de versions

Récemment, lors d'un stand-up, un collègue a fait une proposition rationnelle : automatiser la construction de la version, en prenant comme base des pratiques déjà prêtes à interagir avec Jira écrites en Python.

Le processus de déploiement est le suivant: lorsqu'un nombre suffisant de tâches qui ont été testées s'accumulent, elles libèrent un candidat candidat (RC) dans chaque projet affecté par les tâches, puis les tâches sont testées dans le cadre de RC. Après cela, le RC est versé sur le serveur intermédiaire, où, à proximité immédiate de l'environnement de combat, il est toujours testé et une régression complète est effectuée. Et puis, après les actions de déploiement nécessaires, la nouvelle version est versée dans le maître.

Jusqu'à récemment, l'ensemble du processus d'assemblage était effectué manuellement par l'un des développeurs. Ce qui a pris une heure, deux ou plus de temps et n'était, il me semble, pas une occupation très intéressante. Maintenant, quand presque tout est prêt, la sortie de 20 tâches, affectant 5 projets, va durer moins d'une minute. Il reste, bien sûr, la résolution des conflits, le lancement de tests manqués, etc., mais même en tenant compte de cela, le temps des développeurs et des testeurs, qui sont obligés d'attendre que quelqu'un soit le premier à se libérer et à créer des RC, est beaucoup épargné.

En général, je me suis mis à la tâche, et cela s'est avéré très intéressant et fascinant. Et quoi d'autre est nécessaire pour le plaisir du travail, sinon des projets passionnants?

J'ai étudié l'héritage: il s'est avéré utiliser directement l'API Jira, et il a été écrit, il me semble, pas optimal. Par exemple, la liste des tâches de publication a été obtenue comme suit: toutes les versions existantes ont été téléchargées depuis Jira, puis chacune d'elles par son nom a été comparée avec le nom de notre version jusqu'à ce que celle souhaitée soit trouvée:

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:

                ...


En général, l'interaction directe avec l'API conduit à un code peu lisible. Et je ne voulais pas inventer un vélo. Ma première recherche sur Github m'a conduit à la bibliothèque JIRA Python, une bibliothèque assez simple et puissante. J'ai initialement prévu de créer des requêtes Marge en utilisant la bibliothèque GitPython, également disponible sur Github. Mais une étude plus approfondie de la question a immédiatement conduit à l'idée de trouver quelque chose lié non pas à Git, mais plutôt immédiatement à Gitlab. En conséquence, j'ai opté pour la solution la plus célèbre: Python GitLab.

J'ai commencé par obtenir une liste de tâches dans l'état approprié pour la version . J'ai décidé de ne pas modifier fortement la solution précédente, mais de la rendre plus efficace en demandant immédiatement l'API de la tâche de la version souhaitée:

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

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

Bien que la même chose se produise probablement sous le capot, elle s'est avérée plus belle et optimale, mais toujours pas très lisible. De plus, à partir des tâches reçues, il est nécessaire de collecter des liens pour fusionner les demandes. J'ai décidé de stocker les demandes de fusion trouvées dans namedtuple, elles sont parfaites pour cela:

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

Des demandes de fusion ont également été reçues à l'aide de l'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)   

Après cela, j'ai décidé où je pouvais utiliser les bibliothèques trouvées . Ensuite, je vais peut-être refaçonner ces pièces.

Ensuite, vous devez vérifier, tout à coup, les bonnes branches RC existent déjà, s'il y avait déjà des tentatives de construction, alors elles doivent être supprimées et de nouvelles créées. J'ai déjà fait cela en utilisant la bibliothèque 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'})

Après cela, vous pouvez commencer à remplir le tableau dans la tâche d'assemblage Jira . Les informations du tableau sont contenues dans les colonnes suivantes: Non, Tâche, Priorité, Demandes de fusion de la tâche dans RC, Statut de la demande de fusion (si les tests réussis dans Gitlab, qu'il y ait des conflits ou non, est versé / non versé).

À cette étape, j'ai rencontré une faille désagréable dans Gitlab: si les modifications étaient précédemment gelées dans la branche de destination de la demande de fusion, alors l'API Gitlab, lors de la demande du statut de la demande de fusion, donne une réponse sur la présence de conflits. En principe, cela peut être compris: s'il n'y a rien à verser, cela ne fonctionnera pas, mais pourquoi dire qu'il y a un conflit en ce lieu? Les forums Gitlab demandent aux gens depuis plusieurs années, mais il n'y a pas encore de réponses.

Dans mon cas, cela conduit à un faux statut de conflit et à la vérification manuelle de la demande de fusion. Je n'ai encore rien trouvé.

La logique lors de la création de demandes de fusion est la suivante: si les tests ne sont pas réussis, ou s'il y a un conflit, les demandes de fusion sont créées, mais elles ne circulent pas dans RC, les statuts correspondants sont placés dans le tableau.

Pour vérifier l'exécution des tests, nous recherchons une liste de pipelines adaptés. Gitlab les émet triés par date dans l'ordre décroissant, tout comme nous en avons besoin. Nous prenons le premier - ce sera celui qui sera nécessaire:

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

if pipelines:

   pipelines = pipelines[0]

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

       status = '(x)   !, '

Créez ensuite les demandes de fusion elles-mêmes . Nous vérifions les ouvertures, au cas où ce ne serait pas la première tentative de construction. En l'absence de la demande de fusion requise, créez ceci:

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 ('-', '_') - changez le nom de la tâche pour supprimer les commentaires informatifs de Gitlaba sur la tâche dans Jira.

Ensuite, nous examinons l'état de la demande de fusion reçue : s'il n'y a pas de conflit, nous la versons dans RC. En cas de conflit, notez les statuts appropriés et partez pour une vérification manuelle.

De plus, par analogie, nous créons des demandes de fusion de RC à Staging et de Staging à Master pour tous les projets. Ils seront versés après avoir vérifié l'intégralité de la version dans les branches RC.

Deux champs ont été ajoutés au modèle de tâche Jira: pour les actions pré-pré-post et post-de-post. Pour toutes les tâches impliquées, l'algorithme collecte une liste de ces actions et entre dans la tâche d'assemblage pour ne rien oublier.

Et enfin, la sortie dans Jira est la création d'une tâche d'assemblage :

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)

D'autres réflexions sont les suivantes : exécuter un script sur Gitlab avec un hook web de Jira. Par exemple, vous pouvez créer un webhook pour créer une tâche d'assemblage (créer un type spécial de tâche pour celle-ci), ou créer une tâche avec le mot «Assemblage» dans le titre. Et Gitlab pour ce hook exécutera un script bash qui démarre tout le processus, ou lèvera une image Docker avec Python et exécutera un script dessus. Bien que la deuxième option soit déjà trop compliquée. Oui, et j'ai besoin de comptes techniques dans Jira et Gitlab. En général, il n'y a pas encore de décision finale.

Après avoir créé des branches RC, vous pouvez également déployer un banc de test sur les branches nécessaires et exécuter des tests de régression. Jenkins peut gérer cela. Mais c'est aussi dans les plans pour l'instant.

Tout cela pour accélérer le travail des testeurs et libérer les développeurs du travail de routine. Cependant, le sens économique ici est également assez spécifique: prenez un développeur moyen (dans le vide) avec un salaire hypothétique de 150 000 roubles pour 8 heures de temps de travail par jour. Pendant deux ans, nous avons eu environ 700 versions - il s'agit de la version par jour. Un peu plus, certains moins, mais en moyenne, je pense, au moins une heure du temps du développeur pour construire la version a disparu. Autrement dit, l'automatisation de ce processus permet à l'entreprise d'économiser au moins 150 000/8 = 18 750 roubles par mois.

En cours de route, je me suis fait un script séparé, montrant des statistiques sur la prochaine version: combien de tâches, quels projets sont affectés, etc. Étant donné que si je n'ai le statut de développeur dans aucun des projets de Gitlab, il y aura un refus d'accès lors de la création de demandes de fusion. En outre, il est pratique de connaître à l'avance les actions de déploiement ou de détecter une tâche tombée par erreur dans cette version. La distribution des notifications de publication a été résolue à l'aide des modules SMTP et e-mail.

J'ai été satisfait de la solution à ce problème: il est toujours intéressant d'apprendre quelque chose de nouveau et de le mettre en pratique. Ce sera bien si cette expérience est utile à quelqu'un.

All Articles