Hay una pregunta que este curso no ha hecho todavía y conviene hacerla sin rodeos: ¿dónde está el
código de MercadoFresco? La respuesta honesta es que está en el portátil de Luis, en
~/proyectos/tienda, con un git init de hace catorce meses y 1.847 commits que nadie más ha visto.
Hay una copia comprimida en un USB de septiembre. Marta tiene una carpeta tienda-v2-FINAL que no sabe
si es anterior o posterior. Y cuando Luis despliega, hace scp de la carpeta entera a una instancia del
ASG asg-mercadofresco-tienda y reinicia el servicio a mano.
Todo lo construido en siete módulos descansa sobre un código fuente que vive en un solo disco duro sin copia fiable. Este módulo arregla eso y empieza por el principio: dónde vive el código y cómo se gobiernan los cambios.
Aviso de coste. CodeCommit cobra 1 USD por usuario activo y mes a partir del sexto, con 50 GB y 10.000 peticiones Git incluidas: para el equipo de tres de MercadoFresco, cero. Las conexiones de CodeConnections hacia GitHub o GitLab también son gratuitas; se paga el uso aguas abajo, que veremos en 08-02 y 08-04. Todos los datos y credenciales de esta lección son ficticios.
Contenido
- El punto de partida: un repositorio en un portátil
- Qué aporta un repositorio Git gestionado
- La situación de CodeCommit, sin adornos
- Comparativa: CodeCommit, GitHub, GitLab y Bitbucket
- CodeConnections: el puente entre AWS y tu proveedor Git
- Los repositorios de MercadoFresco
- Autenticación: HTTPS, SSH, el ayudante de la CLI y roles
- Políticas IAM: quién puede empujar a
main - Estrategia de ramas: trunk-based frente a GitFlow
- El flujo de ramas de MercadoFresco
- Pull requests: revisión, aprobación y fusión
- Reglas de aprobación y protección de
main - Convenciones: commits, versión semántica y etiquetas
- El
.gitignorey por qué nunca se sube un.env - Notificaciones y disparadores
- Migrar el repositorio de Luis
- Coste y limpieza
- Errores comunes y consejos
- Ejercicios
- Conclusión
El punto de partida: un repositorio en un portátil
Marta hizo un inventario del riesgo antes de tocar nada:
| Riesgo | Situación actual | Consecuencia |
|---|---|---|
| Pérdida del portátil | Copia USB de hace 5 meses | 5 meses de trabajo perdidos |
| Dos personas tocan lo mismo | Se avisan por chat | Sobrescritura silenciosa |
| Saber qué hay en producción | «Lo que subí el viernes» | Nadie reproduce un fallo |
| Volver a la versión anterior | Buscar en la papelera | Minutos u horas de caída |
| Revisar antes de que entre | No se hace | Errores vistos en producción |
| Secretos en el código | Hay un .env con la clave de RDS |
Fuga al compartir el repositorio |
Los cuatro primeros los resuelve cualquier repositorio remoto. Los tres últimos necesitan además gobierno: reglas sobre quién puede hacer qué y en qué condiciones entra un cambio. Esa distinción es la lección entera: alojar el código es la parte fácil.
Qué aporta un repositorio Git gestionado
Git es distribuido y nada obliga a tener servidor, pero todo equipo designa una copia como la de referencia, y esa copia necesita estar disponible, tener copia de seguridad y controlar quién escribe.
| Aspecto | Servidor propio (EC2 + Gitea/GitLab CE) | Gestionado |
|---|---|---|
| Durabilidad | La del EBS; tú haces las copias | Replicado entre AZ, sin acción tuya |
| Disponibilidad | Una instancia = un punto único de fallo | Acuerdo de servicio del proveedor |
| Cifrado en reposo | Lo configuras tú | Automático; en CodeCommit, con KMS |
| Identidad | Usuarios de sistema o LDAP propio | IAM (CodeCommit) u OIDC/SAML |
| Parcheado y CVE | Tuyos | Del proveedor |
| Coste real | Instancia + EBS + tu tiempo | Por usuario o incluido |
| Auditoría | Logs del servidor, si los guardas | CloudTrail o registro del proveedor |
Para tres personas, mantener un servidor Git es tiempo que no va al producto: MercadoFresco no lo monta.
Merece un párrafo el cifrado en reposo de CodeCommit, que conecta con el módulo 4: cifra siempre, sin
opción de desactivarlo, y puede hacerlo con una clave del cliente. Aquí eso significa reutilizar
alias/mercadofresco-datos, la misma clave que ya cifra Aurora y los buckets: una clave, una política, una
rotación y un único sitio donde revocar el acceso. Es la ventaja concreta de la integración: no hay un
segundo sistema de cifrado que gobernar.
La situación de CodeCommit, sin adornos
Desde julio de 2024, AWS no admite nuevos clientes en CodeCommit. Si tu cuenta ya lo usaba antes, sigue funcionando con soporte y correcciones de seguridad. Si es nueva, la consola no te dejará crear repositorios. No hay fecha de cierre anunciada, pero tampoco funcionalidades nuevas: la recomendación oficial para proyectos nuevos es un proveedor Git externo conectado con CodeConnections.
Tres consecuencias prácticas: te lo vas a encontrar en empresas que lo adoptaron entre 2015 y 2024 con cientos de repositorios, así que saber operarlo es útil; no lo elijas para un proyecto nuevo, ni aunque tu cuenta antigua te deje, porque estarías construyendo sobre un servicio sin evolución; y lo que importa aquí no es el servicio, son los conceptos —ramas, pull requests, reglas de aprobación, protección de rama, convenciones de commit y de versión son idénticos en GitHub, GitLab o Bitbucket, y son lo que consumirán 08-02, 08-03 y 08-04.
Marta toma la decisión que tomarías hoy: el código va a GitHub, y AWS se conecta con CodeConnections. La lección sigue explicando CodeCommit porque te lo encontrarás y porque su modelo de permisos —IAM puro— es didácticamente muy claro.
Comparativa: CodeCommit, GitHub, GitLab y Bitbucket
| Criterio | CodeCommit | GitHub | GitLab | Bitbucket |
|---|---|---|---|---|
| Nuevos clientes | No admitidos | Sí | Sí | Sí |
| Identidad | IAM nativo | Propia; SSO de pago | Propia; SSO | Atlassian |
| Revisión de código | Funcional pero pobre | La mejor del mercado | Muy completa | Correcta |
| CI/CD propio | No (usa CodeBuild) | GitHub Actions | GitLab CI | Bitbucket Pipelines |
| Integración AWS | Nativa total | CodeConnections + OIDC | CodeConnections + OIDC | CodeConnections |
| Autoalojable | No | Enterprise Server | Sí, self-managed | Data Center |
| Ecosistema | Nulo | Enorme | Amplio e integrado | Ligado a Jira |
| Residencia del código | Tu cuenta y región AWS | Servidores de GitHub | GitLab o tu servidor | Atlassian |
| Coste 3 / 30 personas | 0 / ~25 USD mes | 0 / ~120 USD mes | 0 / ~870 USD mes | 0 / ~90 USD mes |
Criterio de elección, en el orden en que hay que aplicarlo:
- ¿Hay exigencia legal o contractual de que el código no salga de tu cuenta o de la UE? Si es innegociable, tus opciones son CodeCommit (solo si ya lo tenías) o GitLab autoalojado. Es el único criterio que anula a todos los demás.
- ¿Dónde está ya tu equipo? Mover a gente que lleva años en GitHub tiene un coste de fricción diario que se paga para siempre. Este criterio decide la mayoría de los casos.
- ¿Usarás el CI/CD del proveedor o el de AWS? Actions y GitLab CI son excelentes y despliegan en AWS con OIDC sin claves; si prefieres construcción y despliegue dentro de AWS —con sus roles, sus VPC y su factura—, CodeConnections da lo mejor de ambos.
- ¿Cuánto ecosistema necesitas? En escaneo de dependencias, incidencias y tableros, GitHub y GitLab ganan sin discusión.
MercadoFresco: el equipo ya está en GitHub, no hay restricción sobre el código fuente (los datos de
clientes sí están restringidos a eu-west-1, pero eso es otra cosa) y quieren construcción y despliegue
en AWS para reutilizar roles y alarmas. GitHub + CodeConnections + CodeBuild/CodeDeploy/CodePipeline.
CodeConnections: el puente entre AWS y tu proveedor Git
CodeConnections —hasta 2023, AWS CodeStar Connections; verás ambos nombres en la documentación y en ARN antiguos— es un recurso de AWS que representa una autorización OAuth entre tu cuenta y una organización de GitHub, GitLab o Bitbucket. Una vez creada, CodePipeline o CodeBuild leen el repositorio y reciben sus eventos sin que tú almacenes ningún token en ningún sitio.
Se crea en dos pasos, y el segundo es obligatoriamente manual:
# Paso 1: crear la conexion. Nace en PENDING y todavia no sirve para nada.
aws codeconnections create-connection \
--provider-type GitHub \
--connection-name conn-mercadofresco-github \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=cicd Key=Propietario,Value=marta Key=CentroCoste,Value=plataforma \
--profile mercadofresco-dev --region eu-west-1Paso 2, en la consola y solo en la consola. Developer Tools → Settings → Connections, seleccionar la conexión pendiente y pulsar «Update pending connection». Se abre el flujo OAuth de GitHub, se instala la app AWS Connector for GitHub en la organización y se eligen los repositorios autorizados. Este paso no se puede automatizar por diseño: es una autorización humana en un proveedor externo, no una llamada a la API de AWS. Si una plantilla de CloudFormation «crea conexiones listas», crea el recurso y alguien lo autoriza a mano una vez.
Comprueba con aws codeconnections list-connections que el estado es AVAILABLE antes de seguir: una
conexión en PENDING es la causa número uno de que la acción de origen de un pipeline falle con un error
de permisos poco descriptivo, y volveremos a ello en 08-04.
Concede acceso solo a los repositorios necesarios. GitHub ofrece dar acceso a toda la organización; elige «Only select repositories» y marca los dos de MercadoFresco. Es el privilegio mínimo de 04-01 aplicado fuera de AWS.
Los repositorios de MercadoFresco
| Repositorio | Contenido | Quién lo toca | Cadencia |
|---|---|---|---|
mercadofresco-tienda |
Web, API, consumidores de colas, código de las Lambdas, pruebas, buildspec.yml, appspec.yml |
Luis | Varias veces al día |
mercadofresco-infra |
Definición de la infraestructura, scripts de operación, runbooks | Marta | Semanal |
Separarlos permite desplegar la aplicación veinte veces al día sin arrastrar cambios de infraestructura y
someter un cambio de red a revisión más estricta. El contenido de mercadofresco-infra lo llenaremos en el
módulo 9: hoy solo existe el repositorio.
# En CodeCommit
aws codecommit create-repository \
--repository-name mercadofresco-tienda \
--repository-description "Aplicacion web, API y consumidores de MercadoFresco" \
--kms-key-id alias/mercadofresco-datos \
--tags Proyecto=mercadofresco,Entorno=produccion,Componente=codigo,Propietario=luis,CentroCoste=plataforma \
--profile mercadofresco-dev --region eu-west-1
# En GitHub (la opcion elegida)
gh repo create mercadofresco/mercadofresco-tienda --private
gh repo create mercadofresco/mercadofresco-infra --private--kms-key-id es donde se materializa el cifrado con clave propia: si algún día revocas esa clave, el
repositorio deja de ser legible, que es exactamente lo que quieres ante una fuga. Y el repositorio es
siempre privado, sin «de momento público que ya lo cambiaremos»: los rastreadores que buscan
credenciales en repositorios públicos tardan minutos, no días.
Autenticación: HTTPS, SSH, el ayudante de la CLI y roles
Aquí está la diferencia conceptual más interesante de CodeCommit: no hay usuarios de Git, hay identidades de IAM. No existe un «usuario luis» del servidor con su contraseña; existe su usuario o rol de IAM, y el permiso para empujar es una política. Dar de baja a alguien es quitarle IAM, no acordarse de un segundo sistema.
| Método | Cómo funciona | Cuándo | Inconveniente |
|---|---|---|---|
| Credenciales HTTPS de IAM | Usuario/contraseña generados en IAM | Portátil con usuario IAM | Material de larga duración |
| Claves SSH | Clave pública en el usuario IAM | Portátil, si prefieres SSH | Igual |
| Ayudante de credenciales | Git pide a la CLI una firma SigV4 | Recomendado: SSO, MFA, roles | Requiere la CLI |
| Rol IAM | La máquina asume un rol, sin credenciales | CodeBuild, EC2, Lambda | Solo para máquinas |
Usa el ayudante de credenciales. Su virtud es que no guarda nada: cada vez que Git necesita
autenticarse invoca aws codecommit credential-helper, que firma con las credenciales actuales del
perfil. Si vienen de IAM Identity Center y caducan a las ocho horas, el acceso caduca con ellas. Es el
principio de credenciales temporales de 04-01 aplicado al repositorio.
git config --global credential.helper '!aws --profile mercadofresco-dev codecommit credential-helper $@'
git config --global credential.UseHttpPath true
git clone https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/mercadofresco-tiendacredential.UseHttpPath true es obligatorio y se olvida constantemente: sin él, Git no envía la ruta del
repositorio, la firma se calcula sobre un recurso equivocado y obtienes un 403 que no explica nada.
En GitHub, el equivalente moderno para las máquinas es OIDC, no una clave de acceso. En lugar de
guardar un AWS_ACCESS_KEY_ID como secreto del repositorio, se establece confianza entre el proveedor
OIDC de GitHub y un rol de tu cuenta, con una condición sobre la organización y el repositorio concretos:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
"StringLike": { "token.actions.githubusercontent.com:sub": "repo:mercadofresco/mercadofresco-tienda:*" }
}
}]
}La condición sub impide que cualquier repositorio del mundo asuma tu rol. Un StringLike con repo:*
es un agujero de seguridad grave que se ha visto en producción más de una vez.
Políticas IAM: quién puede empujar a main
En CodeCommit la protección de rama se expresa como política de IAM, mediante la condición
codecommit:References. Esta política deniega empujar directamente a main:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenegarEscrituraDirectaEnMain",
"Effect": "Deny",
"Action": ["codecommit:GitPush", "codecommit:DeleteBranch",
"codecommit:PutFile", "codecommit:DeleteFile"],
"Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
"Condition": {
"StringEqualsIfExists": { "codecommit:References": ["refs/heads/main"] },
"Null": { "codecommit:References": "false" }
}
}]
}Tres detalles que se copian mal a menudo. StringEqualsIfExists limita la denegación a lo que afecta
a refs/heads/main: un push a funcionalidad/franja-horaria no encaja y se permite.
Null: {"codecommit:References": "false"} significa «aplica solo si la clave está presente»; sin ella
denegarías también operaciones sin referencia de rama, bloqueando cosas que no querías. Y PutFile y
DeleteFile cierran la puerta de la consola web, que permite editar sin pasar por Git y es el agujero
que más se olvida —en cambio, no deniegues las acciones MergeBranchesBy*: son las que ejecuta la
fusión de una PR, y bloquearlas impide cerrar incluso las PR aprobadas.
Deny gana siempre sobre Allow, como vimos en 04-01: esto es efectivo aunque Luis tenga adjunta
AWSCodeCommitPowerUser. En GitHub el mecanismo cambia —reglas de protección de rama o rulesets— pero
la intención es idéntica.
Estrategia de ramas: trunk-based frente a GitFlow
| Aspecto | Trunk-based | GitFlow |
|---|---|---|
| Ramas de larga vida | Una: main |
main + develop + release/* + hotfix/* |
| Vida de una rama de trabajo | Horas o 1-2 días | Días o semanas |
| Conflictos de fusión | Pequeños y frecuentes | Grandes y dolorosos |
| Complejidad mental | Baja | Alta; hay que documentarla |
| Entrega continua | Es su premisa | Mal: la rama de release estorba |
| Funcionalidad a medias | Se oculta con banderas | Se aísla en su rama |
| Versiones simultáneas | Difícil | Es lo que resuelve bien |
GitFlow se diseñó en 2010 para software que se publica en versiones: un instalador, una biblioteca, un producto que el cliente actualiza cuando quiere. Ahí tiene sentido mantener la 1.4 mientras desarrollas la 1.5. Su propio autor publicó después una nota diciendo que si entregas de forma continua —una web, un SaaS— probablemente no lo necesites.
MercadoFresco es una tienda web: solo existe una versión, la que está en producción ahora. Una rama
release/* no aporta nada y una develop que vive semanas garantiza fusiones dolorosas entre tres
personas que tocan los mismos ficheros.
Decisión de Marta: trunk-based con matices. main siempre desplegable, ramas de funcionalidad cortas,
y una rama desarrollo que existe por una razón temporal: hasta que el pipeline de 08-04 esté maduro y las
pruebas den confianza, quiere un punto donde los cambios convivan un día antes de llegar a main. Es un
compromiso consciente con fecha de caducidad declarada: un andamio, no una catedral.
El flujo de ramas de MercadoFresco
gitGraph
commit id: "v1.4.0"
branch desarrollo
checkout desarrollo
commit id: "sync"
branch funcionalidad/franja-horaria
checkout funcionalidad/franja-horaria
commit id: "modelo pedido"
commit id: "API + pruebas"
checkout desarrollo
merge funcionalidad/franja-horaria id: "PR 42"
commit id: "humo dev OK"
checkout main
merge desarrollo tag: "v1.5.0"
branch correccion/iva-canarias
checkout correccion/iva-canarias
commit id: "fix IVA"
checkout main
merge correccion/iva-canarias tag: "v1.5.1"
checkout desarrollo
merge main id: "sync fix"
Las reglas escritas, que viven en el CONTRIBUTING.md:
| Rama | Origen | Destino | Vida | Quién fusiona |
|---|---|---|---|---|
main |
— | — | Permanente | Nadie directamente |
desarrollo |
main |
main vía PR |
Permanente | Marta, tras revisión |
funcionalidad/<n> |
desarrollo |
desarrollo vía PR |
Máx. 3 días | Autor, 1 aprobación |
correccion/<n> |
main |
main y desarrollo |
Horas | Autor, 1 aprobación |
experimento/<n> |
Cualquiera | Se borra | Días | No se fusiona |
El límite de tres días no es burocracia: una rama que vive dos semanas diverge tanto que fusionarla
deja de ser una fusión y pasa a ser una reintegración; si un cambio no cabe en tres días, se parte. Las
correcciones urgentes salen de main, no de desarrollo, que arrastraría cambios a medias a
producción; y hay que fusionarlas también hacia desarrollo, o el fallo reaparecerá en la siguiente
entrega. Y las ramas se borran al fusionar: un repositorio con 140 ramas muertas es un repositorio
donde nadie encuentra nada.
Pull requests: revisión, aprobación y fusión
Un pull request es la propuesta de fusionar una rama en otra, con un espacio para discutirla antes de que ocurra. Es lo que convierte «Luis subió algo» en «el equipo aceptó un cambio».
aws codecommit create-pull-request \
--title "Anadir franja horaria de entrega al pedido" \
--description "$(cat <<'EOF'
## Que cambia
Anade el campo `franja_entrega` (manana|tarde|24h) al pedido y lo propaga al evento
`PedidoConfirmado` como atributo opcional.
## Por que
Peticion de Sara: el 31 % de las incidencias de reparto son por ausencia del cliente.
## Como probarlo
1. POST /pedidos con "franja_entrega": "tarde"
2. Comprobar el mensaje en cola-mercadofresco-pedidos
3. Comprobar que un pedido SIN el campo sigue funcionando (compatibilidad hacia atras)
## Riesgos
Cambio de esquema en Aurora: columna NULLABLE, sin valor por defecto, sin indice nuevo.
Consumidores antiguos ignoran el atributo. Ver 08-05.
EOF
)" \
--targets repositoryName=mercadofresco-tienda,sourceReference=funcionalidad/franja-horaria,destinationReference=desarrollo \
--profile mercadofresco-dev --region eu-west-1Esa descripción es una plantilla real y merece copiarse. Una PR sin descripción obliga al revisor a reconstruir la intención leyendo el diff, la forma más lenta y menos fiable de entender un cambio. Las cuatro secciones se responden en tres minutos y ahorran veinte al revisor.
Los comentarios pueden ser generales o anclarse a una línea concreta del diff con
post-comment-for-pull-request y su parámetro --location filePath=app/pedidos.py,filePosition=147, que
es donde ocurre la revisión útil: sobre la línea, no en un correo aparte.
Las tres estrategias de fusión, idénticas en cualquier plataforma:
| Estrategia | Qué hace | Historia resultante | Cuándo |
|---|---|---|---|
| Fast-forward | Mueve el puntero, sin commit nuevo | Lineal, conserva todos los commits | Rama de 1 commit ya rebasada |
| Squash | Aplasta la rama en un commit | Lineal y limpia: 1 PR = 1 commit | Por defecto en funcionalidades |
| Three-way | Commit de fusión con dos padres | Ramificada, conserva el detalle | Entre ramas de larga vida |
MercadoFresco usa squash para las ramas de funcionalidad y three-way para desarrollo → main.
El razonamiento: los quince commits de «wip», «arreglo el test» y «ahora sí» no aportan nada dentro de
seis meses, y un git log de main donde cada línea es un cambio completo y descrito es una herramienta
de diagnóstico excelente durante un incidente; en cambio, al pasar desarrollo a main interesa conservar
los commits individuales para poder revertir uno solo. Una consecuencia del squash que sorprende la
primera vez: el commit resultante es nuevo y no comparte identificador con los de la rama, así que la
rama queda «no fusionada» a ojos de Git aunque su contenido esté dentro. Bórrala igualmente.
aws codecommit merge-pull-request-by-squash \
--pull-request-id 42 --repository-name mercadofresco-tienda \
--commit-message "Anadir franja horaria de entrega al pedido (#42)" \
--profile mercadofresco-dev --region eu-west-1Reglas de aprobación y protección de main
Una PR que cualquiera fusiona sin que nadie la mire es un formalismo. Lo que la convierte en un control real es una plantilla de regla de aprobación: una política que se aplica automáticamente a las PR que cumplan un criterio y exige aprobaciones de un conjunto de personas.
aws codecommit create-approval-rule-template \
--approval-rule-template-name plantilla-mf-main-requiere-aprobacion \
--approval-rule-template-description "Toda PR hacia main necesita 1 aprobacion del equipo" \
--approval-rule-template-content '{
"Version": "2018-11-08",
"DestinationReferences": ["refs/heads/main"],
"Statements": [{
"Type": "Approvers",
"NumberOfApprovalsNeeded": 1,
"ApprovalPoolMembers": ["arn:aws:sts::111122223333:assumed-role/rol-mercadofresco-desarrollo/*"]
}]
}' \
--profile mercadofresco-dev --region eu-west-1
# La plantilla no hace nada hasta que se asocia a un repositorio
aws codecommit associate-approval-rule-template-with-repository \
--approval-rule-template-name plantilla-mf-main-requiere-aprobacion \
--repository-name mercadofresco-tienda \
--profile mercadofresco-dev --region eu-west-1DestinationReferences limita la regla a las PR hacia main: agilidad en el día a día, rigor en la
puerta de producción. ApprovalPoolMembers admite el patrón assumed-role/<rol>/*, que es el correcto
cuando el equipo entra con SSO y las identidades son sesiones de rol, no usuarios IAM.
NumberOfApprovalsNeeded: 1 es lo razonable para tres personas; con dos, el proceso se bloquearía en
cuanto alguien se fuera de vacaciones. Y el autor no puede aprobar su propia PR: CodeCommit lo impide
de forma nativa y en GitHub hay que activarlo, pero es la mitad del valor del mecanismo.
La segunda condición —que la construcción esté en verde— no se puede expresar aquí, porque estas reglas solo cuentan aprobaciones humanas. Hay dos caminos: que CodeBuild apruebe la PR programáticamente al terminar bien, o que la comprobación viva en el proveedor Git como required status check. Esta pieza se cierra en 08-04, cuando el pipeline exista y tenga algo que decir sobre la calidad del cambio.
Convenciones: commits, versión semántica y etiquetas
Las convenciones parecen cosmética hasta el día en que un pipeline las lee y decide con ellas.
El formato es tipo(ámbito): descripción en imperativo — feat(pedidos): anadir franja horaria de entrega, fix(pagos): evitar doble cobro cuando la pasarela responde 504, chore(deps): subir boto3:
| Tipo | Significado | Efecto en la versión |
|---|---|---|
feat |
Funcionalidad nueva | MINOR |
fix, perf |
Corrección o mejora | PATCH |
refactor, docs, test, chore |
Sin cambio funcional | Ninguno |
Cualquiera con BREAKING CHANGE: en el cuerpo |
Cambio incompatible | MAJOR |
Versionado semántico (MAJOR.MINOR.PATCH) es el otro lado de la misma moneda: v1.5.0 frente a
v1.4.3 dice qué esperar. En una tienda donde el único cliente es tu frontal, el MAJOR importa poco; en
cuanto publicas una API que consume el ERP del almacén, importa mucho.
git tag -a v1.5.0 -m "Franja horaria de entrega + cache de catalogo"
git push origin v1.5.0
git log --oneline v1.4.3..v1.5.0El valor operativo es que el artefacto desplegado lleva ese número. Cuando
mercadofresco-alb-latencia-alta salte a las 19:20 de un viernes, la primera pregunta será «¿qué versión
hay en producción?» y la segunda «¿qué entró desde la anterior?». Con etiquetas se responden en diez
segundos; sin ellas, reconstruyendo fechas y esperanzas.
El .gitignore y por qué nunca se sube un .env
Un .gitignore correcto no es higiene: es control de fugas.
# Entorno y secretos - NUNCA en el repositorio
.env
.env.*
!.env.example
*.pem
*.key
credenciales*.json
# Python y artefactos de construccion
__pycache__/
.venv/
.pytest_cache/
.coverage
dist/
node_modules/
# Datos locales: pueden contener datos personales
*.csv
*.sql
volcado_aurora_*.dump
!datos/ejemplo_anonimo.csvLa línea !.env.example desexcluye una plantilla de variables sin valores, que sí debe estar para que
alguien nuevo sepa qué configuración hace falta. Y las últimas líneas evitan que un volcado de Aurora con
pedidos reales acabe en el repositorio: eso es un incidente de protección de datos, no un descuido.
Por qué el .env es el peor de todos. En el portátil de Luis hay uno con la contraseña de mfadmin,
la clave de la pasarela de pagos y el token del ERP. Si entra una sola vez en el repositorio:
queda para siempre en la historia —borrarlo en el commit siguiente no lo elimina, se recupera con
git show, y limpiarlo de verdad exige reescribir la historia y forzar el push rompiendo todos los
clones—, se replica en cada clon, caché de CI y copia del proveedor, y la única respuesta válida es
rotar las credenciales: si el secreto se expuso está comprometido, y da igual cuánto duró la exposición.
La solución la construimos en 04-03 y aquí solo hay que usarla: contraseñas en
mercadofresco/produccion/rds/mfadmin con rotación automática cada 30 días, y configuración no sensible
en Parameter Store bajo /mercadofresco/produccion/. La aplicación las lee al arrancar con
rol-mercadofresco-tienda. En el repositorio no hay ni un valor secreto, solo el nombre del secreto,
que no es sensible. Y la rotación de 04-03 gana aquí un valor que entonces no era evidente: pone fecha de
caducidad automática al daño de una fuga. En 08-02 añadiremos una red más: un análisis que falla la
construcción si detecta algo con pinta de credencial.
Notificaciones y disparadores
Un repositorio que nadie mira es un repositorio donde las PR esperan cuatro días.
| Mecanismo | Qué es | Casos típicos |
|---|---|---|
| Disparadores | Configuración del repositorio: en push, invoca SNS o Lambda |
Automatismos ligados al push |
| Notificaciones | Regla sobre eventos del servicio hacia SNS o un chat | Avisar a personas de PR y comentarios |
| EventBridge | Todos los eventos de CodeCommit llegan al bus default |
Enrutado por contenido |
aws codecommit put-repository-triggers \
--repository-name mercadofresco-tienda \
--triggers '[{"name":"aviso-push-main",
"destinationArn":"arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
"branches":["main"],"events":["updateReference"]}]' \
--profile mercadofresco-dev --region eu-west-1Y una regla de EventBridge que aprovecha todo lo de 07-03, con source: ["aws.codecommit"],
detail-type: ["CodeCommit Pull Request State Change"] y un filtro por detail.event para reaccionar a
pullRequestCreated. Si lo que quieres es avisar a personas, no lo montes con un disparador: para eso
están las notificaciones, que traen el formato hecho y se integran con Slack sin escribir código.
Hay un antipatrón que conviene nombrar: usar un disparador para lanzar el despliegue. Es tentador y
funciona el primer día, pero no hay construcción, ni pruebas, ni aprobación, ni artefacto versionado, ni
forma de reintentar. Es el scp de Luis con más pasos. Lo que debe reaccionar al push es un pipeline,
y eso es 08-04.
Migrar el repositorio de Luis
El repositorio local tiene 1.847 commits, tres ramas y un .env rastreado desde el segundo commit.
Paso 1: inventario. git branch -a, git log --oneline | wc -l y git count-objects -vH para saber
qué hay y cuánto pesa.
Paso 2: buscar secretos en la historia antes de subir nada. No es opcional.
git log --all --full-history --oneline -- .env
git log --all -p -S "AKIA" --oneline # clave de acceso de AWS
git log --all -p -S "BEGIN RSA PRIVATE KEY" --onelineLuis encuentra .env en 312 commits. Dos caminos: reescribir la historia con git filter-repo si el
repositorio va a ser público o compartido con terceros, o subir y rotar si es privado y el equipo es
pequeño. MercadoFresco elige lo segundo, pero la rotación es innegociable y se hace antes del push, con
aws secretsmanager rotate-secret --secret-id mercadofresco/produccion/rds/mfadmin y regenerando en el
panel del proveedor la clave de la pasarela y el token del ERP.
Paso 3: limpiar el árbol de trabajo.
git rm --cached .env # deja de rastrearlo, no lo borra del disco
printf '.env\n.env.*\n!.env.example\n' >> .gitignore
git add .gitignore && git commit -m "chore(seguridad): dejar de rastrear .env y anadir .gitignore"Paso 4 y 5: remoto y subida, con git remote add origin [email protected]:mercadofresco/mercadofresco-tienda.git
seguido de git push --mirror origin.
--mirror sube todas las referencias —ramas, etiquetas y notas— tal como están en local, que es lo
que quieres en una migración inicial. Dos advertencias: es destructivo en el destino (borra en el
remoto lo que no exista en local, así que solo contra un repositorio vacío y solo la primera vez), y
después no se vuelve a usar nunca; el día a día es git push origin <rama>. Si prefieres control fino,
git push origin --all y git push origin --tags.
Paso 6: verificar clonando en limpio, el paso que se salta la gente y luego lamenta. Clona en /tmp,
cuenta los commits —deben ser los 1.847—, revisa ramas y etiquetas, y comprueba que .env no está.
Pasos 7 y 8: crear desarrollo, proteger main en el proveedor (PR obligatoria, una aprobación, sin
force push), y que Marta y Sara clonen. El repositorio deja de estar en un portátil el día en que hay al
menos dos copias más y alguien ha comprobado que funcionan.
Coste y limpieza
| Concepto | Coste en MercadoFresco |
|---|---|
| CodeCommit, 3 usuarios y 240 MB | 0 USD (5 usuarios y 50 GB incluidos) |
| CodeConnections y GitHub con repositorios privados | 0 USD |
| Notificaciones vía SNS | Céntimos |
| Total del gobierno del código | ≈ 0 USD/mes |
La barrera para adoptar esto nunca es económica: es el hábito. Lo que cuesta dinero empieza en 08-02.
# IRREVERSIBLE y sin papelera: clona con --mirror a un disco antes de ejecutarlo
aws codecommit delete-repository --repository-name pruebas-borrar \
--profile mercadofresco-dev --region eu-west-1Errores Comunes y Consejos
Elegir CodeCommit para un proyecto nuevo. No admite nuevos clientes y no recibe funcionalidades. Aprende sus conceptos, despliega en GitHub o GitLab.
Olvidar credential.UseHttpPath true. Produce un 403 al clonar de CodeCommit que parece un problema
de permisos IAM y no lo es. Es el primer sitio donde mirar.
Dejar la conexión de CodeConnections en PENDING. El recurso existe, la CLI lo lista y todo lo que
dependa de él falla con un mensaje confuso. La autorización en el navegador es obligatoria y manual.
Guardar claves de larga duración como secretos de GitHub. Usa OIDC con una condición sub que nombre
tu organización y repositorio; un repo:* es un agujero grave.
Denegar las acciones MergeBranchesBy* para «proteger main». Nadie puede fusionar ni siquiera una PR
aprobada, y a los dos días alguien pide quitar la política entera. Protege la escritura directa, no la
fusión gobernada.
Ramas de funcionalidad que viven tres semanas. Cada día extra multiplica el coste de fusión. Si el cambio es grande, pártelo en incrementos compatibles hacia atrás. Y adoptar GitFlow por costumbre en un producto de entrega continua añade tres ramas de larga vida a cambio de un problema que no tienes.
Subir un .env «solo un momento para probar». Queda en la historia para siempre y la única respuesta
correcta es rotar. No hay atajo.
Fusionar con squash y luego intentar revertir un commit intermedio. Ya no existe: la rama entera es un solo commit. Es el precio consciente del squash.
Consejo: escribe el CONTRIBUTING.md el primer día, y usa .github/pull_request_template.md para
precargar las cuatro secciones. Media página ahorra las mismas cinco conversaciones cada mes, y un revisor
con contexto revisa el doble de rápido.
Consejo: activa el borrado automático de ramas al fusionar, y etiqueta cada versión que llega a producción sin excepciones: el día del incidente, la diferencia entre diez segundos y veinte minutos de diagnóstico está en esa etiqueta.
Ejercicios
Ejercicio 1: elegir proveedor con criterio
Para cada escenario, elige entre CodeCommit, GitHub, GitLab autoalojado o Bitbucket y justifícalo en tres frases nombrando el criterio decisivo:
(a) Empresa pública española, 40 desarrolladores. El pliego exige que el código resida en infraestructura bajo su control dentro de la UE con auditoría completa de accesos. Tienen cuenta AWS desde 2019 con CodeCommit habilitado y 60 repositorios.
(b) Startup de 6 personas que arranca hoy. Todos vienen de GitHub, quieren escaneo de dependencias sin montarlo y desplegarán en AWS con Lambda y S3.
(c) Equipo de 25 personas en una empresa que usa Jira para todo, con auditorías trimestrales que cruzan incidencias con commits.
Ejercicio 2: la política que bloqueó al equipo
Marta aplica esto al grupo desarrolladores-mercadofresco para proteger main:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": "codecommit:GitPush",
"Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
"Condition": {
"StringNotEqualsIfExists": { "codecommit:References": ["refs/heads/main"] }
}
}]
}Al día siguiente Luis no puede empujar a su rama de funcionalidad, pero sí puede empujar a main.
Responde: (a) qué hace exactamente y por qué produce ese efecto; (b) escríbela correctamente;
(c) qué le falta para proteger main de verdad; (d) qué comprobación habría detectado el fallo
antes de aplicarla.
Ejercicio 3: la migración con sorpresa
Al ejecutar el paso 2, Luis encuentra: un .env en 312 commits con la contraseña de mfadmin y la clave
de la pasarela; un clientes_octubre.csv de 18 MB con 4.200 nombres, direcciones y teléfonos reales,
añadido hace nueve meses; y una rama experimento/pagos-bizum de hace un año sin fusionar.
Responde: (a) qué haces con cada hallazgo y en qué orden; (b) cuál hace que reescribir la historia deje de ser opcional, y por qué; (c) el comando exacto y qué avisas al equipo antes; (d) a quién hay que informar dentro de la empresa por el segundo hallazgo.
Soluciones
Solución 1
(a) CodeCommit, y es el único escenario del ejercicio donde tiene sentido. El criterio decisivo es el
primero de la lista: la exigencia legal de residencia y control del código, que anula a los demás. La
cuenta es anterior a julio de 2024, así que el servicio está disponible; el código vive en su cuenta de
AWS en eu-west-1, cifrado con su clave de KMS, y CloudTrail da la auditoría que pide el pliego. La
alternativa sería GitLab autoalojado en su VPC, que cumple igual el requisito pero les obliga a operar
y parchear el servidor: con 60 repositorios ya migrados no compensa. Lo que sí deben hacer es planificar
la salida a medio plazo, porque están sobre un servicio sin evolución.
(b) GitHub. Criterio decisivo: dónde está ya el equipo, reforzado por el ecosistema. Seis personas que ya conocen la herramienta producen el primer día; Dependabot da el escaneo de dependencias sin montar nada; los repositorios privados son gratuitos a ese tamaño. Para desplegar en AWS, OIDC más CodeConnections cubre todo sin almacenar claves, y no hay restricción de residencia que lo complique.
(c) Bitbucket. Criterio decisivo: la integración con el resto de herramientas. La vinculación nativa entre commits, ramas e incidencias de Jira es exactamente lo que consume su auditoría trimestral; con GitHub habría que montar y mantener esa integración. GitLab sería superior en CI/CD, pero aquí el ahorro está en el proceso, no en el pipeline; y con 25 personas Bitbucket es además el más barato.
Solución 2
(a) La condición está invertida. StringNotEqualsIfExists con refs/heads/main significa «deniega
cuando la referencia no sea main». El resultado es literalmente el contrario del deseado: prohíbe
empujar a cualquier rama excepto main y deja main completamente abierta. Es un error de una sola
palabra —Not— con consecuencias opuestas a la intención, y por eso las denegaciones con condiciones
negadas hay que leerlas dos veces en voz alta.
(b) La correcta usa StringEqualsIfExists para acotar la denegación a main, añade Null para que no
afecte a operaciones sin referencia, y amplía las acciones:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenegarEscrituraDirectaEnMain",
"Effect": "Deny",
"Action": ["codecommit:GitPush", "codecommit:DeleteBranch",
"codecommit:PutFile", "codecommit:DeleteFile"],
"Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
"Condition": {
"StringEqualsIfExists": { "codecommit:References": ["refs/heads/main"] },
"Null": { "codecommit:References": "false" }
}
}]
}(c) DeleteBranch impide borrar main entera; PutFile y DeleteFile cierran la edición desde la
consola web, que salta Git y es el agujero más olvidado. Un push --force ya queda cubierto porque IAM no
lo distingue de un GitPush. Pero falta lo que ninguna política puede dar: una regla de aprobación
asociada al repositorio que exija al menos una aprobación en las PR hacia main, más —cuando exista— la
comprobación de que la construcción está en verde. Sin eso, la política solo obliga a pasar por una PR; no
obliga a que la PR sea buena.
(d) El simulador de políticas de IAM de 04-01, o mejor: aplicarla primero a un repositorio de pruebas
y ejecutar los cuatro casos —push a rama de funcionalidad, push a main, borrado de rama, edición desde
la consola— comprobando que dos pasan y dos fallan. Una denegación con condiciones nunca debe
estrenarse en el repositorio real: el modo de fallo es que bloquea al equipo o, peor, que no bloquea nada
y nadie se entera.
Solución 3
(a) El orden importa porque el primer paso hace irrelevante el tiempo de los demás. Uno: rotar
inmediatamente los secretos del .env —contraseña de mfadmin desde Secrets Manager, clave de la
pasarela regenerada en el panel del proveedor— antes de tocar el repositorio; están comprometidos desde
hace catorce meses y el borrado no los descompromete. Dos: el CSV, decidir la reescritura y no subir
nada mientras tanto. Tres: la rama experimento/pagos-bizum no se migra; si tiene valor se exporta
con git format-patch y se guarda fuera. Migrar ramas muertas es arrastrar ruido.
(b) El CSV. El .env es grave pero acotado: se resuelve rotando, porque un secreto rotado ya no vale
nada aunque siga en la historia. Un fichero con 4.200 nombres, direcciones y teléfonos reales no se
puede rotar. Es dato personal bajo el RGPD, y el principio de minimización más el derecho de supresión
hacen inaceptable conservarlo indefinidamente en un repositorio de código, donde se replicará en cada
clon, cada caché de CI y cada copia del proveedor. Aquí reescribir deja de ser una opción técnica y pasa a
ser una obligación.
(c)
cp -r ~/proyectos/tienda ~/proyectos/tienda-copia-seguridad # filter-repo es irreversible
cd ~/proyectos/tienda
git filter-repo --path datos/clientes_octubre.csv --invert-paths
git filter-repo --path .env --invert-paths # de paso, ya que se reescribegit filter-repo cambia el hash de los commits afectados y de todos sus descendientes, así que hay que
avisar de que cualquier clon existente queda inservible y debe reclonarse: fusionar un clon antiguo con la
historia reescrita crea un desastre de commits duplicados. Aquí la migración aún no ha ocurrido y solo
existe el clon de Luis, así que el impacto es nulo —razón excelente para limpiar antes de subir—. Si ya
estuviera en el remoto, además haría falta push --force, borrar ramas y etiquetas obsoletas del remoto y
pedir al proveedor que purgue los objetos inalcanzables.
(d) Al responsable de protección de datos (DPO o equivalente). Datos personales de 4.200 clientes fuera
de los sistemas autorizados durante nueve meses es un tratamiento no previsto que hay que documentar y
evaluar. Puede que no sea una brecha notificable a la AEPD —el repositorio era privado y local—, pero esa
valoración no la hace el equipo técnico: se aporta la información completa (qué datos, cuánto tiempo,
quién tuvo acceso, qué se ha hecho) y decide quien tiene la responsabilidad. Es el mismo criterio que
aplicamos en 06-03 con el clon aurora-mf-pruebas-luis.
Conclusión
El código de MercadoFresco ha salido del portátil de Luis. Ya no es un git init con una copia USB de
septiembre: es un repositorio remoto, replicado, cifrado, con permisos de IAM en lugar de usuarios de
sistema y con al menos tres copias vivas en el equipo. Los cuatro primeros riesgos del inventario de Marta
están cerrados por el simple hecho de haber subido el código.
Pero lo importante de esta lección no es el alojamiento, es el gobierno. Sabes que CodeCommit ya no
admite nuevos clientes y por qué eso no invalida aprenderlo —te lo vas a encontrar—, y que la respuesta
actual es GitHub o GitLab conectados con CodeConnections, con su autorización manual en el navegador
que tantas acciones de origen rompe cuando se olvida. Tienes el criterio de elección en orden: primero la
exigencia legal si existe, luego dónde está ya tu equipo, luego dónde quieres que viva el CI/CD, y por
último el ecosistema. Sabes autenticarte con el ayudante de credenciales —con credential.UseHttpPath true, que evita el 403 misterioso— y con OIDC en lugar de claves de larga duración para las máquinas.
Y sabes escribir una política que protege main sin bloquear al equipo, y por qué la condición Null
acompaña siempre a StringEqualsIfExists.
Has elegido trunk-based con una rama desarrollo declarada como andamio temporal, porque una tienda
web solo tiene una versión —la que está en producción ahora— y GitFlow resuelve un problema que
MercadoFresco no tiene. Sabes qué es una PR que sirve —qué, por qué, cómo probarlo, riesgos—, cuándo usar
squash y cuándo three-way, y cómo una plantilla de regla de aprobación convierte la revisión en un
control real gracias a que el autor no puede aprobarse a sí mismo. Tienes las convenciones que un pipeline
leerá más adelante: commits convencionales, versión semántica y etiquetas que responden en diez
segundos a «qué hay en producción». Y tienes la regla que no admite excepciones: el .env nunca sube,
porque queda en la historia para siempre y la única respuesta válida es rotar.
Ahora hay un repositorio con reglas. Lo que no hay es ninguna garantía de que lo que entra en él
funcione. La aprobación de la PR significa que una persona ha leído el diff un martes por la tarde, lo
cual es mucho más que nada y bastante menos que suficiente. Nadie ha ejecutado las pruebas: Luis las lanza
en su portátil «casi siempre», y la última vez que falló una decidió que era cosa de su entorno. Nadie ha
comprobado que las dependencias no tengan vulnerabilidades conocidas, ni que no se haya colado una clave
en el código, ni que el proyecto siquiera arranque en una máquina limpia. La frase «funciona en mi
portátil» sigue siendo un argumento admisible en MercadoFresco, y todavía no existe ningún artefacto: lo
que se despliega es una carpeta copiada con scp.
En 08-02, «AWS CodeBuild», eso se acaba. Veremos qué es la integración continua y por qué convierte
«funciona en mi portátil» en una frase sin valor; la anatomía de un proyecto de construcción con su
entorno, su rol de servicio, su caché y sus artefactos; un buildspec.yml completo y comentado con las
fases install, pre_build, build y post_build; cómo inyectar secretos desde Parameter Store y
Secrets Manager sin que aparezcan en los registros; las pruebas unitarias con pytest y los informes de
cobertura como puerta de calidad y no como informe que nadie lee; el escaneo de dependencias y de
credenciales filtradas; la construcción del paquete que irá al bucket mercadofresco-artefactos; y cómo
ejecutar la construcción dentro de vpc-mercadofresco para probar contra el clon
aurora-mf-pruebas-luis, con la advertencia de coste que eso trae.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
