Templates de pipeline
Quando a mesma sequência de steps aparece num terceiro projeto, copiar pela terceira vez deixa de ser razoável. Um template é um bloco de steps com nome e parâmetros:
steps: - use: techlite/imagem@^1.0.0 id: web with: registry: us-central1-docker.pkg.dev/acme/apps nome: acme-web versao: ${outputs['versao'].version} credential: ../service-account.jsonEle é expandido, não executado
Seção intitulada “Ele é expandido, não executado”Um template é açúcar de escrita. Ele some antes de qualquer coisa olhar o
pipeline: quando o resolver, o cache, o consent, o oren.lock e o gerador de CI
veem seu arquivo, não existe use: em lugar nenhum — só os steps que você teria
digitado à mão.
Essa é a decisão de projeto inteira, e a alternativa era tentadora: uma task composta, um step que roda vários por dentro. Daria menos código aqui e muito pior em todo o resto, porque uma composição vive na execução e apaga justamente o que este projeto existe para tornar visível:
- O consent viraria tudo-ou-nada. Aprovar “constrói e publica” não é a mesma coisa que aprovar um build e depois aprovar um push que carrega uma credencial.
- O cache viraria tudo-ou-nada. Um bloco com um step
effects: externalnunca pode vir do cache, então um build puro lá dentro deixaria de ser cacheado para sempre. effectsviraria o pior caso entre os steps.- Uma falha reportaria “a composição falhou” em vez de dizer qual push não subiu.
Expandido, nada disso acontece. A abstração vive só onde é escrita.
apiVersion: oren.sh/v1kind: PipelineTemplatemetadata: namespace: techlite name: imagem version: 1.0.0
spec: parameters: required: [registry, nome, versao, credential] properties: registry: { type: string } nome: { type: string } versao: { type: string } credential: { type: string } context: { type: string, default: "." } buildArgs: { type: object }
outputs: image: ${outputs['push'].image} digest: ${outputs['push'].digest}
steps: - id: build task: techlite/build-docker-image@^1.0.0 implementation: techlite/build-docker-image-kaniko inputs: image: ${parameters.registry}/${parameters.nome}:${parameters.versao} context: ${parameters.context} archiveName: ${parameters.nome}.tar buildArgs: ${parameters.buildArgs} dependencies: source: "." artifacts: ./.oren/artefatos
- id: push task: techlite/push-docker-image@^1.0.0 implementation: techlite/push-docker-image-skopeo inputs: image: ${outputs['build'].image} dependencies: artifacts: ./.oren/artefatos registryCredential: ${parameters.credential}Ele fica no registry como *.template.yaml, ao lado dos contratos — e é
resolvido por faixa, @^1.0.0, como uma task.
parameters é um JSON Schema
Seção intitulada “parameters é um JSON Schema”Mesmo rigor dos inputs de uma task: parâmetro desconhecido é erro, senão um
registy: digitado errado sumiria em silêncio; obrigatório ausente é erro; e o
default: é aplicado aqui.
Um parâmetro opcional não fornecido some da chave inteira. buildArgs: ${parameters.buildArgs} sem with.buildArgs gera um step sem buildArgs
nenhum — e não um com valor vazio.
Sozinho na string, ${parameters.x} devolve o valor, com o tipo
preservado: um mapa continua mapa. No meio de um texto, concatena.
${outputs[...]} passa intacto
Seção intitulada “${outputs[...]} passa intacto”Só parameters. é substituído na expansão. outputs, env e properties são
resolvidos na execução, com valores que ainda não existem — então um template
tanto recebe quanto produz expressões:
with: versao: ${outputs['versao'].version} # entra como texto, resolvido na execuçãoSaídas declaradas
Seção intitulada “Saídas declaradas”spec.outputs dá nome ao que o template produz, como um apelido para o
campo de um dos steps dele:
outputs: image: ${outputs['push'].image} digest: ${outputs['push'].digest}Quem usa referencia então o id que ele mesmo escreveu:
- use: techlite/imagem@^1.1.0 id: web with: { ... }
- id: deploy inputs: images: app: ${outputs['web'].image} # e não web-pushSem isso, todo id interno vira API pública do template: renomear push para
publicar lá dentro quebraria todo pipeline que referenciasse web-push,
transformando um rename cosmético em major version.
Apelido, e nunca valor computado — ${outputs['push'].registry}/... é recusado.
Computar é trabalho de step, e um valor composto não caberia no meio de um texto
do consumidor sem gerar ${} aninhado.
Quatro recusas que vale conhecer:
| situação | o que acontece |
|---|---|
outputs['web'].inventado |
erro, listando as saídas que existem |
step de verdade com id: web |
recusado — outputs['web'] teria dois donos |
| saída apontando para fora do template | recusada, listando os steps dele |
| saída que não é apelido | recusada, dizendo a forma esperada |
Referenciar ${outputs['web-push'].image} direto continua funcionando. Nada
fica escondido — a saída declarada é a superfície estável, não a única. Se
você precisar do archive do build, que o template não declara, o id gerado está
lá.
O id e os nomes gerados
Seção intitulada “O id e os nomes gerados”Com id: web, o build e o push do template viram web-build e
web-push. As referências entre os steps do próprio template são reescritas
junto.
Essa reescrita não é cosmética. Sem ela, dois usos do mesmo template gerariam dois pushes apontando ambos para o primeiro build — resolvendo sem erro, porque o id ainda existiria.
Sem id, os ids do template são mantidos como estão. É a escolha certa quando o
template é usado uma vez e o resto do pipeline referencia a saída dele pelo
nome:
- use: techlite/versionar@^1.0.0 # gera `analise` e `versao` with: { manifest: package.json }
- id: tag task: techlite/commit-and-tag@^2.0.0 implementation: techlite/commit-and-tag-alpine inputs: tagName: v${outputs['versao'].version}O oren validate mostra todos os steps gerados, com os ids gerados. Um template
não esconde nada:
[3/7] Constrói acme-web · web-build · build-docker-image[4/7] Publica acme-web · web-push · push-docker-imageenabled: false apaga o bloco inteiro
Seção intitulada “enabled: false apaga o bloco inteiro”Não step a step. Quem desliga “publicar a imagem” não quer que o push suba sozinho.
Publicando
Seção intitulada “Publicando”Enquanto ele não é publicado, todo projeto que usa o template precisa de uma cópia do arquivo — que é o oposto do que um template existe para fazer. Dez projetos seriam dez cópias, e corrigir um bug seriam dez commits.
oren publish ./imagem.template.yamlUm arquivo só, e não um diretório: um template não tem um par
contrato-e-implementação ao lado. O que decide é o kind dentro do
documento, nunca o nome do arquivo — o nome é convenção para varrer um
diretório, e nada além disso.
O portal recusa um template cujas tasks referenciadas não existam lá. Mesma
regra da implementação, e pela mesma razão: quem consumisse receberia um use:
que não expande, e só descobriria na primeira execução.
Depois disso o oren install baixa o template como baixa uma task — e antes
de expandir, porque a expansão é o que o consome.
O que o catálogo mostra
Seção intitulada “O que o catálogo mostra”Um template quase não tem superfície visível própria. O portal a segue:
- As saídas ganham o tipo do contrato do step que as produz, resolvido agora. O template guarda só o apelido.
- As dependências são a união do que os steps dele exigem, mostrando a
mais forte de cada: no
techlite/imagem,artifactsé escrita pelo build e só lida pelo push, então aparece como escrita — é o que você está concedendo. - Os efeitos ficam por step. Não existe agregado. Um template com um build
(
none) e um push (external) mostra os dois, porque colapsar diria “isto age fora dos próprios outputs” — verdade sobre o conjunto, mentira sobre o build, e deixaria quem lê achando que nada ali é cacheável.
Como a referência é uma faixa, @^1.0.0 resolve para o que estiver publicado no
momento em que você olha. Publicar push@1.1.0 depois do template muda o que o
catálogo mostra — de propósito: é a versão que vai executar.
O que um template não faz
Seção intitulada “O que um template não faz”Usar outro template. A profundidade é de um nível, pela mesma razão do
extends: aninhar devolve a opacidade que a expansão existe para evitar.
Iterar. Não existe “gere N builds a partir de uma lista”. Duas imagens são
dois blocos use: — que são quatro linhas, e mantêm todo step contável no
arquivo.
Escolher a implementação por você. O implementation: é obrigatório dentro
do template como é num step: é uma decisão, e um template a toma uma vez, em
nome de quem o escreveu, por escrito.