- O
shell do GitHub Actions é uma configuração que define como o bloco run: será executado, mas na prática ele pode apontar não só para uma lista fixa de shells, como também para executáveis presentes no $PATH
- Em workflows ele é opcional, mas em definições de actions é obrigatório, e o valor padrão varia conforme o runner, como
bash no Linux/macOS e pwsh no Windows
- Ao especificar algo como
shell: bash, o GitHub acrescenta flags extras como bash --noprofile --norc -eo pipefail, mas o alvo executado em si pode ser um programa arbitrário
- Se o executável não aceitar um único arquivo de entrada, é preciso incluir o argumento
{0} no valor de shell; o GitHub o substitui pelo caminho de um arquivo temporário com o conteúdo do bloco run
- Como nomes familiares como
bash também são resolvidos a partir do $PATH, mudanças no $GITHUB_PATH ou executáveis falsos podem afetar como os passos seguintes serão executados
Comportamento padrão da palavra-chave shell
- A palavra-chave
shell do GitHub Actions define com qual shell um bloco run: específico será executado
- Em workflows ela é opcional, mas em definições de actions seu uso é obrigatório
- O shell padrão é definido de acordo com o ambiente do runner
- Em Linux e macOS, normalmente
bash
- No Windows, normalmente
pwsh
Definição explícita de shell e flags extras
- Na documentação de
defaults.run.shell, o GitHub informa que, ao declarar o shell explicitamente, também aplica flags escolhidas por ele
- Por exemplo, ao especificar
shell: bash, a execução fica assim
bash --noprofile --norc -eo pipefail
- Olhando só para esse comportamento, é fácil supor que o GitHub mantém uma lista limitada de valores válidos de shell e só adiciona flags especiais a esses valores
Qualquer executável no $PATH também pode ser um shell
- Na prática, é possível indicar em
shell qualquer executável presente no $PATH
- O GitHub executa o bloco
run usando o executável especificado
- Se esse comando não aceitar diretamente um único arquivo de entrada, é preciso passar
{0} no valor de shell
- O GitHub substitui
{0} pelo caminho de um arquivo temporário
- Esse arquivo temporário contém o conteúdo do bloco
run após expansão de template
Exemplo usando C como executor de passo
- Ferramentas que funcionam como compilador/intérprete de C também podem ser usadas para executar passos no GitHub Actions
- O exemplo que define
tcc -run {0} como shell funciona normalmente
- run: sudo apt install -y tcc
- shell: tcc -run {0}
run: |
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Exemplo mudando a resolução do shell com $GITHUB_PATH
- Se você alterar dinamicamente o
$PATH via $GITHUB_PATH, um shell: bash posterior pode apontar para um executável diferente do esperado
- O exemplo cria um executável chamado
bash no diretório atual e adiciona esse diretório ao $GITHUB_PATH
- Depois disso, ao usar
shell: bash, o GitHub pode executar o bash falso encontrado no $PATH
- run: |
touch ./bash
chmod +x ./bash
echo '#!/bin/sh' > ./bash
echo 'echo hello from fake bash' >> ./bash
echo "${PWD}" >> "${GITHUB_PATH}"
- run: |
echo "this doesn't do what you expect"
shell: bash
Um ponto inesperado do ponto de vista de segurança
- É difícil afirmar com certeza o quão importante isso é do ponto de vista de segurança
- No GitHub Actions já existem muitos caminhos em que gravar arquivos leva à execução, e
GITHUB_ENV também entra nessa categoria
- Ainda assim, pode ser inesperado que o GitHub faça busca no
$PATH até mesmo para valores de shell bem conhecidos como bash
- Especialmente se ele injeta flags de linha de comando de acordo com o valor de shell conhecido, seria razoável esperar que
bash fosse fixado em um caminho específico como /bin/bash
- De forma mais geral, alguém pode imaginar que o GitHub só permitiria ferramentas previamente registradas no cache de ferramentas, mas o comportamento observado é o uso de executáveis vindos do
$PATH
2 comentários
Só de ver o repositório github/runner-image, já dá para notar que há bastante pacote instalado que pode ser usado direto....
Se criar a imagem, 1 GB entra fácil....
Opiniões no Hacker News
-xno passado para fazer o bash imprimir todos os comandos que executa em workflows do Actions, e isso ajuda bastante na depuraçãohttps://github.com/jstrieb/just.sh/blob/2da1e2a3bfb51d583be0...
O pipefail também não evita estados de erro mais complexos. Por exemplo, numa etapa de configuração como
curl ... | sudo tar ..., casos como falha na extração pelotar, falha dosudoou erro do shell aparecem, mas se ocurlfalhar no meio por um erro de rede, otarpode deixar o bináriojustextraído pela metade e o shell pode encerrar imediatamente. Nesse caso não há mensagem de erro, e um executável corrompido fica no disco; se estiver ativado pular falhas, pode até haver uma tentativa de executá-lorepository_dispatchpara fazer matchingDá para escrever algo como
on: repository_dispatch: - security_scan - security_scan::*. Ao centralizar o pipeline de release, você pode forçar cada repositório a passar pelo workflow reutilizável definido e, ao enviar um evento comosecurity_scan::$product_name::$version, fica muito mais fácil ver na aba Actions do repositório central de releases para qual produto e versão o workflow está rodandoAcabei de entrar numa organização tentando fazer algo parecido, mas na prática parece quase inútil. Os templates quebram com frequência e muitas vezes foram escritos assumindo um modo específico de buildar o código, sem documentação sobre como o código deve ser buildado
Normalmente prefiro colocar a lógica em um sistema de build como Make e apenas chamá-lo pelo GitHub Actions, ou então escrever um pequeno programa de linha de comando e chamá-lo. Isso é muito mais fácil de depurar localmente do que no CI. É um truque interessante, mas não sei bem onde seria útil
make buildemake testDepois de sermos adquiridos, olhei os arquivos de workflow da empresa compradora e vi centenas de linhas, com muitas partes repetidas. Pode me chamar de antiquado, mas quero sair da vila do YAML o mais rápido possível
Saber o que é possível em um sistema é útil para segurança e depuração, mesmo que você não planeje usar o recurso de fato
Especialmente ao “explorar” runners auto-hospedados
A próxima geração vai tremer quando pedirem para impor disciplina a deploys feitos com GitHub Actions
Se der para explicar, acho que talvez consigamos implementá-la na nossa organização
bash, para fazê-lo executar um programa arbitrárioMuitas vezes comecei com um script de shell que era quase uma transcrição de algumas linhas digitadas à mão e ele acabou virando um monstro de mais de cem linhas; nessas horas eu queria muito ter arrays e tipos de verdade, além dos recursos batteries included da biblioteca padrão do Python. Dito isso, não vou implementar a Action de build da empresa em elisp
Ele é exposto pelo método
ScriptHandlerHelpers.GetScriptArgumentsFormat. EmScriptHandler.cshá o código de preparação do ambiente do processo e dos argumentos, e o código que de fato inicia o processo está aqui: https://github.com/actions/runner/blob/main/src/Runner.Worke...No geral, fiquei positivamente surpreso com a simplicidade desse código. É muito procedural e lida com inúmeras exceções, mas parece fácil de entender e depurar
Assembly também deve dar
Mas o goeval ainda não aceita entrada de arquivo diretamente, só entrada padrão, então é necessário um truque de shell. Hoje é algo como
go run github.com/dolmen-go/goeval@v1 - <<'EOF' ... EOF, o que exige um pouco de boilerplate. Observação: sou o autor do goeval[1] https://github.com/dolmen-go/goeval
go run github.com/dolmen-go/goeval@v1 - < file.gonão resolveria?Nesse contexto, se a ideia era promover um projeto de brinquedo, um exemplo mais relevante do que “imprimir hello” teria poupado o clique
run: pipeline.sh?Dá para executar jobs ao mesmo tempo em vários sistemas operacionais e arquiteturas de CPU, além de obter informações de contexto sobre o evento que disparou o job e o estado do repositório. É conveniente para jobs por PR e automação de releases, e também pode se integrar à interface web do GitHub, por exemplo fazendo um linter marcar problemas linha a linha no PR ou renderizando falhas de teste numa página web. Também dá para usar pequenos caches para evitar baixar de novo ou rebuildar arquivos que não mudaram. O ideal é colocar o máximo possível em ferramentas comuns que rodam localmente e deixar a configuração do GitHub CI apenas com a cola necessária para gatilhos, cache e integrações com o GitHub
Você pode aproveitar facilmente GitHub Actions criadas por outras pessoas no pipeline, modularizar workflows e controlar dependências e execução paralela. Há mais vantagens, mas o principal é não precisar implementar essas coisas por conta própria