Fabric: Permitir o armazenamento / uso de metadados sobre hosts

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

Atualização de 2018: este é um tíquete muito antigo e está pronto para ser implementado agora que 2.0 está disponível para desenvolver recursos.

O tl; dr é que os usuários precisam de maneiras de armazenar dados valiosos sobre seus hosts de destino (e, geralmente, grupos ou "funções"), primeiro, e depois precisam de uma maneira de endereçar essas informações ao fazer coisas em nível de API ou CLI, em segundo lugar.

O Fabric 2 é construído em Invoke, que possui um sistema de configuração poderoso, que é então exposto a tarefas por meio de um objeto de 'contexto'. Parece provável que iremos construir este recurso sobre eles, algo como (mas não necessariamente limitado a):

  • Padronize em algum formato de estilo de configuração relativamente genérico para representar hosts; basicamente os parâmetros de conexão (usuário, nome do host, porta, connect_kwargs, tempo limite, etc etc) e provavelmente com um pouco mais no topo

    • ou pelo menos a oportunidade para os usuários colocarem dados arbitrários no topo e fazer com que apareçam nos objetos expostos ao usuário em tempo de execução

  • Atualize o Contexto para que ele tenha um link leve para alguma outra classe ou classes que transformam a configuração 'bruta' em objetos de API utilizáveis, com base em alguma consulta ou pesquisa

    • A questão em aberto é se eles aparecem como conexões reais ou se há uma classe representacional intermediária como Host

Por exemplo (novamente: apenas um _exemplo_do manguito_!) Talvez possamos configurá-lo de forma que os dados do host obtenham seu próprio arquivo de estilo de configuração que fica ao lado dos arquivos de configuração regulares - digamos, $PROJECT/hosts.(yml|json|py|etc) :

web1:
  host: web1
  # Implicit local user, as with Connection
web2:
  host: web2
  user: admin2
  port: 2223
db:
  # Implicit dict-key-is-the-host-value, i.e. implicit "host: db"
  user: dbadmin

Então, talvez haja algo assim (usando tarefas de estilo Invoke puras por enquanto, embora certamente isso desejasse a capacidade de usar -H ou decoradores para selecionar hosts de destino para 'embrulhar' a tarefa, como em v1):

<strong i="27">@task</strong>
def deploy_webs(c):
  # Assuming auto-creation of Connection objects...
  for cxn in [c.find_host('web1'), c.find_host('web2')]:
    cxn.run("hostname")

Há muitas maneiras diferentes de dividir e dividir isso, e várias direções nas quais ele pode ser estendido; a ênfase deve ser dar aos usuários tanto poder e controle quanto razoavelmente possível e, então, sair do caminho deles. Idealmente, tudo o que faremos é padronizar em alguma maneira muito básica de inserir dados em objetos Connection e adicionar suporte à estrutura executiva CLI principal para chegar a algo semelhante ao Fabric 1 em relação à seleção de alvos de execução.

Ao mesmo tempo, expor esses mecanismos publicamente para que usuários avançados possam resolver o problema por conta própria - mais uma vez, espero que qualquer pessoa além dos casos de uso mais básicos tenha grande probabilidade de recorrer a "tarefas regulares do estilo Invoke + fazer as chamadas API necessárias dentro dessas tarefas corpos "abordagem.


Descrição original

No momento, um "host" está limitado exclusivamente ao usuário / nome do host / porta. Seria bom, mesmo apenas para arquivos de fábrica do usuário, armazenar informações adicionais, como sistema operacional, configurações por host como env.shell e assim por diante.

Observe que este pode (ou não) ser um bom momento para reconsiderar a alteração do valor padrão de shell para /bin/sh .


Enviado originalmente por Jeff Forcier ( bitprophet ) em 20/07/2009 às 17h02 EDT

Relações

  • Duplicado por # 43: / bin / bash nem sempre está disponível - por configuração de shell de host?
  • Relacionado ao nº 97: Em algumas situações, pressionar Enter _não_ reutiliza a senha anterior
  • Relacionado a # 138: env.port não honrado se a string do host não tiver especificação de porta
  • Relacionado ao nº 3: Faça uso de ssh_config sempre que possível
  • Relacionado com # 76: Use decorador para definir tarefas
Core Feature

Comentários muito úteis

Olá!

Depois de muita leitura, ainda não entendo como isso realmente funciona. Por exemplo, se eu tiver um arquivo de configuração hosts.yml como este:

hosts.yml:

server1:
  host: serverip
  user: username

Como devo usar isso para criar uma conexão? Tive que renomear o hosts.yml para fabric.yml para obter acesso a esses dados, por meio da variável de contexto, por exemplo:

<strong i="12">@task</strong>
def do(ctx):
    ctx['server1']

E vai me devolver um DataProxy, que não posso usar para criar conexão, ou simplesmente não encontrei na documentação

Meu outro problema: como é possível especificar esses hosts declarados no arquivo hosts.yml com a alternância -H? Só funciona se eu criar um alias no arquivo _ ~ / .ssh / config_ que não é nada bom.

Fora do tópico: o prompt foi removido da api? Não encontrei um método correspondente.

Eu tenho a mesma pergunta. Não encontrei um único exemplo ou tutorial sobre como criar e usar arquivos de configuração. A documentação da API é muito deficiente a esse respeito.

Todos 13 comentários

Silas Sewell ( silas ) postou:


Com relação ao shell padrão, mesmo se você não mudar para /bin/sh , pode ser bom usar /usr/bin/env bash para consertar situações em que o usuário instalou o bash.

Patch de exemplo: http://github.com/silas/fabric/commit/6f7d33c1a3180fbba21c89447bb9a32b84e839ba

PS Eu postei aqui porque "Suporte # 43" foi marcado como uma duplicata.


em 09-11-2009 às 23:13 EST

Erich Heine ( sophacles ) postou:


Eu descobri uma maneira de fazer esses metadados de host que funciona muito bem para os casos simples. Os bits de código relevantes estão aqui http://paste.pocoo.org/show/173964/ no entanto, existem algumas ideias que ainda não tive tempo de implementar - nomeadamente, que este tipo de interface para hospedar metadados pode ser melhor definido como um 'protocolo'. Com isso, quero dizer, ter uma interface definida em uma extremidade, de modo que vários back-ends possam ser implementados, por exemplo, um cliente ldap e um cliente redis, e assim por diante. Isso permitiria que as pessoas se integrassem melhor com as ferramentas existentes (por exemplo, buildbot).


em 04/02/2010 às 15:33 EST

Isso está fortemente relacionado a algumas coisas que eu estava experimentando no # 563, onde ter um objeto Host real ajudaria muito com host_string BS, e também pavimentaria o caminho para esse tipo de sobreposição de configurações por host.

Isso foi reduzido para 2.x, então este também é.

Atualmente, armazenamos metadados adicionais sobre o host remoto em env.server , que extraímos de um inventário de configurações de servidor salvas em disco como JSON. Mas isso só funciona quando estamos executando tarefas em um único servidor ou quando todas as tarefas que devem ser executadas com vários servidores contêm código para atualizar env.server base no valor de env.host_string .

Uma coisa que tornaria isso mais fácil para nós é se o tecido fornecesse um gancho para quando env.host_string fosse trocado. Então poderíamos ter apenas um pedaço de código que se conecta a isso e atualiza env.server com o contexto adicional sempre que env.host_string é alterado.

Isso poderia ser implementado facilmente, sem a necessidade de refatorar host_string em um objeto Host completo?

Copiando comentários relevantes de # 1748, @peteruhnak comentou:

"" "
Oi! É possível especificar hosts em um arquivo de configuração ou no próprio fabfile?

Eu sei que posso fornecê-los via CLI (-H), mas se o fabfile for projetado para se comunicar com um servidor específico, ele apenas força o usuário a fazer coisas extras sem um bom motivo.

A "melhor" solução que consegui descobrir foi criar a conexão manualmente, por exemplo

@tarefa
def my_ls (c):
conn = Conexão ('myhost')
conn.run ('ls')
mas isso parece muito sujo, pois preciso (1) duplicá-lo em todos os lugares e (2) tornar o argumento do contexto sem sentido.
"" "

E eu segui com:

"" "
Concordo com tudo acima. Eu realmente sinto falta da velha configuração env.hosts. Foi lindamente simples.

-1 sobre a necessidade de outro arquivo de configuração como invoke.yaml ou outros enfeites. Eu preferiria muito mais o monkey-patch ou algum tipo de gancho de importação que me permitisse declarar os hosts.

Também demorei um pouco para descobrir que ctx.local não está presente se nenhum host for especificado :(
"" "

Eu entendo que o Invoke fornece suporte de configuração avançada. Eu espero que isso inclua a configuração dos arquivos fab do Python.

Em fabric1, adicionei a capacidade de carregar listas de hosts com --set list = somelistfile.txt. No fabfile, eu analisaria o arquivo e modificaria env.hosts e env.passwords com as configurações desse arquivo. Isso me permitiu fazer, por exemplo, fab task --set list=datacenter1.txt . Em seguida, tive dezenas de tarefas de uso geral que poderia executar em listas predefinidas, em alguns casos uma lista por datacenter ou lista por função lógica. Sei que isso pode ser meio problemático, mas me serviu bem por muitos anos em minha carreira profissional.

Não encontrei uma maneira de fazer isso no fab2, pois os hosts são carregados dentro do executor. Eu gostaria muito de fazer algo semelhante no fab2. No entanto, parece que o paradigma em fab2 exige que eu especifique os hosts nas tarefas. Isso torna impossível ter minhas tarefas de propósito geral e usá-las em uma ampla variedade de servidores (sem usar fab2 -H).

Ter algum tipo de arquivo de configuração de host avançado e ser capaz de especificá-lo na linha de comando seria uma ótima adição ao fabric2.

como @grantjenks mencionou, há suporte de configuração avançada em invoke: http://docs.pyinvoke.org/en/1.1/concepts/configuration.html - portanto, precisaríamos apenas construir um arquivo yaml e pronto. isso seria / poderia ser estendido (manualmente, cada um por sua conta por enquanto), para suportar algo como o conceito env , como no tecido um. Se alguém não gosta de yaml, isso pode ser feito completamente em código python, pelo que entendi.

@benzkji Absolutamente verdadeiro; os usuários podem, agora mesmo, colocar dados arbitrários em seus arquivos de configuração (py, yml, json, eventualmente outros) e extrair esses dados de suas tarefas para construir conexões e grupos e outros enfeites. Isso é intencional; nunca queremos que haja apenas uma maneira de lidar com esse tipo de configuração, e é por isso que os objetos são todos públicos e tal.

Veja também o topo do tíquete; Eu editei a descrição com uma visão moderna de como esse recurso pode se parecer na v2, em termos de alguns auxiliares básicos comuns "para usuários não opinativos". Na verdade, estou mexendo nisso em um repositório privado (cliente do tecido, não bifurcação) hoje em dia para ver quais padrões emergem.

Olá!

Depois de muita leitura, ainda não entendo como isso realmente funciona. Por exemplo, se eu tiver um arquivo de configuração hosts.yml como este:

hosts.yml:

server1:
  host: serverip
  user: username

Como devo usar isso para criar uma conexão? Tive que renomear o hosts.yml para fabric.yml para obter acesso a esses dados, por meio da variável de contexto, por exemplo:

<strong i="11">@task</strong>
def do(ctx):
    ctx['server1']

E vai me devolver um DataProxy, que não posso usar para criar conexão, ou simplesmente não encontrei na documentação

Meu outro problema: como é possível especificar esses hosts declarados no arquivo hosts.yml com a alternância -H? Só funciona se eu criar um alias no arquivo _ ~ / .ssh / config_ que não é nada bom.

Fora do tópico: o prompt foi removido da api? Não encontrei um método correspondente.

Olá!

Depois de muita leitura, ainda não entendo como isso realmente funciona. Por exemplo, se eu tiver um arquivo de configuração hosts.yml como este:

hosts.yml:

server1:
  host: serverip
  user: username

Como devo usar isso para criar uma conexão? Tive que renomear o hosts.yml para fabric.yml para obter acesso a esses dados, por meio da variável de contexto, por exemplo:

<strong i="12">@task</strong>
def do(ctx):
    ctx['server1']

E vai me devolver um DataProxy, que não posso usar para criar conexão, ou simplesmente não encontrei na documentação

Meu outro problema: como é possível especificar esses hosts declarados no arquivo hosts.yml com a alternância -H? Só funciona se eu criar um alias no arquivo _ ~ / .ssh / config_ que não é nada bom.

Fora do tópico: o prompt foi removido da api? Não encontrei um método correspondente.

Eu tenho a mesma pergunta. Não encontrei um único exemplo ou tutorial sobre como criar e usar arquivos de configuração. A documentação da API é muito deficiente a esse respeito.

O fabric 2 é ótimo, mas da minha perspectiva de usuário final, é muito mais difícil colocá-lo em funcionamento (em comparação com o fabric 1), por exemplo, como uma ferramenta de implantação simples para webapps Django (no meu caso).

Há alguma notícia sobre este assunto? Eu acho que um pacote pypi "django / qualquer implantação" opinado faria sentido, para mexer com as coisas básicas que seriam necessárias, de modo que a integração possa ser muito mais fácil. Existe algo assim conhecido / em andamento?

Em qualquer caso, vou precisar mexer no tecido 2 e compartilhar algumas idéias aqui, se valem a pena ;-)

Qual é a maneira apropriada de armazenar vários hosts / tipos de hosts? Ou seja:

gpu_workers:
  - [email protected]
cpu_workers:
  - [email protected]
fab --FLAG gpu_workers do_some_task

Podemos criar um decorador task que substitui o argumento de contexto:

import fabric
from functools import wraps

def task(f, *args, **kwargs):
    @wraps(f)
    def wrapper(c, *args, **kwargs):
        c = fabric.Connection('host')
        f(c, *args, **kwargs)

    return fabric.task(wrapper, *args, **kwargs)

<strong i="7">@task</strong>
def deploy(c):
    with c.forward_remote(9418):
        c.run('git pull http://localhost/app')

ou colocá-lo em um módulo externo

fabutil.py

import inspect
import fabric
from functools import wraps


class Connection(fabric.Connection):
  # add helper methods
  def exists(self, path):
     return not self.run(f'test -e "{path}").failed

def task(f, *args, **kwargs):
    caller = inspect.getmodule(inspect.currentframe().f_back)
    host = caller.HOST
    user = caller.USER

    @wraps(f)
    def wrapper(c, *args, **kwargs):
        c = Connection(host, user=user, connect_kwargs={'key_filename': os.path.expanduser('~/.ssh/id_rsa.pub')})
        f(c, *args, **kwargs)

    return fabric.task(wrapper, *args, **kwargs)

fabfile.py

from fabutil import task

HOST = 'host'
USER = 'user'

<strong i="17">@task</strong>
def deploy(c):
    with c.forward_remote(9418):
        c.run('git pull http://localhost/app')
Esta página foi útil?
0 / 5 - 0 avaliações