Fabric: Opcionalmente, evite usar ssh se for para localhost

Criado em 19 ago. 2011  ·  59Comentários  ·  Fonte: fabric/fabric

Descrição

run () / sudo () veria de forma inteligente que você está indo para o localhost e apenas executaria local (). Isso provavelmente seria opcional.

Comentários de Jeff no IRC:

e sim, quero dizer que sempre haverá sobrecarga com ssh vs tubos diretos de improviso. Não acho que seria terrivelmente difícil atualizar run / sudo (especialmente no master, agora que eles foram refatorados) para chamar / retornar local ( ) de forma inteligente, não tenho certeza de que gostaria desse comportamento semágico no núcleo (mesmo com ele desativado por padrão com um optin para ativá-lo, embora isso ajudasse), mas mesmo assim, seria um experimento interessante. e se for tão simples quanto estou pensando, honestamente, não consigo encontrar um bom motivo para não o fazer (mais uma vez, desde que não seja o comportamento padrão)

Originalmente apresentado por Nick Welch ( mackstann ) em 2009-11-11 às 01:39 pm EST

Relações

  • Duplicado por # 364: Permitir que a operação local ignore a camada SSH
  • Relacionado ao nº 26: Implementar o recurso de "simulação"
Feature Network

Comentários muito úteis

Se alguém está se perguntando "por que alguém faria isso?", A resposta é que se você tiver um pipeline de implantação, pode ser útil executar o mesmo script de implantação exato, não importa o ambiente, em vez de ter um script de configuração especial para localhost contra tudo o mais.

Todos 59 comentários

James Pearson (xiong.chiamiov) postou:


Como também mencionei no irc, eu normalmente não executo o servidor ssh em uma máquina desktop, então não posso realmente executar ssh para localhost.


em 11/11/2009 às 15:13 EST

Travis Swicegood ( tswicegood ) postou:


Acabei de implementar algo semelhante esta noite na forma de uma nova função fabric.operations chamada do . Ele olha para env.run_as para ver se é igual a "local" e, ao fazer isso, muda para o método local vez de run (ou sudo se sudo=True for passado como um kwarg). Ele também lida com a prefixação de comandos locais com sudo no caso de estarem executando localmente.

Esta é uma maneira diferente de contornar este problema, que funciona sem alterar o comportamento de run ou sudo . Essas mudanças estão disponíveis em meu repositório .


em 11/01/2010 às 12h22 EST

Morgan Goose ( goosemo ) postou:


Eu realmente não vejo isso sendo plausível. Qual é o objetivo de executar como local. Um dos requisitos do Fabric é o sshd rodando na máquina, remoto ou loopback. O outro problema é que apenas alterar o local não leva em consideração put, get, rsync_project e outros que ainda precisam do ssh. Tentar implementá-los realmente causaria mais problemas, já que agora está no reino de fazer fabfiles traduzir para o bash.


em 13/03/2011 às 23:14 EDT

Jeff Forcier ( bitprophet ) postou:


Embora eu também não esteja 100% convencido de que essa é uma ótima ideia, é claramente algo que vários usuários sentem necessidade - outra solicitação foi apresentada como # 364 com outra explicação do caso de uso.

Também adicionei o tíquete de simulação conforme relacionado a este, porque (presumo - se algum dos usuários solicitantes puder verificar isso, seria ótimo) o principal caso de uso para esse recurso é para teste / teste correndo.


em 23/06/2011 às 11h26 EDT

Conforme observado em # 538, se algum dia formos capazes de normalizar totalmente os três corredores para que possam ser usados ​​de forma intercambiável, precisaremos nos certificar de que o escape do shell funcione de forma consistente entre eles. No momento, não temos o escape de shell local , embora isso seja pelo menos em parte porque ele não está usando um invólucro de shell.

Se alguém está se perguntando "por que alguém faria isso?", A resposta é que se você tiver um pipeline de implantação, pode ser útil executar o mesmo script de implantação exato, não importa o ambiente, em vez de ter um script de configuração especial para localhost contra tudo o mais.

+1 para o recurso

+1

+10

+1

+1

Para atrasar você, certifique-se de ter o servidor OpenSSH em execução. Primeiro faça sudo apt-get install ssh para ter certeza de que o instalou (mesmo se você achar que tem). Em seguida, faça sudo service ssh start | stop | restart conforme necessário. Aprendi com este tópico .

+1

Meu caso de uso é simples: eu quero usar o mesmo script django-deploy para configurar instâncias ec2 com cloud-init por meio do CloudWatch (o caso para executar comandos locais) e usar o fab deploy_django -H foo@bar regular.

+1

Isso seria muito útil. Um caso de uso que eu tenho é usar o vagrant shell provisioner para configurar um VM particular usando fabric e sem a necessidade de ssh localhost.

+1

Fiquei surpreso ao não ver isso ainda no Fabric.

Para sua informação: A implementação deste recurso fica mais complexa quando você pensa em funções de malha como reboot() .

+1

Já deve fazer parte do núcleo!

+1

Isso faria sentido: de um ponto de vista abstrato, local é apenas um caso especial de run , onde nenhuma máquina SSH está envolvida.

Mais uma coisa a apontar (talvez óbvio): o tecido deve ser inteligente o suficiente para decidir se run deve ser convertido em local APÓS a leitura de / etc / hosts.

Quero dizer: se tivermos

env.host = [ 'mywebserver' ]

e em / etc / hosts temos:

127.0.0.1 mywebserver

então, quaisquer run chamadas devem, na verdade, ser local chamadas.

Levando este conceito um passo adiante, devemos também tratar run como uma chamada local quando o host remoto resolve para um IP que é atribuído a uma interface de rede da máquina local.
Por exemplo:
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

O Fabric 2 usará pyinvoke / invoke, então isso deve ser bem fácil de fazer lá. Eu esperaria pelo Fabric 2 por uma maneira não hacky de fazer isso.

: +1:

+1

: +1:

: +1: implemente isso, especialmente porque os computadores mac não são configurados automaticamente para ter túneis SSH configurados para acesso remoto ao servidor localhost.

+1

+1 :)

+1 por favor

: +1:

: +1:

: +1:

Estamos usando o Fab para construir pacotes debian e isso adiciona complexidade extra

pessoal, olá a todos
tento criar clone de tecido com diferença:

  • A função run () funciona da mesma maneira com subprocess.popen em localhost como em ssh conectar ao host remoto
  • A fábrica usa o openssh ou qualquer outro cliente ssh (você deve modificar a configuração para isso), então você pode usar todo o poder dos soquetes ssh
  • A fábrica usa a biblioteca gevent para execução assíncrona

Você pode dar uma olhada se precisar deste recurso
https://github.com/Friz-zy/factory

Posso estar faltando alguma coisa nesta discussão, mas aqui está o que fiz para usar o mesmo código com o comando fab run em ambos os locaishost e máquinas remotas.

  1. Eu defino env.use_ssh_config = True no meu fabfile.py
  2. ssh-copy-id localhost

Isso não resolve o seu problema se você não estiver executando o servidor ssh em sua máquina local

: +1:

+1

+1 Implemente este recurso :)

+1

Pode ser muito útil para inicializar imagens do Docker usando scripts existentes do Fabric. Este recurso evitaria a instalação de um servidor SSH no contêiner, o que vai contra as práticas recomendadas do

+1

+1

+1

Além da resposta fornecida por @AntoniosHadji , aqui estão as instruções completas para fazer este trabalho;

# 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

Na verdade, isso pode ser feito com a culinária . Você precisa alterar todas as run execuções para fazer referência à função cuisine.run , o que pode ser feito facilmente com uma importação, e alterar o modo para local:

from cuisine import run, mode_local

mode_local()
print run("echo Hello")

Otimo @cgarciaarano

Para casos de uso simples, isso funciona para mim:

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

: +1:

Eu quero que meu fabfile seja executado remotamente ou localmente quando o ssh não for uma opção. Isso inclui wrappers locais para obter / colocar / existir etc.

: +1: Eu tenho fabfiles que são executados tanto localmente quanto remotamente e acabei hackeando minhas próprias funções de wrapper para executar / local / conseguir lidar com todas as diferenças sutis, como captura de saída e tratamento de erros.

E se você tiver uma conexão ssh fazendo encaminhamento de porta dinâmico e vinculando em 127.0.0.2 (ainda tecnicamente localhost) na porta 2223. Posso ver como isso pode causar problemas, para esse fim combinando em localhost e resolvendo para 127.0.0.1 em vez de também suportar toda a classe 127.0.0.0/8 pode ser uma boa ideia de lidar.

@ blade2005 Sim, todo o intervalo 127 ._._. * aponta para o seu host local (exceto 127.0.0.0 e 127.255.255.255), mas quando você está realmente apontando para o seu host local, você não usa a porta certa?
Portanto, acredito que podemos assumir com segurança que 127.*.*.* == localhost e ssh podem ser evitados, mas 127.*.*.*:* aponta para uma porta encaminhada e ssh é necessário.

Honestamente, esse recurso provavelmente faria mais sentido como um plug-in de terceiros criado em tecido, semelhante à biblioteca de culinária. Em seguida, apenas importaríamos funções agrupadas para run / get / put / etc, que saberiam se executariam local ou remotamente com base em uma variável env. Pelo menos assim, alguém poderia começar isso para que todos usassem.

Implementei algo localmente e é muito mais trabalhoso do que apenas alternar entre local / execução. Você deve considerar prefixos, diretórios alterados, usuários sudo, etc.

Estava pensando brevemente sobre isso no contexto de outro tíquete relacionado ao 2.0, e percebi que há mais coisas surgindo além de apenas " run torna-se uma nova ligação de local ":

  • Qualquer tipo de tarefa de modo verdadeiramente misto usando local e run , ou put / get , torna-se inerentemente problemática: operações com definição clara 'local' e 'remoto' "termina" agora ambos apontam localmente.

    • Eu suporia que seja um caso de uso minoritário (se for um), mas ainda precisa ser descoberto, mesmo que seja "chamar qualquer operação, exceto run ou sudo aumenta DoesntMakeAnySenseError "ou qualquer outra coisa.

    • put / get presumivelmente poderia se transformar em shutil.copy ou similar

    • local presumivelmente não seria alterado (embora ao imprimir o que está acontecendo, provavelmente ainda queira que seja diferenciado do que run-except-locally tem como prefixo ...?)

    • Tocado acima, os vários métodos / gerenciadores de contexto de manipulação de contexto, como prefix , cd etc, todos precisam de perguntas semelhantes respondidas.

  • Deixando isso de lado, executar localmente sudo comandos, é uma metralhadora potencialmente enorme e provavelmente deseja verificações de segurança adicionais.

    • A menos que também se torne apenas mais uma ligação para local , que é outra possibilidade. Embora não seja grande, qualquer comando sudo que funcione localmente (ou seja, um está implantando e implantando a partir do Linux) presumivelmente precisaria permanecer privilegiado localmente (por exemplo, apt / yum e amigos, ajustes de firewall, etc).

  • sudo também (como observado acima por Jon) precisa aumentar a possibilidade de configurar vetores de configuração locais vs remotos distintos, uma vez que o usuário sudo, a senha etc. provavelmente diferem entre os dois lados.

    • Embora, como estou pensando em tudo isso no contexto do Fab 2, as substituições de configuração por host esperadas provavelmente resolveriam essa parte das coisas pelo menos - o contexto localhost simplesmente receberia os valores apropriados. (Além disso, como uma subclasse de Context dedicada "para executar coisas remotas localmente", ele poderia fazer outras coisas também, se necessário).

@ max-arnold estava tentando fazer isso na versão alfa v2 e encontrou problemas confusos, o que era de se esperar desde então - eu ainda não tinha chegado ao caso de uso deste tíquete em particular, além de garantir run e local têm APIs tão semelhantes quanto possível.

No momento, o grande problema é simplesmente a natureza e a API do objeto vinculado ao contexto de uma tarefa ( c ou ctx ou qualquer outro nome) posarg. No momento, e novamente, isso não pretende ser definitivo, é apenas como acabou até agora:

  • por padrão, quando executado por Executor do Invoke ou por FabExecutor Fab 2 quando nenhum host está presente, é invoke.Context , que tem um run executado localmente , e não tem um local ;
  • quando Fab 2 tem host (s) para executar, ele cria um fabric.Connection , cujo run é executado remotamente e cujo local é uma religação de run de Invoke

É necessária uma reflexão mais específica, incluindo a análise de casos de uso aqui e em tickets vinculados. Brainstorm improvisado:

  • Uma solução útil (ou pelo menos __ documentação) para isso deve quase definitivamente existir no núcleo (por bate-papo anterior sobre isso vivendo fora do núcleo) porque:

    • é um caso de uso comum o suficiente

    • é fácil bagunçar

    • é necessário implementar versões v2 compatíveis de patchwork (née contrib ) e / ou invocations (versão de Invoke do mesmo), especialmente porque informa quanto compartilhamento de código eles podem Faz. Muitas tarefas e / ou sub-rotinas nesses tipos de bases de código podem ser executadas local ou remotamente.

  • No fundo, trata-se de qual API esperar de objetos Context onde a tarefa pode não saber com certeza "como" está sendo chamada
  • Pode depender de como a tarefa é gerada, ou seja, diferentes versões de @task e / ou kwargs para o mesmo, onde o usuário pode declarar suas expectativas (ou seja, "Eu realmente quero ter um contexto com capacidade remota", "por favor, nunca me forneça um contexto com capacidade remota", etc)

    • Podemos querer _require_ isso para evitar ambigüidade ( ZoP # 12 )

    • Quanto mais penso nisso, mais parece claro que queremos que a Fabric crie sua própria embalagem leve em torno de @task / Task ; As bases de código do pure-Invoke apenas usariam seu @task que sempre acionaria o recebimento de um vanilla Context , enquanto as tarefas criadas por meio da versão do Fabric teriam pelo menos a opção de receber um Connection , se não exigir um.

    • Uma desvantagem é o tipo de tarefa "Eu posso ser útil localmente XOR remotamente" mencionada acima; uma tarefa que deseja apenas uma única opção "executar comandos por favor" e não mistura os dois modos simultaneamente. Isso _não_ funciona bem com soluções do tipo "o decorador declara o tipo de contexto" porque _precisa_ 'alternar' o tipo de contexto dependendo de quem o está chamando e como.

    • Embora esse seja, na verdade, um ponto inteiro da API atual; essas tarefas _não se importam_ com a subclasse de contexto, _ desde que ctx.run() exista_.

    • Então, esses presumivelmente querem ser decorados com a versão "Eu só preciso de um contexto básico e vanilla" de @task , com o entendimento de que alguém do ponto de vista de invocação de Tecido (ou semelhante a Tecido) tem a opção de dar a eles executa a tarefa Connection vez de Context .



      • O que nos leva de volta à pergunta exatamente como executar tarefas, também conhecido como pyinvoke / invoke # 170



  • Independentemente da implementação, temos que garantir que minimizamos o potencial de footgun re: usuários fazendo coisas como:

    • Esperando local quando não existe (código do Fabric / Connection-expecting executado via Invoke)

    • Esperando que run seja executado localmente quando, em vez disso, for dado um contexto com um run remoto (código de chamada / expectativa de contexto executado via Fabric)

    • Qualquer outra coisa do comentário adicional de Max aqui

  • Como visto em comentários muito mais antigos, um subcaso de uso aqui são os usuários que esperam que o equivalente da v2 de Connection('localhost').run('foo') _não use SSH_, mas em vez disso atue exatamente como Connection('localhost').local('foo') .

    • _Estou supondo_ que não queremos fazer isso, pois parece uma espingarda nojenta para qualquer pessoa que tente fazer verificações de integridade do host local. Parece muito mágico para mim de improviso. Mas estou aberto a argumentos, provavelmente por opção (por exemplo, definir uma opção de configuração como ssh.localhost_becomes_subprocess = True ou qualquer outra.)

Meu único caso de uso aqui no momento seria upload_template() ser capaz de renderizar um modelo localmente.

Claro que se poderia fazer assim:

#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)

Mas por que não ter a opção de renderizar localmente?

O principal uso desse recurso, no meu caso, seria implantar a configuração do aplicativo em minha máquina local para teste local.

Considere que você tem um settings.py.j2 que é renderizado para o servidor de destino na implantação e lá é denominado settings.py e contém apenas código python, sem jinja.
Agora você deseja testar localmente, mas localmente não há settings.py ainda, porque ele precisa ser renderizado a partir de settings.py.j2 .
Portanto, seu aplicativo não pode ser iniciado e você terá que criar um settings.py separado manualmente para o seu teste local.

Isso é muito cansativo e deveria ser mais fácil.

Por exemplo, em Ansible, eu simplesmente diria à tarefa que ela usará "conexão local" e renderizaria no host local sem tentar fazer o ssh nele.

Até que esse recurso esteja disponível no Fabric, usarei a solução colada acima, é claro, pois são apenas algumas linhas de código. Deve ser mais fácil embora imho. Eu sinto que esse é realmente o tipo de material que o tecido deveria tornar mais fácil para mim.

@fninja Eu não upload_template ainda, mas eu definitivamente concordo que se enquadra neste espaço de problema. Indiscutivelmente, pode-se lidar com isso apenas dividindo a etapa de renderização do Jinja-wrapping e a etapa de upload de alguma string, especialmente porque a última já existe na forma de "entregar um FLO para put ". Por exemplo:

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')

Mas provavelmente ainda há espaço para um análogo de 1 parada ainda mais curto para upload_template que seria um método Connection ou uma sub-rotina tomando um argumento Connection .

De qualquer forma, isso levanta mais questões sobre: ​​exatamente como tratar esse tipo de coisa - por exemplo, objetos Context invocação não têm put / get . Vale a pena adicioná-los? Faz muito sentido para usuários do Fabric no contexto deste tíquete (então upload_template ou w / e pode simplesmente chamar put em ambos os casos), mas para usuários do Invoke puro, é um bizarro e parte inútil da API.

+1 para tornar este um recurso central

Poste cruzado de # 1637. Apenas uma ideia:

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')

Basicamente local() pode atuar como decorador / gerenciador de contexto / função e transformar Connection em Context .

Outro caso de uso que acho que não vi mencionado: Construindo uma biblioteca de funções reutilizáveis. No meu caso, são principalmente git comandos. Eu escrevi um dorun excessivamente simplista que esconde as diferenças entre os parâmetros de função run e local (na v1); qual função é escolhida é passada como um parâmetro. Aqui está git checkout por exemplo:

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

Se parece com isso:

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)

Não tenho ideia do que acontece com sudo e existem problemas com os quais não posso lidar facilmente, como expandir ~remoteuser para produzir um caminho.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

26huitailang picture 26huitailang  ·  3Comentários

peteruhnak picture peteruhnak  ·  6Comentários

peteruhnak picture peteruhnak  ·  4Comentários

SamuelMarks picture SamuelMarks  ·  3Comentários

shadyabhi picture shadyabhi  ·  5Comentários