Fabric: 'roles' compatibles con v2 o similares

Creado en 22 abr. 2017  ·  7Comentarios  ·  Fuente: fabric/fabric

Sinopsis

Al momento de escribir este artículo, la rama v2 tiene una clase Group que debería ser capaz de servir como las unidades antes conocidas como 'roles', también conocido como "un montón de hosts para hacer cosas con / en".

Sin embargo, todavía no existe una forma específica de organizar o etiquetar objetos Group ; está lo suficientemente "hecho" para el caso de uso de API puro de usuarios avanzados que desean desarrollar su propia forma específica de crearlos, pero carece de algo para usuarios orientados a CLI o personas intermedias que desean algo con un marco para construir.

Dicho de otra manera, a menos que esté trabajando exclusivamente con la API, tener objetos de grupo en algún lugar es inútil si la CLI o los bits de llamada de tareas no tienen forma de encontrarlos.

Fondo

En v1, los roles eran efectivamente un único espacio de nombres plano que asignaba etiquetas de cadena simples a lo que serían Grupos en v2, y podían seleccionarse en la CLI en tiempo de ejecución ( fab --roles=web,db ) y / o registrarse como destinos predeterminados para tareas ( @task('db') \n def migrate(): ), al igual que los hosts.

Los usuarios los definieron en env.roledefs , un diccionario simple; cualquier funcionalidad intermedia a avanzada giraba en torno a modificarla, generalmente en tiempo de ejecución (a través de una tarea previa o subrutina), a veces en el momento de carga del módulo.

Casos de uso / necesidades / subfunciones específicas

  • Mapeo básico e ingenuo para uso / referencia en cualquier otro lugar del sistema: ingrese un nombre, obtenga algunos iterables de Group sy / o Connection s.

    • El aliasing a menudo quiere ir junto con eso, por ejemplo, un Lexicon lugar de un dict .

    • Construcciones aún más profundas, como 'empaquetado', por ejemplo, tiene asignaciones directas llamadas db , web , lb , pero luego un nombre de segundo nivel llamado prod que es siempre la unión de los otros tres. Olvidé si agregué eso a Lexicon todavía. Es posible que haya otras subclases de mapas que ya lo hagan.

    • Además / alternativamente, cosas como globbing u otras sintaxis de cadena, aunque personalmente preferiría aprovechar el hecho de que Python no está "escrito en cadena" ...

  • Útil 'mapeo inverso' para que pueda identificar a qué grupos pertenece una conexión determinada.

    • Problemático: debido a que actualmente no hay un estado compartido global, la respuesta ingenua a esto, usar la identidad, falla porque técnicamente puede crear múltiples objetos Connection idénticos.

    • Especialmente porque Group puede crearlos implícitamente en su nombre si solo le da cadenas de host abreviadas, aunque esa es solo una opción de conveniencia.

    • Sin embargo, dada la restricción de ningún estado global, no puedo ver problemas obvios con el uso de pruebas de igualdad en su lugar, por lo que debería ser factible, por ejemplo, if cxn in group funcionaría incluso si cxn es un objeto distinto del miembro igual dentro de group .

    • Lo único que me viene a la mente es si hubiera vínculos fuertes y con estado de una Conexión a un Grupo (tendrían que ser grupos, en plural) que lo sostuvieran, en lugar de viceversa, pero no puedo ver grandes razones para eso de repente.

  • Muy relacionado con lo anterior: capacidad para inspeccionar / mostrar cuál es el "rol que se está ejecutando actualmente" (algo que la gente quería durante mucho tiempo en la versión 1 que no era trivial debido a su diseño)

    • El problema principal es que se trata en realidad de dos preguntas semi distintas: "¿de qué rol (s) es la parte del host actual, en términos generales" (básicamente, ese caso de uso anterior de la búsqueda inversa) pero también "qué rol (s) era el maquinaria de ejecución específicamente solicitada para correr en contra ".

    • En otras palabras, dado el host 'foo' que pertenece a los roles A, B y C: dentro de una tarea dada cuyo contexto es 'foo', pero que se ejecutó debido a una solicitud para 'ejecutar en el rol A', es un usuario que busca una respuesta de "A, B y C" (los roles 'foo' está en general) o simplemente "A" (el rol actualmente en ejecución)?

    • Esto realmente se siente como dos llamadas API distintas, a pesar de que las solicitudes de funciones que recuerdo haber combinado las dos.

  • Selección de destino en la CLI, globalmente y / o por tarea

    • Una extensión del sistema CLI de Invoke para tener en cuenta "indicadores de que todas las tareas superan lo que definen" puede ser útil o necesaria para esto. Lo cual cae firmemente en el territorio de pyinvoke / invoke # 205, de hecho, por lo que acaba de obtener una prioridad más alta de lo que ya era (que era bastante alta).

  • Lo mismo ocurre con los valores predeterminados a nivel de tarea

    • Aunque los valores predeterminados de destino a nivel de tarea realmente quieren ser cualquiera de: conexión, conexiones, obj de grupo, obj de grupo o nombre que evalúa _to_ obj de grupo (podría decirse que eso último es lo único que pertenece directamente a este ticket)

  • Ídem valores predeterminados de nivel de colección (¡NUEVO en v2!)

    • Es decir, "todas las tareas en $ submodule se ejecutan de forma predeterminada en el rol db "

    • El mismo trato que en el punto anterior: este valor predeterminado quiere permitir una cantidad de valores diferentes, no solo una clave de cadena.

  • ¿Algo más nuevo y emocionante habilitado por un enfoque OO que realmente quiera estar de acuerdo con esto? Recuerde que el énfasis debe estar en los componentes básicos y en la habilitación de usuarios avanzados, no en, por ejemplo, reinventar totalmente sistemas como Chef o Ansible.

Ideas / preocupaciones de implementación

  • Si usamos el sistema de configuración como el vector de almacenamiento principal, los valores "quieren" ser primitivos para que puedan almacenarse en yaml, json, etc., pero esa es una lata de gusanos que terminan con "almacenar todos los kwargs de grupo / conexión en un gran ol ' list-o-dicts ", etc.
  • Si esperamos que las definiciones estén principalmente en Python, simplemente podemos decir "crear instancias de objetos de grupo", y luego tenemos la opción de fusionar esos datos en el sistema de configuración o dejarlo independiente de alguna manera.

    • Creo que prefiero lo último porque meter literalmente todo en los dictados de configuración anidados parece que conducirá a malas noticias.

  • Las construcciones más profundas como el aliasing y la agrupación agregan complejidad y problemas de orden (es decir, imagine una configuración de alias trivial donde el valor de key1 es un grupo pero el valor de key2 es key1; ahora debe rastrear la estructura dos veces para resolver o verificar key2)

    • aunque si optamos por un enfoque mayormente "hazlo en python", se vuelve muy parecido a la API del sistema de configuración, donde puedes comenzar con una estructura declarativa pero cualquier otra cosa se habilita mediante llamadas a métodos después de esa configuración inicial. ¿No creo que eso sea horrible? EDITAR: y creo que así es exactamente como funciona Lexicon de todos modos.

  • Independientemente del formato, tenemos que averiguar cómo los usuarios avanzados querrán generarlo sobre la marcha a partir de fuentes externas o similares; esto más los problemas con el aliasing y demás, implica que es posible que no queramos esto en una estructura ingenua "almacenada" en algún lugar, sino como una API en algún objeto u objetos que se llama para generarlo.

    • Sospecho que es posible que queramos trabajar 'hacia abajo' a partir de la selección de roles / grupos, llegando a lo que sea el API de nivel más alto para "convertir lo que el usuario proporcionó en una unidad procesable de objetivos", porque los usuarios más avanzados necesariamente querrán tener control total sobre la implementación de esa llamada a la API. Entonces, como siempre, podemos proporcionar lo que se siente como un caso común útil, pero que está claramente marcado como "solo una forma de hacerlo".

    • @RedKrieg tiene una idea ingeniosa en este sentido en la que tenemos @group como @task , y las funciones no son unidades de trabajo ejecutables, sino que producen objetos de grupo.

    • Este enfoque reutiliza de forma nativa la jerarquía de tareas (Colección), lo cual es práctico (por qué reinventar la rueda) y elegante (porque en los casos del mundo real, las definiciones de rol / grupo con frecuencia se corresponden muy de cerca con las tareas que las utilizan).



      • También funciona bien incluso si sus grupos NO se asignan a sus tareas, porque simplemente puede escribir las definiciones en su nivel de colección raíz. Pan comido.



    • No me queda claro si esto es mejor devolver un solo grupo de cada función, o si queremos la capacidad de producir múltiples grupos (o conexiones), o si es mejor hacerlo no como funciones decoradas en absoluto, sino simplemente como llamadas a API en Colección (como cómo se almacenan las configuraciones a nivel de colección).

    • Por ejemplo, el caso de uso en el que los datos de grupo / rol son dinámicos y están fuera de Fabric aún deben resolverse aquí (por eso antes señalé que primero debemos identificar la API de nivel más alto para este espacio; luego debemos ver cómo se combina eso con esta idea de nivel intermedio.)

Feature

Comentario más útil

Hola, no sé qué pasó con este software después de muchos años, pero realmente extrañé el concepto de "roles" en [email protected] , especialmente cuando se ejecuta $ fab -R dev

Todos 7 comentarios

De la lista de correo:

Mejoramos nuestra propia API REST interna que llena env.roledefs dinámicamente dependiendo del proyecto que se está implementando y confiamos en gran medida en no incrustar cadenas de host en el fabfile del proyecto o especificarlas en CLI.

Nuestros casos de uso son:

  1. Código base libre de entorno https://12factor.net/config. Los entornos (roles) y sus respectivas cadenas de host se almacenan en una base de datos centralizada. Cada fabfile.py tiene algo como esto (rellena env.roledefs cuando se importa el archivo):
EnvironmentDatabaseAPIClient(
    'https://rest.api.url/schema/',
    env.service_name,
).apply_env()
  1. Número de entornos de servidor: múltiples entornos de prueba (algunos de ellos son privados, otros públicos) y múltiples entornos de producción (para diferentes clientes). Cada entorno consta de uno o más hosts y se asigna a la función de la estructura.

  2. Cada servicio ( env.service_name en el ejemplo anterior) tiene un conjunto diferente de entornos.

  3. También tenemos meta-roles (grupos de roles). Tienen el prefijo group- : group-production , group-test , group-external , group-internal , group-all . Esto nos permite implementar en múltiples roles de servidor sin especificarlos uno por uno, por ejemplo group-all implementa en todos los roles, tanto de producción como de prueba.

  4. Tenemos tareas especiales de estructura para imprimir información sobre grupos de roles, roles y hosts.

  5. También confiamos en gran medida en el mapeo inverso de las cadenas de host a los nombres de los roles (las cadenas de hosts son únicas por service_name). Se utiliza para el registro y las notificaciones de implementación. Básicamente, registramos las implementaciones de servicios en cada host y enviamos una notificación de Slack cuando el servicio se ha implementado en todos los hosts en un rol. El servidor EnvironmentDatabaseAPI es responsable de esto (mantiene los registros y el estado de implementación). Esto se hace decorando las tareas de la tela con un decorador que envía env.host , env.port y env.service_name (más información de confirmación) al servidor API.

  6. Planeamos agregar la autenticación de implementación en el futuro, también es muy probable que extraigaremos más variables env del servidor para que estén disponibles dentro del contexto de la tarea.

¡Gracias @ max-arnold! También reconozco muchos de mis propios casos de uso en el pasado. Recuerdo que el bit de mapeo inverso en particular apareció en v1 varias veces, así que lo agregué a la lista.

Para que Fabric v2 me sea útil, necesitaría una forma de decirle a fab qué conjunto de hosts ejecutar una tarea.

Anteriormente, definí roles y luego ejecuté fab -R ... . (En realidad, los roles se definieron mediante programación utilizando un rango de direcciones IP, pero eso no es un requisito y una lista estática dentro de un archivo YAML estaría bien).

No encuentro un equivalente en Fabric v2, y tampoco pude emular esta función usando:

  • un archivo de configuración fabric.yaml que contiene
active_hostset: null
hostsets:
  myhostset:
  - ...
  • active_hostset = config["hostsets"][config["active_hostset"]] en fabfile.py
  • env INVOKE_ACTIVE_HOSTSET=myhostset fab ...

En lugar de la lista esperada de hosts, obtengo KeyError: 'active_hostset' .

Asignamos diferentes conjuntos de hosts a cada rol para cada uno de nuestros entornos en fabric v1, y el entorno se configura ejecutando una tarea role.environment:staging para especificarlo. Entonces, esta tarea influye en los hosts utilizados por las siguientes tareas.

En la v2 intentamos usar una Tarea personalizada, pero el problema es que Executor.expand_calls ejecuta antes de que se ejecute nuestra tarea role.environment y, por lo tanto, ninguna de las siguientes tareas conoce el entorno para construir dinámicamente sus listas de hosts.

Hacer de Executor.expand_calls un generador permite que la ejecución de tareas influya en la ejecución de tareas posteriores. Entonces, mi ejemplo anterior funciona, donde tenemos un Task que necesita conocer su entorno para expandir adecuadamente los roles a los hosts. por ejemplo, fab role.environment dev deploy.app - la tarea role.environment ahora se ejecuta antes de que deploy.app se expanda, por lo que deploy.app conoce el entorno y puede configurar sus hosts y luego se expande a el conjunto correcto de tareas.

Hice un prototipo de esto en mis horquillas:
https://github.com/pyinvoke/invoke/compare/master...rectalogic : expand-generator
https://github.com/fabric/fabric/compare/master...rectalogic : expand-generator

Hola, no sé qué pasó con este software después de muchos años, pero realmente extrañé el concepto de "roles" en [email protected] , especialmente cuando se ejecuta $ fab -R dev

También usamos roles para representar el mismo conjunto de operaciones en diferentes entornos. ¿Quizás sería útil separar el concepto de un rol con nombre y un entorno con nombre? Como en el rol web en el entorno de desarrollo.

¿Fue útil esta página
0 / 5 - 0 calificaciones