{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Que GitHub trabaje mientras duermes\n",
    "\n",
    "Actions explicado sin jerga: qué es, para qué sirve y cuándo no vale la pena\n",
    "\n",
    "Cuaderno de soluciones del capítulo 31 de **Git desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/git-desde-cero/github-actions/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es el último ladrillo, y es el que convierte un repositorio en algo que\n",
    "trabaja solo. La idea en una frase: **cuando pase esto, corre esto** ⚙️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Para qué sirve de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Revisar que lo que alguien propone no rompa nada, antes de aceptarlo.\n",
    "\n",
    "- Publicar la web sola cada vez que subes, como en\n",
    "19.\n",
    "\n",
    "- Tareas de calendario: un reporte todos los lunes a las siete.\n",
    "\n",
    "- Comprobaciones aburridas que nadie se acuerda de hacer, como que ningún CSV\n",
    "llegue con la ciudad vacía.\n",
    "\n",
    "Y para qué no: si tu proyecto es una carpeta con dos scripts que corres tú\n",
    "una vez al mes, montar Actions es trabajo que no se paga 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El archivo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vive en una ruta exacta, `.github/workflows/`, y termina en\n",
    "`.yml`. Vamos a escribir uno que revise el CSV de ventas en cada\n",
    "push:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "mkdir reporte-ventas\n",
    "cd reporte-ventas\n",
    "git init -q\n",
    "printf 'ciudad,monto\\nLima,1200\\nArequipa,890\\n' > ventas.csv\n",
    "mkdir -p .github/workflows\n",
    "cat > .github/workflows/revisa.yml <<'FIN'\n",
    "name: Revisa el CSV de ventas\n",
    "\n",
    "on:\n",
    "  push:\n",
    "  pull_request:\n",
    "\n",
    "jobs:\n",
    "  revisar:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - uses: actions/checkout@v4\n",
    "      - name: Ningun monto vacio\n",
    "        run: |\n",
    "          tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "          echo \"CSV correcto\"\n",
    "FIN\n",
    "git add .\n",
    "git commit -q -m \"Se revisa el CSV de ventas en cada push\"\n",
    "git ls-files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo de arriba abajo y se entiende sin saber nada: se llama así, se dispara\n",
    "con un push o un pull request, corre en una máquina Linux, se baja tu código y\n",
    "ejecuta esos comandos 📖"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Probar la revisión aquí antes de subirla"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo que corre allá son comandos normales, así que se pueden probar en tu\n",
    "máquina primero. Es lo que yo hago siempre, porque esperar a que GitHub falle\n",
    "para descubrir un error de dedo es lentísimo:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "echo \"CSV correcto\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "printf 'Cusco,\\n' >> ventas.csv\n",
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -q '^$'\n",
    "echo \"encontro una fila vacia, la revision fallaria\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con eso ya sabes que la revisión detecta lo que tiene que detectar, y recién\n",
    "ahí la subes 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "git checkout -q ventas.csv\n",
    "cat ventas.csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuándo se dispara"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Escribes | Se ejecuta |\n",
    "|---|---|\n",
    "| `on: push` | Cada vez que subes |\n",
    "| `on: pull_request` | Cuando alguien propone un cambio |\n",
    "| `on: schedule` con un cron | A una hora fija |\n",
    "| `on: workflow_dispatch` | Cuando le das a un botón |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El de `pull_request` es el que más cambia la vida de un equipo:\n",
    "la revisión automática corre sobre la propuesta y quien revisa ya sabe si\n",
    "funciona antes de leer una línea, que es justo lo que le faltaba a la trampa de\n",
    "23 ✅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las claves no van dentro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si tu automatización necesita una clave, va en\n",
    "*Settings > Secrets and variables > Actions*, y en el archivo se\n",
    "usa por su nombre:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "      - name: Manda el reporte\n",
    "        env:\n",
    "          TOKEN: ${{ secrets.TOKEN_DEL_REPORTE }}\n",
    "        run: python3 manda_reporte.py\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese archivo se puede subir tranquilamente: no lleva el valor, lleva el\n",
    "nombre. Y GitHub tapa el valor en los registros si por accidente se imprime 🔐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cuánto cuesta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En repositorios públicos, Actions es gratis. En privados hay minutos\n",
    "incluidos al mes y después se paga, así que conviene mirar el consumo antes de\n",
    "poner algo a correr cada cinco minutos ⏱️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Montas una automatización que sube el reporte a un servicio y pegas la clave dentro del archivo de configuración, que total es tu repositorio.\n",
    "\n",
    "```\n",
    "name: Reporte diario\n",
    "on:\n",
    "  schedule:\n",
    "    - cron: '0 12 * * *'\n",
    "jobs:\n",
    "  reporte:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - run: curl -H \"Authorization: Bearer sk-abc123...\" ...\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Esa clave está en el repositorio y en cada registro de ejecución 🔓\n",
    "\n",
    "Los archivos de Actions son archivos normales del proyecto: se ven, se clonan y se indexan igual que el README. Y además, todo lo que el paso imprima queda guardado en el registro de la ejecución, que en un repositorio público lee cualquiera.\n",
    "\n",
    "Para esto existen los *secrets*: se guardan en *Settings > Secrets and variables* y se usan por su nombre, nunca por su valor. GitHub además los tapa en los registros.\n",
    "\n",
    "Y si ya pasó, el orden es el de 28: rotar primero, limpiar después."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tu automatización necesita la clave de un servicio. ¿Dónde la pones?\n",
    "\n",
    "a) En los secrets del repositorio, y la uso por su nombre\n",
    "\n",
    "b) Dentro del archivo `.yml`\n",
    "\n",
    "c) En un archivo aparte que agrego al `.gitignore`\n",
    "\n",
    "d) La escribo cada vez que ejecuto\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Ese archivo es parte del repositorio: se clona, se lee y se indexa como cualquier otro.\n",
    "\n",
    "*c)* La máquina de GitHub solo tiene lo que está en el repositorio, así que ese archivo no llegaría.\n",
    "\n",
    "*d)* Actions corre sin nadie delante, que es justamente la gracia.\n",
    "\n",
    "El archivo lleva el nombre de la clave, nunca el valor 🔐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Escribe tu primera automatización\n",
    "\n",
    "Una revisión que comprueba que el catálogo no tenga\n",
    "ciudades vacías."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "cd ..\n",
    "mkdir catalogo\n",
    "cd catalogo\n",
    "git init -q\n",
    "printf 'bodega,ciudad\\nBodega Inti,Cusco\\n' > bodegas.csv\n",
    "mkdir -p .github/workflows\n",
    "cat > .github/workflows/revisa.yml <<'FIN'\n",
    "name: Revisa el catalogo\n",
    "\n",
    "on:\n",
    "  push:\n",
    "  pull_request:\n",
    "\n",
    "jobs:\n",
    "  revisar:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - uses: actions/checkout@v4\n",
    "      - run: |\n",
    "          tail -n +2 bodegas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "          echo \"catalogo correcto\"\n",
    "FIN\n",
    "git add .\n",
    "git commit -q -m \"Se revisa el catalogo en cada push\"\n",
    "git ls-files\n",
    ".github/workflows/revisa.yml\n",
    "bodegas.csv\n",
    "La ruta tiene que ser exactamente esa o GitHub no lo encuentra."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Prueba la revisión aquí\n",
    "\n",
    "Los mismos comandos, en tu máquina."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "tail -n +2 bodegas.csv | cut -d, -f2 | grep -q '^$' && exit 1\n",
    "echo \"catalogo correcto\"\n",
    "catalogo correcto\n",
    "Pasa. Ahora hay que comprobar que también sabe fallar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Comprueba que detecta el error\n",
    "\n",
    "Mete una bodega sin ciudad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "printf 'Mayorista Norte,\\n' >> bodegas.csv\n",
    "tail -n +2 bodegas.csv | cut -d, -f2 | grep -q '^$'\n",
    "echo \"detectado, la revision fallaria\"\n",
    "detectado, la revision fallaria\n",
    "Una revisión que nunca falla no está revisando nada 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Devuelve el archivo a como estaba\n",
    "\n",
    "Con lo del capítulo de deshacer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "git checkout -q bodegas.csv\n",
    "cat bodegas.csv\n",
    "git status --short\n",
    "bodega,ciudad\n",
    "Bodega Inti,Cusco\n",
    "Limpio otra vez."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Agrega un disparo por calendario\n",
    "\n",
    "Que además corra todos los lunes a las 12 UTC."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "cat > .github/workflows/semanal.yml <<'FIN'\n",
    "name: Reporte semanal\n",
    "\n",
    "on:\n",
    "  schedule:\n",
    "    - cron: '0 12 * * 1'\n",
    "  workflow_dispatch:\n",
    "\n",
    "jobs:\n",
    "  reporte:\n",
    "    runs-on: ubuntu-latest\n",
    "    steps:\n",
    "      - uses: actions/checkout@v4\n",
    "      - run: wc -l bodegas.csv\n",
    "FIN\n",
    "git add .github\n",
    "git commit -q -m \"Reporte semanal del catalogo\"\n",
    "git ls-files .github\n",
    ".github/workflows/revisa.yml\n",
    ".github/workflows/semanal.yml\n",
    "El `workflow_dispatch` agrega además un botón para lanzarlo a\n",
    "mano."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Comprueba que no metiste claves\n",
    "\n",
    "La revisión de siempre, ahora sobre los archivos de\n",
    "automatización."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "grep -rn \"TOKEN=\\|API_KEY=\\|password\" .github || echo \"limpio\"\n",
    "cat .github/workflows/semanal.yml | head -6\n",
    "limpio\n",
    "name: Reporte semanal\n",
    "\n",
    "on:\n",
    "  schedule:\n",
    "    - cron: '0 12 * * 1'\n",
    "  workflow_dispatch:\n",
    "Limpio. Si eso devuelve algo, no se sube 🔐"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Pide un archivo de workflow que no existe\n",
    "\n",
    "Los nombres de esta carpeta son literales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "cat .github/workflows/despliegue.yml\n",
    "cat: .github/workflows/despliegue.yml: No such file or directory\n",
    "Si el archivo no está donde toca, para GitHub simplemente no existe 🕳️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Cuando pase esto, corre esto. Prueba los comandos en tu máquina\n",
    "antes, y las claves siempre en los secrets.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 31 de **Git desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/git-desde-cero/github-actions/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
