Integración de Python de Gitlab, Jira y Confluence para automatizar las versiones de lanzamiento

Recientemente, en un stand-up, un colega hizo una propuesta racional : automatizar la compilación del lanzamiento, tomando como base desarrollos listos para la interacción con Jira escritos en Python.

El proceso de implementación es el siguiente: cuando se acumula un número suficiente de tareas que se han probado, liberan un candidato candidato (RC) en cada proyecto afectado por las tareas, luego las tareas se prueban como parte de RC. Después de eso, el RC se vierte en el servidor de preparación, donde, muy cerca del entorno de combate, todavía se prueba y se realiza una regresión completa. Y luego, después de las acciones de implementación necesarias, la nueva versión se vierte en el maestro.

Hasta hace poco, todo el proceso de ensamblaje lo realizaba manualmente uno de los desarrolladores. Lo que me llevó una hora, dos o más y fue, me parece, una ocupación no muy interesante. Ahora, cuando casi todo está listo, el lanzamiento de 20 tareas, que afectan a 5 proyectos, se llevará a menos de un minuto. Queda, por supuesto, la resolución de conflictos, el lanzamiento de pruebas perdidas, etc., pero incluso teniendo en cuenta esto, el tiempo de los desarrolladores y probadores, que se ven obligados a esperar hasta que alguien sea el primero en liberarse y crear RC, se ahorra mucho.

En general, comencé la tarea, y resultó ser muy interesante y fascinante. ¿Y qué más se necesita para el placer del trabajo, si no proyectos emocionantes?

Estudié legado: resultó que utilizaba la API de Jira directamente, y fue escrito, me pareció, no óptimo. Por ejemplo, la lista de tareas de lanzamiento se obtuvo de la siguiente manera: todos los lanzamientos existentes se descargaron de Jira, y luego cada uno de ellos por nombre se comparó con el nombre de nuestro lanzamiento hasta que se encontró el deseado:

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 general, la interacción directa con la API conduce a un código poco legible. Y no quería inventar una bicicleta. Mi primera búsqueda en Github me llevó a la Biblioteca JIRA Python, una biblioteca bastante simple y poderosa. Inicialmente planeé crear solicitudes de Marge utilizando la biblioteca GitPython, también encontrada en Github. Pero un estudio posterior del problema llevó inmediatamente a la idea de encontrar algo relacionado no con git, sino de inmediato con Gitlab. Como resultado, me decidí por la solución más famosa: Python GitLab.

Comencé obteniendo una lista de tareas en el estado apropiado para el lanzamiento . Decidí no alterar fuertemente la solución anterior, sino hacerla más efectiva solicitando de inmediato a la API la tarea de la versión deseada:

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

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

Aunque lo más probable es que ocurra lo mismo debajo del capó, sin embargo, resultó ser más hermoso y óptimo, pero aún no muy legible. Además, de las tareas recibidas, es necesario recopilar enlaces para fusionar las solicitudes. Decidí almacenar las solicitudes de fusión encontradas en namedtuple, son excelentes para esto:

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

Las solicitudes de fusión también se recibieron utilizando la API de 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)   

Después de eso, decidí dónde podría usar las bibliotecas encontradas . Entonces, quizás, refactorizaré estas piezas.

A continuación, debe verificar, de repente, las ramas RC correctas ya existen, si ya hubo intentos de compilación, entonces deben eliminarse y crearse otras nuevas. Ya hice esto usando la 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'})

Después de eso, puede comenzar a llenar la tabla en la tarea de ensamblaje de Jira . La información en la tabla está contenida en las siguientes columnas: No., Tarea, Prioridad, Solicitudes de fusión de la tarea en RC, Estado de la solicitud de fusión (si las pruebas aprobadas en Gitlab, si hay conflictos o no, se vierte / no se vierte).

En este paso, me encontré con un defecto desagradable de Gitlab: si los cambios se congelaron previamente en la rama de destino de la solicitud de fusión, entonces la API de Gitlab, al solicitar el estado de la solicitud de fusión, da una respuesta sobre la presencia de conflictos. En principio, esto se puede entender: si no hay nada que verter, entonces no funcionará, pero ¿por qué decir que hay un conflicto en este lugar? Los foros de Gitlab han estado preguntando a la gente durante varios años, pero aún no hay respuestas.

En mi caso, esto lleva a un estado de conflicto falso y a verificar manualmente la solicitud de fusión. Todavía no se me ocurrió nada.

La lógica al crear solicitudes de fusión es la siguiente: si no se pasan las pruebas, o si hay un conflicto, se crean solicitudes de fusión, pero no fluyen hacia RC, los estados correspondientes se colocan en la tabla.

Para verificar la ejecución de las pruebas, buscamos una lista de tuberías adecuadas. Gitlab los emite ordenados por fecha en orden descendente, tal como lo necesitamos. Tomamos el primero: será el que se necesita:

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

if pipelines:

   pipelines = pipelines[0]

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

       status = '(x)   !, '

Luego cree las solicitudes de fusión ellos mismos . Verificamos los abiertos, en caso de que este no sea el primer intento de compilación. En ausencia de la solicitud de fusión requerida, cree esto:

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 ('-', '_'): cambie el nombre de la tarea para deshacerse de los comentarios poco informativos de Gitlaba sobre la tarea en Jira.

A continuación, observamos el estado de la solicitud de fusión recibida : si no hay conflicto, lo vertimos en RC. Si hay un conflicto, anote los estados apropiados y deje la verificación manual.

Además, por analogía, creamos solicitudes de fusión de RC a Staging y de Staging a Master para todos los proyectos. Se verterán después de verificar toda la versión de lanzamiento en las ramas RC.

Se agregaron dos campos a la plantilla de tareas de Jira: para acciones previas y posteriores a la publicación. Para todas las tareas involucradas, el algoritmo recopila una lista de tales acciones y entra en la tarea de ensamblaje para no olvidar nada.

Y finalmente, el resultado en Jira es la creación de una tarea de ensamblaje :

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)

Otros pensamientos son los siguientes : ejecutar un script en Gitlab con un enlace web de Jira. Por ejemplo, puede hacer un webhook para crear una tarea de ensamblaje (crear un tipo especial de tarea para ella) o crear una tarea con la palabra "Ensamblaje" en el título. Y Gitlab para este gancho ejecutará un script bash que inicia todo el proceso o generará una imagen de Docker con Python y ejecutará un script en él. Aunque la segunda opción ya es demasiado complicada. Sí, y necesito cuentas técnicas en Jira y Gitlab. En general, aún no hay una decisión final.

Después de crear ramas RC, también puede implementar un banco de pruebas en las ramas necesarias y ejecutar pruebas de regresión. Jenkins puede manejar esto. Pero esto también está en los planes por ahora.

Todo esto fue en aras de acelerar el trabajo de los probadores y liberar a los desarrolladores del trabajo de rutina. Sin embargo, el sentido económico aquí también es bastante específico: tome un desarrollador promedio (en el vacío) con un salario hipotético de 150,000 rublos por 8 horas de tiempo de trabajo por día. Durante dos años, tuvimos alrededor de 700 lanzamientos, esto es aproximadamente el lanzamiento por día. Un poco más, algo menos, pero creo que en promedio, al menos una hora del tiempo del desarrollador para construir el lanzamiento se había ido. Es decir, la automatización de este proceso le ahorra a la empresa un mínimo de 150,000 / 8 = 18,750 rublos por mes.

En el camino, hice un guión separado para mí, mostrando estadísticas sobre el próximo lanzamiento: cuántas tareas, qué proyectos se ven afectados, etc. Dado que si no tengo estado de desarrollador en ninguno de los proyectos en Gitlab, habrá una denegación de acceso al crear solicitudes de fusión. Además, es conveniente saber de antemano acerca de las acciones de implementación o detectar una tarea que cayó en esta versión por error. La distribución de las notificaciones de lanzamiento se resolvió utilizando los módulos SMTP y de correo electrónico.

Estaba satisfecho con la solución a este problema: siempre es interesante aprender algo nuevo y ponerlo en práctica. Será bueno si esta experiencia es útil para alguien.

All Articles