Fabric: Éviter éventuellement d'utiliser ssh si vous allez sur localhost

Créé le 19 août 2011  ·  59Commentaires  ·  Source: fabric/fabric

La description

run()/sudo() verrait intelligemment que vous allez sur localhost et exécutez simplement local() à la place. Ce serait probablement une chose facultative.

Commentaires de Jeff sur IRC :

et oui, je veux dire qu'il y aura toujours des frais généraux avec ssh vs tuyaux droits à la main, je ne pense pas qu'il serait très difficile de mettre à jour run/sudo (surtout dans master maintenant qu'ils ont été refactorisés) pour appeler/retourner local ( ) intelligemment, je ne suis pas certain que je voudrais ce comportement semi-magique dans le noyau (même s'il est désactivé par défaut avec une option pour l'activer, mais cela aiderait) mais même ainsi, ce serait une expérience intéressante. et si c'est aussi simple que je le pense, honnêtement, je ne peux pas trouver une bonne raison de ne pas le faire (encore une fois, à condition que ce ne soit pas le comportement par défaut)

Présentée à l' origine par Nick Welch ( mackstann ) sur 2009-11-11 à 13:39 EST

Rapports

  • Dupliqué par #364 : autoriser l'opération locale à contourner la couche SSH
  • Lié à #26 : Implémenter la fonctionnalité « essai à vide »
Feature Network

Commentaire le plus utile

Si quelqu'un se demande "pourquoi quelqu'un ferait-il cela ?", la réponse est que si vous avez un pipeline de déploiement, il peut être utile d'exécuter exactement le même script de déploiement, quel que soit l'environnement, plutôt que d'avoir un script de configuration spécial pour localhost contre tout le reste.

Tous les 59 commentaires

James Pearson (xiong.chiamiov) a posté :


Comme mentionné également sur irc, je n'exécute normalement pas de serveur ssh sur un ordinateur de bureau, je ne peux donc pas réellement ssh vers localhost.


sur 2009-11-11 à 15:13 EST

Travis Swicegood ( tswicegood ) a posté :


Je viens d'implémenter quelque chose de similaire ce soir sous la forme d'une nouvelle fonction fabric.operations appelée do . Il regarde env.run_as pour voir s'il est égal à "local", et ce faisant passe à la méthode local au lieu de la méthode run (ou sudo si sudo=True est passé en tant que kwarg). Il gère également le préfixe des commandes locales avec sudo dans le cas où elles s'exécutent en local.

C'est une manière différente de contourner ce problème qui fonctionne sans changer le comportement de run ou sudo . Ces modifications sont disponibles dans mon référentiel .


le 2010-01-11 à 00h22 HNE

Morgan Goose ( goosemo ) a posté :


Je ne vois vraiment pas cela comme plausible. Quel est l'intérêt de faire fonctionner en tant que local. L'une des exigences de Fabric est que sshd s'exécute sur la machine, à distance ou en boucle. L'autre problème étant que seul le changement de local ne prend pas en compte put, get, rsync_project et d'autres qui auraient tous encore besoin de ssh. Essayer de les implémenter causerait vraiment plus de problèmes, car il s'agit maintenant de traduire les fabfiles en bash.


le 13/03/2011 à 23h14 HAE

Jeff Forcier ( bitprophet ) a posté :


Bien que je ne sois pas non plus convaincu à 100% que c'est une excellente idée, c'est clairement quelque chose dont un certain nombre d'utilisateurs ressentent le besoin - une autre demande a été déposée en tant que #364 avec une autre explication du cas d'utilisation.

J'ai également ajouté le ticket de tirage à sec en rapport avec celui-ci, car (je suppose - si l'un des utilisateurs demandeurs peut le vérifier, ce serait formidable) le principal cas d'utilisation de cette fonctionnalité est le test / le séchage. fonctionnement.


le 23/06/2011 à 11h26 HAE

Comme indiqué dans le #538, si nous parvenons un jour à normaliser complètement les trois coureurs afin qu'ils puissent être utilisés de manière interchangeable, nous devrons nous assurer que l'échappement du shell fonctionne de manière cohérente sur eux. Pour le moment, nous n'utilisons pas d'échappement de shell local , bien que ce soit au moins en partie parce qu'il n'utilise pas de shell wrapper.

Si quelqu'un se demande "pourquoi quelqu'un ferait-il cela ?", la réponse est que si vous avez un pipeline de déploiement, il peut être utile d'exécuter exactement le même script de déploiement, quel que soit l'environnement, plutôt que d'avoir un script de configuration spécial pour localhost contre tout le reste.

+1 pour la fonctionnalité

+1

+10

+1

+1

Pour vous retenir, vous pouvez simplement vous assurer que le serveur OpenSSH est en cours d'exécution. Faites d'abord sudo apt-get install ssh pour vous assurer que vous l'avez installé (même si vous pensez l'avoir). Ensuite, faites sudo service ssh start | stop | restart au besoin. Appris de ce fil .

+1

Mon cas d'utilisation est simple : je souhaite utiliser le même script django-deploy pour configurer les instances ec2 à la fois avec cloud-init via CloudWatch (le cas pour exécuter des commandes locales) et en utilisant le fab deploy_django -H foo@bar .

+1

Ce serait vraiment utile. Un cas d'utilisation que j'ai consiste à utiliser un fournisseur de shell vagrant pour configurer une machine virtuelle particulière à l'aide de Fabric et sans avoir besoin de ssh localhost.

+1

J'ai été surpris de ne pas voir cela déjà dans Fabric.

Pour info : la mise en œuvre de cette fonctionnalité devient plus complexe lorsque vous pensez à des fonctions de structure telles que reboot() .

+1

Devrait déjà faire partie du noyau !

+1

Cela serait parfaitement logique : d'un point de vue abstrait, local n'est qu'un cas particulier de run , où aucune machinerie SSH n'est impliquée.

Une dernière chose à souligner (peut-être évidente) : Fabric devrait être suffisamment intelligent pour décider si un run doit être converti en local APRÈS avoir lu /etc/hosts.

Je veux dire : si nous avons

env.host = [ 'mywebserver' ]

et dans /etc/hosts nous avons :

127.0.0.1 mywebserver

alors, tous les appels run devraient en fait être des appels local .

En poussant ce concept un peu plus loin, nous devrions également traiter run comme un appel local lorsque l'hôte distant se résout en une adresse IP qui est attribuée à une interface réseau de la machine locale.
Par exemple:
fabfile :

env.host = [ 'mywebserver' ]

/etc/hosts :

192.168.1.1 mywebserver

ip addr :

[...]
eth0:
  inet 192.168.1.1
[...]

+1

+1 :+1:

:+1:

+1

+1

Fabric 2 utilisera pyinvoke/invoke donc cela devrait être assez facile à faire là-bas. J'attendrais Fabric 2 pour un moyen non piraté de le faire.

:+1:

+1

:+1:

:+1: Veuillez implémenter ceci, d'autant plus que les ordinateurs mac ne sont pas automatiquement configurés pour avoir des tunnels SSH configurés pour l'accès à distance au serveur localhost.

+1

+1 :)

+1 s'il vous plait

:+1:

:+1:

:+1:

Nous utilisons Fab pour construire des paquets Debian et cela ajoute une complexité supplémentaire

les gars, bonjour à tous
J'essaie de créer un clone de tissu avec différence :

  • La fonction run() fonctionne de la même manière avec subprocess.popen sous localhost que sous ssh connect to remote host
  • Factory utilise openssh ou tout autre client ssh (vous devez modifier la configuration pour cela), vous pouvez donc utiliser toute la puissance des sockets ssh
  • Factory utilise la bibliothèque gevent pour l'exécution asynchrone

Vous pouvez jeter un oeil si vous avez besoin de cette fonctionnalité
https://github.com/Frizz-zy/factory

Il me manque peut-être quelque chose dans cette discussion, mais voici ce que j'ai fait pour utiliser le même code avec la commande fab run sur les machines locales et distantes.

  1. J'ai mis env.use_ssh_config = True dans mon fabfile.py
  2. ssh-copy-id localhost

Cela ne résout pas votre problème si vous n'exécutez pas de serveur ssh sur votre machine locale

:+1:

+1

+1 Veuillez implémenter cette fonctionnalité :)

+1

Cela pourrait être très utile pour amorcer des images Docker à l'aide de scripts Fabric existants. Cette fonctionnalité éviterait d'installer un serveur SSH sur le conteneur, ce qui va à l'encontre des bonnes pratiques de Docker

+1

+1

+1

Suite à la réponse fournie par @AntoniosHadji , voici les instructions complètes pour que cela fonctionne;

# Generate new SSH key for local usage
ssh-keygen -f ~/.ssh/id_rsa -N ''

# Add server keys to users known hosts (eliminates 'are you sure' messages);
ssh-keyscan -H localhost > ~/.ssh/known_hosts

# Allow user to ssh to itself
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

En fait, cela peut être fait en utilisant la cuisine . Vous devez modifier toutes les exécutions de run pour référencer la fonction cuisine.run , ce qui peut être fait facilement avec une importation, et changer le mode en local :

from cuisine import run, mode_local

mode_local()
print run("echo Hello")

Super @cgarciaarano

Pour les cas d'utilisation simples, cela fonctionne pour moi :

from fabric.api import run, local
# ...
# in task:
  if env.host is None or env.host == 'localhost':
    run = local

:+1:

Je veux que mon fabfile s'exécute à distance ou localement lorsque ssh n'est pas une option. Cela inclut les wrappers locaux pour get/put/exists, etc.

:+1: J'ai des fabfiles qui s'exécutent à la fois localement et à distance et j'ai fini par pirater mes propres fonctions de wrapper pour run/local/get afin de gérer toutes les différences subtiles telles que la capture de sortie et la gestion des erreurs.

Que faire si vous avez une connexion ssh faisant un transfert de port dynamique et une liaison sur 127.0.0.2 (toujours techniquement localhost) sur le port 2223. Je peux voir comment cela pourrait causer des problèmes, à cette fin correspondant à localhost et résolu à 127.0.0.1 plutôt qu'aussi la prise en charge de l'ensemble de la classe 127.0.0.0/8 peut être une bonne idée à gérer.

@blade2005 Oui, toute la plage 127._._.* pointe vers votre hôte local (sauf 127.0.0.0 et 127.255.255.255) mais lorsque vous pointez réellement vers votre hôte local, vous n'utiliserez pas le port, n'est-ce pas ?
Je pense donc que nous pouvons supposer en toute sécurité que 127.*.*.* == localhost et ssh peuvent être évités, mais que 127.*.*.*:* pointe vers un port transféré et ssh est nécessaire.

Honnêtement, cette fonctionnalité aurait probablement plus de sens en tant que plugin tiers construit sur du tissu, similaire à la bibliothèque de cuisine. Ensuite, nous importerions simplement des fonctions encapsulées pour run/get/put/etc, qui sauraient s'il faut s'exécuter localement ou à distance en fonction d'une variable d'environnement. Au moins de cette façon, quelqu'un pourrait démarrer cela pour que tout le monde puisse l'utiliser.

J'ai implémenté quelque chose localement, et c'est beaucoup plus de travail que de simplement basculer entre local/run. Vous devez tenir compte des préfixes, des répertoires modifiés, des utilisateurs sudo, etc.

J'y réfléchissais brièvement dans le contexte d'un autre ticket lié à la 2.0, et j'ai réalisé qu'il y avait autre chose que " run devient une reliure de local " :

  • Toute sorte de tâche en mode véritablement mixte utilisant à la fois local et run , ou l'un des put / get , devient intrinsèquement problématique : des opérations avec des 'local' et 'remote' "se termine" maintenant tous les deux localement.

    • Je suppose qu'il s'agit d'un cas d'utilisation minoritaire (s'il en est un) mais il doit encore être compris, même s'il "appelle n'importe quelle opération mais run ou sudo augmente DoesntMakeAnySenseError " ou autre.

    • put / get pourrait vraisemblablement se transformer en shutil.copy ou similaire

    • local ne serait vraisemblablement pas modifié (bien que lors de l'impression de ce qui se passe, vous voulez probablement toujours qu'il soit différencié de ce que run-except-locally est préfixé avec...?)

    • Abordé ci-dessus, les différentes méthodes/gestionnaires de contexte de manipulation de contexte comme prefix , cd etc. ont tous besoin de réponses à des questions similaires.

  • Cela mis à part, exécuter localement des commandes sudo est une arme potentiellement énorme et nécessite probablement des contrôles de sécurité supplémentaires.

    • À moins qu'elle ne devienne aussi une autre liaison à local , ce qui est une autre possibilité. Bien qu'elles ne soient pas importantes, toutes les commandes sudo qui fonctionnent même localement (c'est-à-dire qu'elles sont déployées vers et à partir de Linux) devraient vraisemblablement rester privilégiées localement (par exemple, apt / yum et amis, bricolage du pare-feu, etc.).

  • sudo (comme indiqué ci-dessus par Jon) doit également augmenter la possibilité de configurer des vecteurs de configuration locaux vs distants distincts, car l'utilisateur sudo, le mot de passe, etc. sont susceptibles de différer entre les deux côtés.

    • Bien que, puisque je pense à tout cela dans le contexte de Fab 2, les remplacements de configuration par hôte attendus résoudraient probablement au moins cette partie des choses - le contexte localhost recevrait simplement les valeurs appropriées. (De plus, en tant que sous-classe Context dédiée "pour exécuter des choses distantes localement", elle pourrait également faire d'autres choses, si nécessaire).

@max-arnold essayait cela dans l'alpha v2 et s'est heurté à des problèmes déroutants, ce à quoi il faut s'attendre à ce stade puisque - je n'étais pas encore arrivé au cas d'utilisation de ce ticket particulier, à part assurer run et local ont des API aussi similaires que possible.

Pour le moment, le gros problème est simplement la nature et l'API de l'objet lié au contexte d'une tâche ( c ou ctx ou quel que soit le nom qu'on lui donne) posarg. À l'heure actuelle, et encore une fois, ce n'est pas destiné à être définitif, c'est juste comment cela s'est terminé jusqu'à présent :

  • par défaut, lorsqu'il est exécuté par Executor d'Invoke ou par FabExecutor de Fab 2 lorsqu'aucun hôte n'est présent, c'est invoke.Context , qui a un run qui s'exécute localement , et il manque un local ;
  • lorsque Fab 2 a un ou plusieurs hôtes sur lesquels s'exécuter, il crée un fabric.Connection , dont le run s'exécute à distance et dont le local est une reliure du run d'Invoke

Une réflexion plus spécifique est nécessaire, notamment en examinant les cas d'utilisation ici et dans les tickets liés. Remue-méninges désinvolte :

  • Une solution utile (ou au moins_ une documentation) pour cela devrait presque certainement exister dans le noyau (selon une discussion précédente à ce sujet vivant en dehors du noyau) car :

    • c'est un cas d'utilisation assez courant

    • c'est facile de tout gâcher

    • il est nécessaire d'implémenter utilement des versions compatibles v2 de patchwork (née contrib ) et/ou invocations (la version d'Invoke de la même), d'autant plus qu'elle indique combien de partage de code ils peuvent faire. De nombreuses tâches et/ou sous-routines dans ces types de bases de code peuvent vouloir s'exécuter localement ou à distance.

  • Au fond, il s'agit de l'API à attendre des objets Context où la tâche peut ne pas savoir avec certitude "comment" elle est invoquée
  • Cela pourrait dépendre de la façon dont la tâche est générée, c'est-à-dire différentes versions de @task et/ou de kwargs, où l'utilisateur peut déclarer ses attentes (c'est-à-dire "Je veux vraiment qu'on me donne un contexte distant", "s'il vous plaît, ne me donnez jamais un contexte à distance", etc.)

    • Nous pouvons vouloir _exiger_ cela pour éviter toute ambiguïté ( ZoP #12 )

    • Plus j'y pense, plus il semble clair que nous voulons que Fabric développe son propre emballage léger autour de @task / Task ; les bases de code pure-Invoke utiliseraient simplement son @task qui déclencherait toujours l'attribution d'un Context vanille, tandis que les tâches créées via la version de Fabric auraient au moins la possibilité de se voir attribuer un Connection , sinon en demander un.

    • Un inconvénient est le type de tâche « Je peux être utile localement XOR à distance » mentionné ci-dessus ; une tâche qui ne veut qu'une seule option "exécuter les commandes s'il vous plaît" et ne mélange pas les deux modes simultanément. Cela _ne_ fonctionne pas bien avec les solutions "le décorateur déclare le type de contexte" car il _doit_ "basculer" le type de contexte selon qui l'appelle et comment.

    • Bien que ce soit en fait un point entier de l'API actuelle ; ces tâches _ne se soucient pas_ de la sous-classe de contexte, _tant que ctx.run() existe_.

    • Donc, ceux-ci veulent probablement être décorés avec la version "Je n'ai besoin que d'un contexte de base, vanille" de @task , étant entendu que quelqu'un du point de vue de l'invocation Fabric (ou Fabric-like) a la possibilité de leur donner tâches un Connection au lieu d'un Context .



      • Ce qui nous ramène à nous demander exactement comment exécuter les tâches, alias pyinvoke/invoke#170



  • Indépendamment de la mise en œuvre, nous devons nous assurer de minimiser le potentiel des armes à pied concernant les utilisateurs faisant des choses comme :

    • Attendre local quand il n'existe pas (Fabric/Connection-attendant le code exécuté via Invoke)

    • S'attendre à ce que run s'exécute localement alors qu'on a plutôt reçu un contexte avec un run distant (Invoke/Context-attending code exécuté via Fabric)

    • Rien d'autre du commentaire supplémentaire de Max ici

  • Comme on le voit dans des commentaires beaucoup plus anciens, un sous-cas d'utilisation ici est que les utilisateurs s'attendent à ce que l'équivalent v2 de Connection('localhost').run('foo') _n'utilise pas SSH_ mais agisse exactement comme Connection('localhost').local('foo') .

    • Je suppose que nous ne voulons pas réellement faire cela car cela ressemble à une arme de pied méchante pour quiconque tente de faire des vérifications de l'intégrité de l'hôte local. Cela me semble trop magique pour moi. Mais je suis ouvert aux arguments, probablement sur la base d'un opt-in (par exemple, définissez une option de configuration comme ssh.localhost_becomes_subprocess = True ou autre.)

Mon seul cas d'utilisation ici pour le moment serait que upload_template() soit capable de rendre un modèle localement.

Bien sûr, on pourrait le faire comme ceci :

#http://matthiaseisen.com/pp/patterns/p0198/
import os
import jinja2


def render(tpl_path, context):
    path, filename = os.path.split(tpl_path)
    return jinja2.Environment(
        loader=jinja2.FileSystemLoader(path or './')
    ).get_template(filename).render(context)

Mais pourquoi ne pas avoir une option pour rendre localement ?

L'utilisation principale de cette fonctionnalité, dans mon cas, serait de déployer la configuration de l'application sur ma machine locale pour les tests locaux.

Considérez que vous avez un settings.py.j2 qui est rendu au serveur de destination lors du déploiement et qu'il s'appelle settings.py et ne contient que du code python, pas de jinja.
Maintenant, vous voulez tester localement, mais localement il n'y a pas encore de settings.py , car il doit être rendu à partir de settings.py.j2 .
Votre application ne peut donc pas démarrer et vous devrez créer manuellement un settings.py séparé pour vos tests locaux.

C'est très fatiguant, et ça devrait être plus facile.

Par exemple, dans Ansible, je dirais simplement à la tâche qu'elle va utiliser une "connexion locale", et elle sera rendue sur l'hôte local sans essayer de s'y connecter.

Jusqu'à ce que cette fonctionnalité soit disponible dans Fabric, j'utiliserai bien sûr la solution collée ci-dessus, car il ne s'agit que de quelques lignes de code. Cela devrait être plus facile à mon humble avis. J'ai l'impression que c'est vraiment le genre de tissu qui devrait me faciliter la tâche.

@fninja Je n'ai pas encore porté upload_template lui-même mais je suis tout à fait d'accord pour dire que cela relève de cet espace problématique. On pourrait sans doute gérer cela en divisant simplement l'étape de rendu Jinja-wrapping et l'étape de téléchargement upload-some-string, d'autant plus que cette dernière existe déjà sous la forme de "donner un FLO à put ". Par exemple:

from StringIO import StringIO # too lazy to remember the newer path offhand
from somewhere.jinja_wrapper import render
from invoke import task

<strong i="9">@task</strong>
def render_settings(c):
    rendered = render('settings.py.j2', {'template': 'params'})
    c.put(StringIO(rendered), 'remote/path/to/settings.py')

Mais il y a probablement encore de la place pour un analogue 1-stop encore plus court à upload_template qui serait soit une méthode Connection soit un sous-programme prenant un argument Connection .

Quoi qu'il en soit, cela soulève plus de questions sur : comment traiter exactement ce genre de chose - par exemple, les objets Context appel uniquement n'ont pas de put / get . Cela vaut-il la peine de les ajouter ? Cela a beaucoup de sens pour les utilisateurs de Fabric dans le contexte de ce ticket (alors upload_template ou w/e peut simplement appeler put dans les deux cas), mais pour les utilisateurs pur-Invoke, c'est un et une partie inutile de l'API.

+1 pour en faire une fonctionnalité principale

Crosspost de #1637. Juste une idée:

from fabric import task, local

<strong i="6">@task</strong>
<strong i="7">@local</strong>
def build(ctx):
    with ctx.cd('/project/dir'):
        ctx.run('build > artifact.zip')

<strong i="8">@task</strong>
def deploy(conn):
    build(local(conn))

    with conn.cd('/remote/path'), local(conn).cd('/project/dir'):
        conn.put(remote_path='build.zip', local_path='artifact.zip')

Fondamentalement, local() peut agir comme décorateur/gestionnaire de contexte/fonction et transformer Connection en Context .

Un autre cas d'utilisation que je ne pense pas avoir vu mentionné : Construire une bibliothèque de fonctions réutilisables. Dans mon cas, il s'agit principalement de commandes git . J'ai écrit un dorun trop simpliste qui masque les différences entre les paramètres de fonction run et local (sur v1) ; quelle fonction est choisie est passé en paramètre. Voici un git checkout par exemple :

def git_checkout(branch, remote='origin', run=run):
    """Checkout a branch if necessary."""

    if branch == git_current_branch(run=run):
        return
    elif branch in git_local_branches(run=run):
        dorun('git checkout ' + branch, run=run)
    else:
        dorun('git checkout -t -b {0} {1}/{0}'.format(branch, remote), run=run)


def git_current_branch(run=run):
    """Get the current branch (aka HEAD)"""

    output = dorun('git name-rev --name-only HEAD', run=run)
    return output.strip()


def git_local_branches(run=run):
    """Get a list of local branches; assumes in repo directory."""

    output = dorun('git branch --no-color', run=run)
    branches = {l.strip().split(' ')[-1]
                for l in output.strip().split('\n')}
    return branches

Cela ressemble à ceci :

from fabric.api import run as run_remote, local as run_local

def dorun(*args, **kwargs):
    """Work around the fact that "local" and "run" are very different."""
    kwargs.setdefault('run', run_remote)
    run = kwargs.pop('run')

    if run == run_local:
        kwargs.setdefault('capture', True)
    elif 'capture' in kwargs:
        del kwargs['capture']

    return run(*args, **kwargs)

Je n'ai aucune idée de ce qui se passe avec sudo et il y a des problèmes que je ne peux pas facilement résoudre, comme étendre ~remoteuser pour produire un chemin.

Cette page vous a été utile?
0 / 5 - 0 notes