- El
shell de GitHub Actions es una configuración que determina cómo se ejecuta un bloque run:, pero en la práctica no se limita a una lista fija de shells: también puede apuntar a ejecutables en $PATH
- En los workflows es opcional; en las definiciones de acciones es obligatorio. El valor predeterminado varía según el runner, como
bash en Linux/macOS y pwsh en Windows
- Si se especifica explícitamente, como
shell: bash, GitHub agrega flags adicionales como bash --noprofile --norc -eo pipefail, pero el objetivo ejecutado puede ser cualquier programa
- Si el ejecutable no recibe directamente una entrada de un solo archivo, hay que incluir el argumento
{0} en el valor de shell; GitHub lo reemplaza por la ruta del archivo temporal que contiene el bloque run
- Incluso nombres conocidos como
bash se resuelven desde $PATH, por lo que cambios en $GITHUB_PATH o ejecutables falsos pueden afectar cómo se ejecutan los pasos posteriores
Comportamiento básico de la palabra clave shell
- La palabra clave
shell de GitHub Actions especifica con qué shell se ejecutará un bloque run: determinado
- En los workflows es opcional, pero en las definiciones de acciones es obligatoria
- El shell predeterminado se determina según el entorno del runner
- En Linux y macOS suele ser
bash
- En Windows suele ser
pwsh
Especificar un shell explícitamente y flags adicionales
- En la documentación de
defaults.run.shell, GitHub indica que, al especificar un shell, también se aplican los flags elegidos por GitHub
- Por ejemplo, si se especifica
shell: bash, la forma de ejecución queda así
bash --noprofile --norc -eo pipefail
- Viendo solo este comportamiento, es fácil pensar que GitHub mantiene una lista limitada de valores válidos de shell y que solo agrega flags especiales a esos valores
Ejecutables arbitrarios en $PATH también pueden ser shells
- En realidad, en
shell se puede especificar cualquier ejecutable que esté en $PATH
- GitHub ejecuta el bloque
run con el ejecutable indicado
- Si ese comando no recibe directamente una entrada de un solo archivo, se debe pasar
{0} en el valor de shell
- GitHub reemplaza
{0} por la ruta de un archivo temporal
- Ese archivo temporal contiene el contenido del bloque
run con las plantillas ya expandidas
Ejemplo usando C como runner de pasos
- También pueden usarse herramientas que funcionan como compiladores/intérpretes de C para ejecutar pasos de GitHub Actions
- Un ejemplo que especifica
tcc -run {0} como shell funciona correctamente
- run: sudo apt install -y tcc
- shell: tcc -run {0}
run: |
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Ejemplo de cambiar la resolución del shell con $GITHUB_PATH
- Si se modifica dinámicamente
$PATH mediante $GITHUB_PATH, un shell: bash posterior puede apuntar a un ejecutable distinto al esperado
- El ejemplo crea en el directorio actual un ejecutable llamado
bash y agrega el directorio actual a $GITHUB_PATH
- Luego, al usar
shell: bash, GitHub puede ejecutar el bash falso encontrado en $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
Punto inesperado desde el punto de vista de seguridad
- Es difícil afirmar si este comportamiento es importante para la seguridad
- En GitHub Actions ya hay muchos caminos donde escribir archivos puede derivar en ejecución, y
GITHUB_ENV también entra en esa categoría
- Aun así, puede resultar inesperado que GitHub haga una búsqueda en
$PATH incluso para valores de shell conocidos como bash
- En particular, si inyecta flags de línea de comandos según valores de shell conocidos, podría esperarse que
bash quedara fijado a una ruta específica como /bin/bash
- De forma más general, también podría pensarse que GitHub solo permite herramientas registradas previamente en la caché de herramientas, pero el comportamiento observado usa ejecutables de
$PATH
2 comentarios
Con solo ver el repositorio github/runner-image, ya se nota que vienen instalados bastantes paquetes que se pueden usar tal cual....
Si armas la imagen, fácilmente se va a 1 GB....
Opiniones de Hacker News
-xpara que bash imprimiera todos los comandos que ejecuta en un workflow de Actions, y ayuda bastante para depurarhttps://github.com/jstrieb/just.sh/blob/2da1e2a3bfb51d583be0...
pipefail tampoco evita estados de error más complejos. Por ejemplo, en el paso
curl ... | sudo tar ...de la configuración, se evidencian casos como una falla al extraer contar, una falla desudoo un error del shell, pero sicurlfalla a mitad de camino por un error de red,tarpuede dejar descomprimido solo la mitad del binario dejusty el shell puede terminar de inmediato. En ese caso no hay mensaje de error, el ejecutable dañado queda en el disco y, si está activado omitir fallas, incluso se puede intentar ejecutarlorepository_dispatchSe puede usar algo como
on: repository_dispatch: - security_scan - security_scan::*. Si centralizas el pipeline de releases, puedes obligar a que cada repositorio pase por un workflow reutilizable definido, y si envías eventos comosecurity_scan::$product_name::$version, es mucho más fácil ver en la pestaña Actions del repositorio central de releases qué workflows se están ejecutando para qué producto y versiónAcabo de entrar a una organización que intenta hacer algo parecido, pero en la práctica parece casi inútil. Las plantillas se rompen seguido y muchas veces están escritas asumiendo una forma específica de compilar el código, sin documentación sobre cómo debería compilarse
Normalmente prefiero poner la lógica en un sistema de build como Make y solo invocarlo desde GitHub Actions, o escribir un pequeño programa de línea de comandos e invocarlo. Eso es mucho más fácil de depurar localmente que en CI. Es un truco interesante, pero no sé bien dónde sería útil
make buildymake testDespués de que nos adquirieron, vi el archivo de workflow de la empresa compradora y tenía cientos de líneas, con muchas secciones repetidas. Llámenme anticuado si quieren, pero quiero salir del pueblo YAML lo antes posible
Saber qué es posible en un sistema es útil para seguridad y depuración, incluso si no planeas usar esa función en la práctica
En especial al “explorar” runners autoalojados
La siguiente generación se aterrorizará cuando le pidan imponer disciplina a despliegues hechos con GitHub Actions
Si se puede explicar, probablemente podamos implementarlo en nuestra organización
bash, para que ejecute un programa arbitrarioMuchas veces he visto scripts de shell que empezaron como unas cuantas líneas de comandos tecleadas a mano, casi copiadas tal cual, y terminaron convertidos en monstruos de más de cien líneas; en esos casos siempre he deseado tener arreglos y tipos reales, y las funciones baterías incluidas de la biblioteca estándar de Python. Dicho eso, no implementaría la Action de build de la empresa en elisp
Se expone mediante el método
ScriptHandlerHelpers.GetScriptArgumentsFormat. EnScriptHandler.csestá el código que prepara el entorno del proceso y los argumentos, y el código que inicia realmente el proceso está aquí: https://github.com/actions/runner/blob/main/src/Runner.Worke...En general, me sorprendió para bien la simplicidad de este código. Es muy procedimental y maneja muchísimos casos excepcionales, pero parece fácil de entender y depurar
Supongo que también se podrá con ensamblador
Aunque goeval todavía no admite entrada de archivos directamente y solo admite entrada estándar, así que hace falta un truco de shell. Ahora es algo como
go run github.com/dolmen-go/goeval@v1 - <<'EOF' ... EOF, lo cual requiere un poco de boilerplate. Por cierto, soy el autor de goeval[1] https://github.com/dolmen-go/goeval
go run github.com/dolmen-go/goeval@v1 - < file.go?Si vas a promocionar un proyecto de juguete en este contexto, mostrar un ejemplo más relevante que “imprimir hello” me habría ahorrado el clic
run: pipeline.sh?Puedes ejecutar trabajos al mismo tiempo en varios sistemas operativos y arquitecturas de CPU, y también obtener información de contexto sobre el evento que disparó el trabajo y el estado del repositorio. Es práctico para trabajos por PR o automatización de releases, y también puede integrarse con la UI web de GitHub, por ejemplo para que un linter muestre problemas en cada línea del PR o para renderizar fallas de tests en una página web. También se puede usar una pequeña caché para no volver a descargar ni recompilar archivos que no cambiaron. Idealmente, conviene poner lo más posible en herramientas comunes que corran localmente, y dejar que la configuración de GitHub CI se encargue solo del código pegamento necesario para triggers, caché e integración con GitHub
Puedes aprovechar fácilmente GitHub Actions hechas por otras personas en tu pipeline, modularizar workflows y controlar dependencias y ejecución en paralelo. Seguramente hay más, pero la ventaja principal es no tener que implementar todo eso tú mismo