web-dev-qa-db-fra.com

Comment définir des hôtes cibles dans un fichier Fabric

Je souhaite utiliser Fabric pour déployer mon code d'application Web sur des serveurs de développement, de stockage intermédiaire et de production. Mon fabfile:

def deploy_2_dev():
  deploy('dev')

def deploy_2_staging():
  deploy('staging')

def deploy_2_prod():
  deploy('prod')

def deploy(server):
  print 'env.hosts:', env.hosts
  env.hosts = [server]
  print 'env.hosts:', env.hosts

Exemple de sortie:

Host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: ['dev']
No hosts found. Please specify (single) Host string for connection:

Lorsque je crée une tâche set_hosts(), comme indiqué dans le docs Fabric , env.hosts est défini correctement. Cependant, ce n'est pas une option viable, ni un décorateur. Passer des hôtes sur la ligne de commande aboutirait finalement à une sorte de script Shell qui appelle la fabfile. Je préférerais qu'un seul outil fasse le travail correctement.

Dans la documentation Fabric, il est indiqué que "env.hosts est simplement un Python objet liste"). D'après mes observations, ce n'est tout simplement pas vrai.

Quelqu'un peut-il expliquer ce qui se passe ici? Comment définir l'hôte sur lequel déployer?

107
ssc

Je le fais en déclarant une fonction réelle pour chaque environnement. Par exemple:

def test():
    env.user = 'testuser'
    env.hosts = ['test.server.com']

def prod():
    env.user = 'produser'
    env.hosts = ['prod.server.com']

def deploy():
    ...

En utilisant les fonctions ci-dessus, je taperais les éléments suivants à déployer dans mon environnement de test:

fab test deploy

... et les suivants à déployer en production:

fab prod deploy

La bonne chose à faire de cette façon est que les fonctions test et prod peuvent être utilisées avant la fonction toute fab, pas seulement la déployer. C'est incroyablement utile.

128
Zac

Utilisez roledefs

from fabric.api import env, run

env.roledefs = {
    'test': ['localhost'],
    'dev': ['[email protected]'],
    'staging': ['[email protected]'],
    'production': ['[email protected]']
} 

def deploy():
    run('echo test')

Choisissez le rôle avec -R:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...
75
thomie

Voici une version plus simple de serverhorrors answer :

from fabric.api import settings

def mystuff():
    with settings(Host_string='192.0.2.78'):
        run("hostname -f")
49
tobych

Était coincé sur moi-même, mais a finalement compris. Vous simplement ne pouvez pas définir la configuration env.hosts à partir de dans une tâche. Chaque tâche est exécutée N fois, une fois pour chaque hôte spécifié, de sorte que le paramètre est en dehors de la portée de la tâche.

En regardant votre code ci-dessus, vous pouvez simplement faire ceci:

@hosts('dev')
def deploy_dev():
    deploy()

@hosts('staging')
def deploy_staging():
    deploy()

def deploy():
    # do stuff...

Ce qui semble vouloir faire ce que vous avez l'intention de faire.

Ou vous pouvez écrire du code personnalisé dans la portée globale qui analyse manuellement les arguments et définit env.hosts avant que votre fonction de tâche ne soit définie. Pour quelques raisons, c'est en fait ainsi que j'ai créé la mienne.

21
GoldenBoy

Depuis la fab 1.5, il s'agit d'une méthode documentée pour définir de manière dynamique les hôtes.

http://docs.fabfile.org/fr/1.7/usage/execution.html#dynamic-hosts

Citation de la doc ci-dessous.

Utilisation de execute avec des listes d'hôtes définies de manière dynamique

Un cas d'utilisation courant intermédiaire à avancé pour Fabric consiste à paramétrer la recherche de la liste d'hôtes cible au moment de l'exécution (lorsque l'utilisation de rôles ne suffit pas). execute peut rendre cela extrêmement simple, comme ceci:

from fabric.api import run, execute, task

# For example, code talking to an HTTP API, or a database, or ...
from mylib import external_datastore

# This is the actual algorithm involved. It does not care about Host
# lists at all.
def do_work():
    run("something interesting on a Host")

# This is the user-facing task invoked on the command line.
@task
def deploy(lookup_param):
    # This is the magic you don't get with @hosts or @roles.
    # Even lazy-loading roles require you to declare available roles
    # beforehand. Here, the sky is the limit.
    Host_list = external_datastore.query(lookup_param)
    # Put this dynamically generated Host list together with the work to be
    # done.
    execute(do_work, hosts=Host_list)
18
j-a

Contrairement à d'autres réponses, il est possible de modifier les variables d'environnement env d'une tâche. Cependant, ce env ne sera utilisé que pour les tâches ultérieures exécutées à l'aide de la fonction fabric.tasks.execute.

from fabric.api import task, roles, run, env
from fabric.tasks import execute

# Not a task, plain old Python to dynamically retrieve list of hosts
def get_stressors():
    hosts = []
    # logic ...
    return hosts

@task
def stress_test():
    # 1) Dynamically generate hosts/roles
    stressors = get_stressors()
    env.roledefs['stressors'] = map(lambda x: x.public_ip, stressors)

    # 2) Wrap sub-tasks you want to execute on new env in execute(...)
    execute(stress)

    # 3) Note that sub-tasks not nested in execute(...) will use original env
    clean_up()

@roles('stressors')
def stress():
    # this function will see any changes to env, as it was wrapped in execute(..)
    run('echo "Running stress test..."')
    # ...

@task
def clean_up():
    # this task will NOT see any dynamic changes to env

Sans inclure les sous-tâches dans execute(...), vos paramètres env de niveau module ou tout ce qui est passé à partir de la fab CLI seront utilisés.

10
pztrick

Pour expliquer pourquoi c'est même un problème. La commande fab exploite la bibliothèque pour exécuter les tâches sur les listes d'hôtes. Si vous essayez de modifier la liste des hôtes dans une tâche, vous tentez essentiellement de modifier une liste tout en effectuant une itération. Ou, dans le cas où aucun hôte n'est défini, bouclez sur une liste vide dans laquelle le code sur lequel vous définissez la liste en boucle n'est jamais exécuté.

L'utilisation de env.Host_string est un moyen de contourner ce problème uniquement dans le sens où elle spécifie directement aux fonctions les hôtes avec lesquels se connecter. Cela pose certains problèmes en ce sens que vous allez refaire la boucle d'exécution si vous voulez avoir plusieurs hôtes sur lesquels exécuter.

Le moyen le plus simple de configurer les hôtes au moment de l’exécution est de laisser l’environnement peupler en tant que tâche distincte, qui configure toutes les chaînes de l’hôte, les utilisateurs, etc. Ensuite, ils exécutent la tâche de déploiement. Cela ressemble à ceci:

fab production deploy

ou

fab staging deploy

Où la mise en scène et la production sont similaires aux tâches que vous avez confiées, mais elles n'appellent pas la tâche suivante elles-mêmes. La raison pour laquelle il doit fonctionner comme ceci est que la tâche doit être terminée et sortir de la boucle (des hôtes, dans le cas env. None, mais c'est une boucle de un à ce moment-là), puis laisser la boucle passer. les hôtes (maintenant définis par la tâche précédente) à nouveau.

9
Morgan

Vous devez définir Host_string un exemple serait:

from fabric.context_managers import settings as _settings

def _get_hardware_node(virtualized):
    return "localhost"

def mystuff(virtualized):
    real_Host = _get_hardware_node(virtualized)
    with _settings(
        Host_string=real_Host):
        run("echo I run on the Host %s :: `hostname -f`" % (real_Host, ))
9
serverhorror

Je suis totalement nouveau dans la structure, mais pour que la structure exécute les mêmes commandes sur plusieurs hôtes (par exemple, pour déployer sur plusieurs serveurs, en une seule commande), vous pouvez exécuter:

fab -H staging-server,production-server deploy 

serveur de stockage intermédiaire et serveur de production sont deux serveurs pour lesquels vous souhaitez exécuter l'action de déploiement. Voici un fichier fabfile.py simple qui affichera le nom du système d'exploitation. Notez que fabfile.py doit se trouver dans le même répertoire que celui où vous exécutez la commande fab.

from fabric.api import *

def deploy():
    run('uname -s')

Cela fonctionne avec le tissu 1.8.1 au moins.

3
Brad Parks

Vous devez modifier env.hosts au niveau du module, pas dans une fonction de tâche. J'ai fait la même erreur.

from fabric.api import *

def _get_hosts():
    hosts = []
    ... populate 'hosts' list ...
    return hosts

env.hosts = _get_hosts()

def your_task():
    ... your task ...
3
mlbright

Donc, pour définir les hôtes et faire en sorte que les commandes soient exécutées sur tous les hôtes, vous devez commencer par:

def PROD():
    env.hosts = ['10.0.0.1', '10.0.0.2']

def deploy(version='0.0'):
    Sudo('deploy %s' % version)

Une fois ceux-ci définis, exécutez la commande sur la ligne de commande:

fab PROD deploy:1.5

Ce qui exécutera la tâche de déploiement sur tous les serveurs répertoriés dans la fonction PROD, car il définit env.hosts avant d'exécuter la tâche.

3
athros

C'est très simple. Il suffit d'initialiser la variable env.Host_string et toutes les commandes suivantes seront exécutées sur cet hôte.

from fabric.api import env, run

env.Host_string = '[email protected]'

def foo:
    run("hostname -f")
3
Vladimir Osintsev

Vous pouvez affecter à env.hoststring avant d'exécuter une sous-tâche. Affectez cette variable globale dans une boucle si vous souhaitez effectuer une itération sur plusieurs hôtes.

Malheureusement pour vous et moi, le tissu n'est pas conçu pour ce cas d'utilisation. Découvrez la fonction main sur http://github.com/bitprophet/fabric/blob/master/fabric/main.py pour voir comment cela fonctionne.

2
Andrew B.

Voici un autre modèle "summersault" qui permet l'utilisation de fab my_env_1 my_command:

Avec ce modèle, il suffit de définir les environnements une seule fois à l’aide d’un dictionnaire. env_factory Crée des fonctions basées sur les noms de clé de ENVS. J'ai mis ENVS dans son propre répertoire et le fichier secrets.config.py Pour séparer la configuration du code de structure.

L’inconvénient est que, comme il est écrit, l’ajout du décorateur @task Aura le casser .

Notes: Nous utilisons def func(k=k): au lieu de def func(): en usine à cause de liaison tardive . Nous obtenons le module en cours d'exécution avec cette solution et le corrigeons pour définir la fonction.

secrets.config.py

ENVS = {
    'my_env_1': {
        'HOSTS': [
            'Host_1',
            'Host_2',
        ],
        'MY_OTHER_SETTING': 'value_1',
    },
    'my_env_2': {
        'HOSTS': ['Host_3'],
        'MY_OTHER_SETTING': 'value_2'
    }
}

fabfile.py

import sys
from fabric.api import env
from secrets import config


def _set_env(env_name):
    # can easily customize for various use cases
    selected_config = config.ENVS[env_name]
    for k, v in selected_config.items():
        setattr(env, k, v)


def _env_factory(env_dict):
    for k in env_dict:
        def func(k=k):
            _set_env(k)
        setattr(sys.modules[__name__], k, func)


_env_factory(config.ENVS)

def my_command():
    # do work
2
whp

L'utilisation de rôles est actuellement considérée comme la manière "correcte" et "correcte" de le faire et c'est ce que vous "devriez" faire.

Cela dit, si vous êtes comme la plupart de ce que vous "voulez" ou "désirez", c'est la possibilité d'effectuer un "système tordu" ou de changer de système cible à la volée.

Ainsi, à des fins de divertissement uniquement (!), L'exemple suivant illustre ce que beaucoup pourraient considérer comme une manœuvre risquée, et pourtant tout à fait satisfaisante, qui ressemble à ceci:

env.remote_hosts       = env.hosts = ['10.0.1.6']
env.remote_user        = env.user = 'bob'
env.remote_password    = env.password = 'password1'
env.remote_Host_string = env.Host_string

env.local_hosts        = ['127.0.0.1']
env.local_user         = 'mark'
env.local_password     = 'password2'

def perform_sumersault():
    env_local_Host_string = env.Host_string = env.local_user + '@' + env.local_hosts[0]
    env.password = env.local_password
    run("hostname -f")
    env.Host_string = env.remote_Host_string
    env.remote_password = env.password
    run("hostname -f")

Puis en cours d'exécution:

fab perform_sumersault
0
user1180527