Hi ha una pregunta que aquest curs encara no ha fet i convé fer-la sense embuts: on és el codi de MercadoFresco? La resposta honesta és que és al portàtil del Luis, a ~/proyectos/tienda, amb un git init de fa catorze mesos i 1.847 commits que ningú més no ha vist. Hi ha una còpia comprimida en un USB de setembre. La Marta té una carpeta tienda-v2-FINAL i no sap si és anterior o posterior. I quan el Luis desplega, fa scp de la carpeta sencera a una instància de l'ASG asg-mercadofresco-tienda i reinicia el servei a mà.

Tot el que s'ha construït en set mòduls descansa sobre un codi font que viu en un sol disc dur sense còpia fiable. Aquest mòdul ho arregla i comença pel principi: on viu el codi i com es governen els canvis.

Avís de cost. CodeCommit cobra 1 USD per usuari actiu i mes a partir del sisè, amb 50 GB i 10.000 peticions Git incloses: per a l'equip de tres de MercadoFresco, zero. Les connexions de CodeConnections cap a GitHub o GitLab també són gratuïtes; es paga l'ús aigües avall, que veurem a 08-02 i 08-04. Totes les dades i credencials d'aquesta lliçó són fictícies.

Contingut

  1. El punt de partida: un repositori en un portàtil
  2. Què aporta un repositori Git gestionat
  3. La situació de CodeCommit, sense adorns
  4. Comparativa: CodeCommit, GitHub, GitLab i Bitbucket
  5. CodeConnections: el pont entre AWS i el teu proveïdor Git
  6. Els repositoris de MercadoFresco
  7. Autenticació: HTTPS, SSH, l'ajudant de la CLI i rols
  8. Polítiques IAM: qui pot empènyer a main
  9. Estratègia de branques: trunk-based enfront de GitFlow
  10. El flux de branques de MercadoFresco
  11. Pull requests: revisió, aprovació i fusió
  12. Regles d'aprovació i protecció de main
  13. Convencions: commits, versió semàntica i etiquetes
  14. El .gitignore i per què mai no es puja un .env
  15. Notificacions i disparadors
  16. Migrar el repositori del Luis
  17. Cost i neteja
  18. Errors habituals i consells
  19. Exercicis
  20. Conclusió

El punt de partida: un repositori en un portàtil

La Marta va fer un inventari del risc abans de tocar res:

Risc Situació actual Conseqüència
Pèrdua del portàtil Còpia USB de fa 5 mesos 5 mesos de feina perduts
Dues persones toquen el mateix S'avisen pel xat Sobreescriptura silenciosa
Saber què hi ha en producció «El que vaig pujar divendres» Ningú no reprodueix una fallada
Tornar a la versió anterior Buscar a la paperera Minuts o hores de caiguda
Revisar abans que entri No es fa Errors vistos en producció
Secrets al codi Hi ha un .env amb la clau d'RDS Fuita en compartir el repositori

Els quatre primers els resol qualsevol repositori remot. Els tres últims necessiten a més govern: regles sobre qui pot fer què i en quines condicions entra un canvi. Aquesta distinció és la lliçó sencera: allotjar el codi és la part fàcil.

Què aporta un repositori Git gestionat

Git és distribuït i res no obliga a tenir servidor, però tot equip designa una còpia com la de referència, i aquesta còpia ha d'estar disponible, tenir còpia de seguretat i controlar qui hi escriu.

Aspecte Servidor propi (EC2 + Gitea/GitLab CE) Gestionat
Durabilitat La de l'EBS; les còpies les fas tu Replicat entre AZ, sense acció teva
Disponibilitat Una instància = un únic punt de fallada Acord de servei del proveïdor
Xifratge en repòs El configures tu Automàtic; a CodeCommit, amb KMS
Identitat Usuaris de sistema o LDAP propi IAM (CodeCommit) o OIDC/SAML
Pedaços i CVE Teus Del proveïdor
Cost real Instància + EBS + el teu temps Per usuari o inclòs
Auditoria Registres del servidor, si els deses CloudTrail o registre del proveïdor

Per a tres persones, mantenir un servidor Git és temps que no va al producte: MercadoFresco no el munta. Mereix un paràgraf el xifratge en repòs de CodeCommit, que connecta amb el mòdul 4: xifra sempre, sense opció de desactivar-ho, i pot fer-ho amb una clau del client. Aquí això vol dir reutilitzar alias/mercadofresco-datos, la mateixa clau que ja xifra Aurora i els buckets: una clau, una política, una rotació i un únic lloc on revocar l'accés. És l'avantatge concret de la integració: no hi ha un segon sistema de xifratge per governar.

La situació de CodeCommit, sense adorns

Des del juliol del 2024, AWS no admet nous clients a CodeCommit. Si el teu compte ja el feia servir abans, continua funcionant amb suport i correccions de seguretat. Si és nou, la consola no et deixarà crear repositoris. No hi ha data de tancament anunciada, però tampoc funcionalitats noves: la recomanació oficial per a projectes nous és un proveïdor Git extern connectat amb CodeConnections.

Tres conseqüències pràctiques: te'l trobaràs a empreses que el van adoptar entre el 2015 i el 2024 amb centenars de repositoris, així que saber-lo operar és útil; no l'escullis per a un projecte nou, ni encara que el teu compte antic t'ho permeti, perquè estaries construint sobre un servei sense evolució; i el que importa aquí no és el servei, són els conceptes —branques, pull requests, regles d'aprovació, protecció de branca, convencions de commit i de versió són idèntics a GitHub, GitLab o Bitbucket, i són el que consumiran 08-02, 08-03 i 08-04.

La Marta pren la decisió que prendries avui: el codi va a GitHub, i AWS s'hi connecta amb CodeConnections. La lliçó continua explicant CodeCommit perquè te'l trobaràs i perquè el seu model de permisos —IAM pur— és didàcticament molt clar.

Comparativa: CodeCommit, GitHub, GitLab i Bitbucket

Criteri CodeCommit GitHub GitLab Bitbucket
Nous clients No admesos
Identitat IAM natiu Pròpia; SSO de pagament Pròpia; SSO Atlassian
Revisió de codi Funcional però pobra La millor del mercat Molt completa Correcta
CI/CD propi No (fa servir CodeBuild) GitHub Actions GitLab CI Bitbucket Pipelines
Integració AWS Nativa total CodeConnections + OIDC CodeConnections + OIDC CodeConnections
Autoallotjable No Enterprise Server Sí, self-managed Data Center
Ecosistema Nul Enorme Ampli i integrat Lligat a Jira
Residència del codi El teu compte i regió AWS Servidors de GitHub GitLab o el teu servidor Atlassian
Cost 3 / 30 persones 0 / ~25 USD mes 0 / ~120 USD mes 0 / ~870 USD mes 0 / ~90 USD mes

Criteri d'elecció, en l'ordre en què cal aplicar-lo:

  1. Hi ha exigència legal o contractual que el codi no surti del teu compte o de la UE? Si és innegociable, les teves opcions són CodeCommit (només si ja el tenies) o GitLab autoallotjat. És l'únic criteri que anul·la tots els altres.
  2. On és ja el teu equip? Moure gent que fa anys que treballa amb GitHub té un cost de fricció diari que es paga per sempre. Aquest criteri decideix la majoria dels casos.
  3. Faràs servir el CI/CD del proveïdor o el d'AWS? Actions i GitLab CI són excel·lents i despleguen a AWS amb OIDC sense claus; si prefereixes construcció i desplegament dins d'AWS —amb els seus rols, les seves VPC i la seva factura—, CodeConnections dóna el millor de tots dos móns.
  4. Quant ecosistema necessites? En escaneig de dependències, incidències i taulers, GitHub i GitLab guanyen sense discussió.

MercadoFresco: l'equip ja treballa amb GitHub, no hi ha restricció sobre el codi font (les dades de clients sí que estan restringides a eu-west-1, però això és una altra cosa) i volen construcció i desplegament a AWS per reutilitzar rols i alarmes. GitHub + CodeConnections + CodeBuild/CodeDeploy/CodePipeline.

CodeConnections: el pont entre AWS i el teu proveïdor Git

CodeConnections —fins al 2023, AWS CodeStar Connections; veuràs tots dos noms a la documentació i en ARN antics— és un recurs d'AWS que representa una autorització OAuth entre el teu compte i una organització de GitHub, GitLab o Bitbucket. Un cop creada, CodePipeline o CodeBuild llegeixen el repositori i en reben els esdeveniments sense que tu hagis d'emmagatzemar cap token enlloc.

Es crea en dos passos, i el segon és obligatòriament manual:

# Pas 1: crear la connexio. Neix en PENDING i encara no serveix per a res.
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-1

Pas 2, a la consola i només a la consola. Developer Tools → Settings → Connections, seleccionar la connexió pendent i prémer «Update pending connection». S'obre el flux OAuth de GitHub, s'instal·la l'app AWS Connector for GitHub a l'organització i s'escullen els repositoris autoritzats. Aquest pas no es pot automatitzar per disseny: és una autorització humana en un proveïdor extern, no una crida a l'API d'AWS. Si una plantilla de CloudFormation «crea connexions llestes», crea el recurs i algú l'autoritza a mà una vegada.

Comprova amb aws codeconnections list-connections que l'estat és AVAILABLE abans de continuar: una connexió en PENDING és la causa número u que l'acció d'origen d'un pipeline falli amb un error de permisos poc descriptiu, i hi tornarem a 08-04.

Concedeix accés només als repositoris necessaris. GitHub ofereix donar accés a tota l'organització; tria «Only select repositories» i marca els dos de MercadoFresco. És el privilegi mínim de 04-01 aplicat fora d'AWS.

Els repositoris de MercadoFresco

Repositori Contingut Qui el toca Cadència
mercadofresco-tienda Web, API, consumidors de cues, codi de les Lambdes, proves, buildspec.yml, appspec.yml Luis Diverses vegades al dia
mercadofresco-infra Definició de la infraestructura, scripts d'operació, runbooks Marta Setmanal

Separar-los permet desplegar l'aplicació vint vegades al dia sense arrossegar canvis d'infraestructura i sotmetre un canvi de xarxa a una revisió més estricta. El contingut de mercadofresco-infra l'omplirem al mòdul 9: avui només existeix el repositori.

# A CodeCommit
aws codecommit create-repository \
  --repository-name mercadofresco-tienda \
  --repository-description "Aplicacio web, API i consumidors 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

# A GitHub (l'opcio triada)
gh repo create mercadofresco/mercadofresco-tienda --private
gh repo create mercadofresco/mercadofresco-infra --private

--kms-key-id és on es materialitza el xifratge amb clau pròpia: si algun dia revoques aquesta clau, el repositori deixa de ser llegible, que és exactament el que vols davant d'una fuita. I el repositori és sempre privat, sense «de moment públic, que ja ho canviarem»: els rastrejadors que busquen credencials en repositoris públics triguen minuts, no dies.

Autenticació: HTTPS, SSH, l'ajudant de la CLI i rols

Aquí hi ha la diferència conceptual més interessant de CodeCommit: no hi ha usuaris de Git, hi ha identitats d'IAM. No existeix un «usuari luis» del servidor amb la seva contrasenya; existeix el seu usuari o rol d'IAM, i el permís per empènyer és una política. Donar de baixa algú és treure-li IAM, no recordar-se d'un segon sistema.

Mètode Com funciona Quan Inconvenient
Credencials HTTPS d'IAM Usuari/contrasenya generats a IAM Portàtil amb usuari IAM Material de llarga durada
Claus SSH Clau pública a l'usuari IAM Portàtil, si prefereixes SSH Igual
Ajudant de credencials Git demana a la CLI una signatura SigV4 Recomanat: SSO, MFA, rols Requereix la CLI
Rol IAM La màquina assumeix un rol, sense credencials CodeBuild, EC2, Lambda Només per a màquines

Fes servir l'ajudant de credencials. La seva virtut és que no desa res: cada vegada que Git necessita autenticar-se invoca aws codecommit credential-helper, que signa amb les credencials actuals del perfil. Si vénen d'IAM Identity Center i caduquen al cap de vuit hores, l'accés caduca amb elles. És el principi de credencials temporals de 04-01 aplicat al repositori.

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-tienda

credential.UseHttpPath true és obligatori i s'oblida constantment: sense ell, Git no envia la ruta del repositori, la signatura es calcula sobre un recurs equivocat i obtens un 403 que no explica res.

A GitHub, l'equivalent modern per a les màquines és OIDC, no una clau d'accés. En lloc de desar un AWS_ACCESS_KEY_ID com a secret del repositori, s'estableix confiança entre el proveïdor OIDC de GitHub i un rol del teu compte, amb una condició sobre l'organització i el repositori concrets:

{
  "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ó sub impedeix que qualsevol repositori del món assumeixi el teu rol. Un StringLike amb repo:* és un forat de seguretat greu que s'ha vist en producció més d'una vegada.

Polítiques IAM: qui pot empènyer a main

A CodeCommit la protecció de branca s'expressa com a política d'IAM, mitjançant la condició codecommit:References. Aquesta política denega empènyer directament 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 detalls que sovint es copien malament. StringEqualsIfExists limita la denegació al que afecta refs/heads/main: un push a funcionalidad/franja-horaria no hi encaixa i es permet. Null: {"codecommit:References": "false"} significa «aplica només si la clau hi és present»; sense ella denegaries també operacions sense referència de branca, i bloquejaries coses que no volies. I PutFile i DeleteFile tanquen la porta de la consola web, que permet editar sense passar per Git i és el forat que més s'oblida —en canvi, no deneguis les accions MergeBranchesBy*: són les que executen la fusió d'una PR, i bloquejar-les impedeix tancar fins i tot les PR aprovades.

Deny guanya sempre sobre Allow, com vam veure a 04-01: això és efectiu encara que el Luis tingui adjuntada AWSCodeCommitPowerUser. A GitHub el mecanisme canvia —regles de protecció de branca o rulesets— però la intenció és idèntica.

Estratègia de branques: trunk-based enfront de GitFlow

Aspecte Trunk-based GitFlow
Branques de llarga vida Una: main main + develop + release/* + hotfix/*
Vida d'una branca de treball Hores o 1-2 dies Dies o setmanes
Conflictes de fusió Petits i freqüents Grans i dolorosos
Complexitat mental Baixa Alta; cal documentar-la
Lliurament continu És la seva premissa Malament: la branca de release fa nosa
Funcionalitat a mitges S'amaga amb banderes S'aïlla a la seva branca
Versions simultànies Difícil És el que resol bé

GitFlow es va dissenyar el 2010 per a programari que es publica en versions: un instal·lador, una biblioteca, un producte que el client actualitza quan vol. Allà té sentit mantenir la 1.4 mentre desenvolupes la 1.5. El seu propi autor va publicar després una nota dient que si lliures de forma contínua —un web, un SaaS— probablement no el necessites.

MercadoFresco és una botiga web: només existeix una versió, la que és en producció ara. Una branca release/* no aporta res i una develop que viu setmanes garanteix fusions doloroses entre tres persones que toquen els mateixos fitxers.

Decisió de la Marta: trunk-based amb matisos. main sempre desplegable, branques de funcionalitat curtes, i una branca desarrollo que existeix per una raó temporal: fins que el pipeline de 08-04 sigui madur i les proves donin confiança, vol un punt on els canvis convisquin un dia abans d'arribar a main. És un compromís conscient amb data de caducitat declarada: una bastida, no una catedral.

El flux de branques 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: "model comanda"
    commit id: "API + proves"
    checkout desarrollo
    merge funcionalidad/franja-horaria id: "PR 42"
    commit id: "fum 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"

Les regles escrites, que viuen al CONTRIBUTING.md:

Branca Origen Destinació Vida Qui fusiona
main Permanent Ningú directament
desarrollo main main via PR Permanent Marta, després de revisió
funcionalidad/<n> desarrollo desarrollo via PR Màx. 3 dies Autor, 1 aprovació
correccion/<n> main main i desarrollo Hores Autor, 1 aprovació
experimento/<n> Qualsevol S'esborra Dies No es fusiona

El límit de tres dies no és burocràcia: una branca que viu dues setmanes divergeix tant que fusionar-la deixa de ser una fusió i passa a ser una reintegració; si un canvi no cap en tres dies, es parteix. Les correccions urgents surten de main, no de desarrollo, que arrossegaria canvis a mitges a producció; i cal fusionar-les també cap a desarrollo, o la fallada reapareixerà en el següent lliurament. I les branques s'esborren en fusionar: un repositori amb 140 branques mortes és un repositori on ningú no troba res.

Pull requests: revisió, aprovació i fusió

Un pull request és la proposta de fusionar una branca en una altra, amb un espai per discutir-la abans que passi. És el que converteix «el Luis va pujar alguna cosa» en «l'equip va acceptar un canvi».

aws codecommit create-pull-request \
  --title "Afegir franja horaria de lliurament a la comanda" \
  --description "$(cat <<'EOF'
## Que canvia
Afegeix el camp `franja_entrega` (manana|tarde|24h) a la comanda i el propaga a l'esdeveniment
`PedidoConfirmado` com a atribut opcional.

## Per que
Peticio de la Sara: el 31 % de les incidencies de repartiment son per absencia del client.

## Com provar-ho
1. POST /pedidos amb "franja_entrega": "tarde"
2. Comprovar el missatge a cola-mercadofresco-pedidos
3. Comprovar que una comanda SENSE el camp continua funcionant (compatibilitat cap enrere)

## Riscos
Canvi d'esquema a Aurora: columna NULLABLE, sense valor per defecte, sense index nou.
Els consumidors antics ignoren l'atribut. Veure 08-05.
EOF
)" \
  --targets repositoryName=mercadofresco-tienda,sourceReference=funcionalidad/franja-horaria,destinationReference=desarrollo \
  --profile mercadofresco-dev --region eu-west-1

Aquesta descripció és una plantilla real i val la pena copiar-la. Una PR sense descripció obliga el revisor a reconstruir la intenció llegint el diff, la manera més lenta i menys fiable d'entendre un canvi. Les quatre seccions es responen en tres minuts i n'estalvien vint al revisor.

Els comentaris poden ser generals o ancorar-se a una línia concreta del diff amb post-comment-for-pull-request i el seu paràmetre --location filePath=app/pedidos.py,filePosition=147, que és on passa la revisió útil: sobre la línia, no en un correu a part.

Les tres estratègies de fusió, idèntiques en qualsevol plataforma:

Estratègia Què fa Història resultant Quan
Fast-forward Mou el punter, sense commit nou Lineal, conserva tots els commits Branca d'1 commit ja rebasada
Squash Aixafa la branca en un commit Lineal i neta: 1 PR = 1 commit Per defecte en funcionalitats
Three-way Commit de fusió amb dos pares Ramificada, conserva el detall Entre branques de llarga vida

MercadoFresco fa servir squash per a les branques de funcionalitat i three-way per a desarrollomain. El raonament: els quinze commits de «wip», «arreglo el test» i «ara sí» no aporten res d'aquí a sis mesos, i un git log de main on cada línia és un canvi complet i descrit és una eina de diagnòstic excel·lent durant un incident; en canvi, en passar desarrollo a main interessa conservar els commits individuals per poder revertir-ne un de sol. Una conseqüència del squash que sorprèn la primera vegada: el commit resultant és nou i no comparteix identificador amb els de la branca, així que la branca queda «no fusionada» als ulls de Git encara que el seu contingut hi sigui. Esborra-la igualment.

aws codecommit merge-pull-request-by-squash \
  --pull-request-id 42 --repository-name mercadofresco-tienda \
  --commit-message "Afegir franja horaria de lliurament a la comanda (#42)" \
  --profile mercadofresco-dev --region eu-west-1

Regles d'aprovació i protecció de main

Una PR que qualsevol fusiona sense que ningú se la miri és un formalisme. El que la converteix en un control real és una plantilla de regla d'aprovació: una política que s'aplica automàticament a les PR que compleixen un criteri i exigeix aprovacions d'un conjunt de persones.

aws codecommit create-approval-rule-template \
  --approval-rule-template-name plantilla-mf-main-requiere-aprobacion \
  --approval-rule-template-description "Tota PR cap a main necessita 1 aprovacio de l'equip" \
  --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 fa res fins que s'associa a un repositori
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-1

DestinationReferences limita la regla a les PR cap a main: agilitat en el dia a dia, rigor a la porta de producció. ApprovalPoolMembers admet el patró assumed-role/<rol>/*, que és el correcte quan l'equip entra amb SSO i les identitats són sessions de rol, no usuaris IAM. NumberOfApprovalsNeeded: 1 és el raonable per a tres persones; amb dues, el procés es bloquejaria així que algú se n'anés de vacances. I l'autor no pot aprovar la seva pròpia PR: CodeCommit ho impedeix de manera nativa i a GitHub cal activar-ho, però és la meitat del valor del mecanisme.

La segona condició —que la construcció estigui en verd— no es pot expressar aquí, perquè aquestes regles només compten aprovacions humanes. Hi ha dos camins: que CodeBuild aprovi la PR programàticament en acabar bé, o que la comprovació visqui al proveïdor Git com a required status check. Aquesta peça es tanca a 08-04, quan el pipeline existeixi i tingui alguna cosa a dir sobre la qualitat del canvi.

Convencions: commits, versió semàntica i etiquetes

Les convencions semblen cosmètica fins al dia en què un pipeline les llegeix i hi pren decisions.

El format és tipus(àmbit): descripció en imperatiufeat(pedidos): afegir franja horaria de lliurament, fix(pagos): evitar el doble cobrament quan el proveidor respon 504, chore(deps): pujar boto3:

Tipus Significat Efecte en la versió
feat Funcionalitat nova MINOR
fix, perf Correcció o millora PATCH
refactor, docs, test, chore Sense canvi funcional Cap
Qualsevol amb BREAKING CHANGE: al cos Canvi incompatible MAJOR

El versionatge semàntic (MAJOR.MINOR.PATCH) és l'altra cara de la mateixa moneda: v1.5.0 enfront de v1.4.3 diu què cal esperar. En una botiga on l'únic client és el teu frontal, el MAJOR importa poc; així que publiques una API que consumeix l'ERP del magatzem, importa molt.

git tag -a v1.5.0 -m "Franja horaria de lliurament + memoria cau de cataleg"
git push origin v1.5.0
git log --oneline v1.4.3..v1.5.0

El valor operatiu és que l'artefacte desplegat porta aquest número. Quan mercadofresco-alb-latencia-alta salti a les 19:20 d'un divendres, la primera pregunta serà «quina versió hi ha en producció?» i la segona «què ha entrat des de l'anterior?». Amb etiquetes es responen en deu segons; sense, reconstruint dates i esperances.

El .gitignore i per què mai no es puja un .env

Un .gitignore correcte no és higiene: és control de fuites.

# Entorn i secrets - MAI al repositori
.env
.env.*
!.env.example
*.pem
*.key
credenciales*.json

# Python i artefactes de construccio
__pycache__/
.venv/
.pytest_cache/
.coverage
dist/
node_modules/

# Dades locals: poden contenir dades personals
*.csv
*.sql
volcado_aurora_*.dump
!datos/ejemplo_anonimo.csv

La línia !.env.example desexclou una plantilla de variables sense valors, que sí que hi ha de ser perquè algú nou sàpiga quina configuració cal. I les últimes línies eviten que un bolcat d'Aurora amb comandes reals acabi al repositori: això és un incident de protecció de dades, no un descuit.

Per què el .env és el pitjor de tots. Al portàtil del Luis n'hi ha un amb la contrasenya de mfadmin, la clau de la passarel·la de pagaments i el token de l'ERP. Si entra una sola vegada al repositori:

queda per sempre a la història —esborrar-lo al commit següent no l'elimina, es recupera amb git show, i netejar-lo de debò exigeix reescriure la història i forçar el push trencant tots els clons—, es replica a cada clon, memòria cau de CI i còpia del proveïdor, i l'única resposta vàlida és rotar les credencials: si el secret s'ha exposat està compromès, i tant se val quant va durar l'exposició.

La solució la vam construir a 04-03 i aquí només cal fer-la servir: contrasenyes a mercadofresco/produccion/rds/mfadmin amb rotació automàtica cada 30 dies, i configuració no sensible a Parameter Store sota /mercadofresco/produccion/. L'aplicació les llegeix en arrencar amb rol-mercadofresco-tienda. Al repositori no hi ha ni un valor secret, només el nom del secret, que no és sensible. I la rotació de 04-03 hi guanya un valor que llavors no era evident: posa data de caducitat automàtica al dany d'una fuita. A 08-02 hi afegirem una xarxa més: una anàlisi que falla la construcció si detecta alguna cosa amb pinta de credencial.

Notificacions i disparadors

Un repositori que ningú no mira és un repositori on les PR esperen quatre dies.

Mecanisme Què és Casos típics
Disparadors Configuració del repositori: en push, invoca SNS o Lambda Automatismes lligats al push
Notificacions Regla sobre esdeveniments del servei cap a SNS o un xat Avisar persones de PR i comentaris
EventBridge Tots els esdeveniments de CodeCommit arriben al bus default Encaminament per contingut
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-1

I una regla d'EventBridge que aprofita tot el de 07-03, amb source: ["aws.codecommit"], detail-type: ["CodeCommit Pull Request State Change"] i un filtre per detail.event per reaccionar a pullRequestCreated. Si el que vols és avisar persones, no ho muntis amb un disparador: per a això hi ha les notificacions, que porten el format fet i s'integren amb Slack sense escriure codi.

Hi ha un antipatró que convé anomenar: fer servir un disparador per llançar el desplegament. És temptador i funciona el primer dia, però no hi ha construcció, ni proves, ni aprovació, ni artefacte versionat, ni manera de reintentar. És l'scp del Luis amb més passos. El que ha de reaccionar al push és un pipeline, i això és 08-04.

Migrar el repositori del Luis

El repositori local té 1.847 commits, tres branques i un .env rastrejat des del segon commit.

Pas 1: inventari. git branch -a, git log --oneline | wc -l i git count-objects -vH per saber què hi ha i quant pesa.

Pas 2: buscar secrets a la història abans de pujar res. No és opcional.

git log --all --full-history --oneline -- .env
git log --all -p -S "AKIA" --oneline                     # clau d'acces d'AWS
git log --all -p -S "BEGIN RSA PRIVATE KEY" --oneline

El Luis troba .env en 312 commits. Dos camins: reescriure la història amb git filter-repo si el repositori serà públic o compartit amb tercers, o pujar i rotar si és privat i l'equip és petit. MercadoFresco tria la segona opció, però la rotació és innegociable i es fa abans del push, amb aws secretsmanager rotate-secret --secret-id mercadofresco/produccion/rds/mfadmin i regenerant al tauler del proveïdor la clau de la passarel·la i el token de l'ERP.

Pas 3: netejar l'arbre de treball.

git rm --cached .env                    # deixa de rastrejar-lo, no l'esborra del disc
printf '.env\n.env.*\n!.env.example\n' >> .gitignore
git add .gitignore && git commit -m "chore(seguridad): deixar de rastrejar .env i afegir .gitignore"

Passos 4 i 5: remot i pujada, amb git remote add origin [email protected]:mercadofresco/mercadofresco-tienda.git seguit de git push --mirror origin.

--mirror puja totes les referències —branques, etiquetes i notes— tal com són en local, que és el que vols en una migració inicial. Dos advertiments: és destructiu a la destinació (esborra al remot el que no existeixi en local, així que només contra un repositori buit i només la primera vegada), i després no es torna a fer servir mai; el dia a dia és git push origin <branca>. Si vols control fi, git push origin --all i git push origin --tags.

Pas 6: verificar clonant en net, el pas que la gent es salta i després lamenta. Clona a /tmp, compta els commits —han de ser els 1.847—, revisa branques i etiquetes, i comprova que .env no hi és.

Passos 7 i 8: crear desarrollo, protegir main al proveïdor (PR obligatòria, una aprovació, sense force push), i que la Marta i la Sara clonin. El repositori deixa d'estar en un portàtil el dia que hi ha almenys dues còpies més i algú ha comprovat que funcionen.

Cost i neteja

Concepte Cost a MercadoFresco
CodeCommit, 3 usuaris i 240 MB 0 USD (5 usuaris i 50 GB inclosos)
CodeConnections i GitHub amb repositoris privats 0 USD
Notificacions via SNS Cèntims
Total del govern del codi ≈ 0 USD/mes

La barrera per adoptar això mai no és econòmica: és l'hàbit. El que costa diners comença a 08-02.

# IRREVERSIBLE i sense paperera: clona amb --mirror a un disc abans d'executar-ho
aws codecommit delete-repository --repository-name pruebas-borrar \
  --profile mercadofresco-dev --region eu-west-1

Errors Habituals i Consells

Triar CodeCommit per a un projecte nou. No admet nous clients i no rep funcionalitats. Aprèn-ne els conceptes, però desplega a GitHub o GitLab.

Oblidar credential.UseHttpPath true. Produeix un 403 en clonar de CodeCommit que sembla un problema de permisos IAM i no ho és. És el primer lloc on cal mirar.

Deixar la connexió de CodeConnections en PENDING. El recurs existeix, la CLI el llista i tot el que en depengui falla amb un missatge confús. L'autorització al navegador és obligatòria i manual.

Desar claus de llarga durada com a secrets de GitHub. Fes servir OIDC amb una condició sub que anomeni la teva organització i el teu repositori; un repo:* és un forat greu.

Denegar les accions MergeBranchesBy* per «protegir main». Ningú no pot fusionar ni tan sols una PR aprovada, i al cap de dos dies algú demana treure la política sencera. Protegeix l'escriptura directa, no la fusió governada.

Branques de funcionalitat que viuen tres setmanes. Cada dia extra multiplica el cost de fusió. Si el canvi és gran, parteix-lo en increments compatibles cap enrere. I adoptar GitFlow per costum en un producte de lliurament continu afegeix tres branques de llarga vida a canvi d'un problema que no tens.

Pujar un .env «només un moment per provar». Queda a la història per sempre i l'única resposta correcta és rotar. No hi ha drecera.

Fusionar amb squash i després intentar revertir un commit intermedi. Ja no existeix: la branca sencera és un sol commit. És el preu conscient del squash.

Consell: escriu el CONTRIBUTING.md el primer dia, i fes servir .github/pull_request_template.md per precarregar les quatre seccions. Mitja pàgina estalvia les mateixes cinc converses cada mes, i un revisor amb context revisa el doble de ràpid.

Consell: activa l'esborrat automàtic de branques en fusionar, i etiqueta cada versió que arriba a producció sense excepcions: el dia de l'incident, la diferència entre deu segons i vint minuts de diagnòstic és en aquesta etiqueta.

Exercicis

Exercici 1: triar proveïdor amb criteri

Per a cada escenari, tria entre CodeCommit, GitHub, GitLab autoallotjat o Bitbucket i justifica-ho en tres frases anomenant el criteri decisiu:

(a) Empresa pública espanyola, 40 desenvolupadors. El plec exigeix que el codi resideixi en infraestructura sota el seu control dins de la UE amb auditoria completa d'accessos. Tenen compte AWS des del 2019 amb CodeCommit habilitat i 60 repositoris.

(b) Startup de 6 persones que arrenca avui. Tots vénen de GitHub, volen escaneig de dependències sense muntar-lo i desplegaran a AWS amb Lambda i S3.

(c) Equip de 25 persones en una empresa que fa servir Jira per a tot, amb auditories trimestrals que creuen incidències amb commits.

Exercici 2: la política que va bloquejar l'equip

La Marta aplica això al grup desarrolladores-mercadofresco per protegir 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"] }
    }
  }]
}

L'endemà el Luis no pot empènyer a la seva branca de funcionalitat, però sí que pot empènyer a main. Respon: (a) què fa exactament i per què produeix aquest efecte; (b) escriu-la correctament; (c) què li falta per protegir main de debò; (d) quina comprovació hauria detectat la fallada abans d'aplicar-la.

Exercici 3: la migració amb sorpresa

En executar el pas 2, el Luis troba: un .env en 312 commits amb la contrasenya de mfadmin i la clau de la passarel·la; un clientes_octubre.csv de 18 MB amb 4.200 noms, adreces i telèfons reals, afegit fa nou mesos; i una branca experimento/pagos-bizum de fa un any sense fusionar.

Respon: (a) què fas amb cada troballa i en quin ordre; (b) quina fa que reescriure la història deixi de ser opcional, i per què; (c) l'ordre exacta i de què avises l'equip abans; (d) a qui cal informar dins de l'empresa per la segona troballa.

Solucions

Solució 1

(a) CodeCommit, i és l'únic escenari de l'exercici on té sentit. El criteri decisiu és el primer de la llista: l'exigència legal de residència i control del codi, que anul·la tots els altres. El compte és anterior al juliol del 2024, així que el servei està disponible; el codi viu al seu compte d'AWS a eu-west-1, xifrat amb la seva clau de KMS, i CloudTrail dóna l'auditoria que demana el plec. L'alternativa seria GitLab autoallotjat a la seva VPC, que compleix igual el requisit però els obliga a operar i pedaçar el servidor: amb 60 repositoris ja migrats no compensa. El que sí que han de fer és planificar la sortida a mitjà termini, perquè estan sobre un servei sense evolució.

(b) GitHub. Criteri decisiu: on és ja l'equip, reforçat per l'ecosistema. Sis persones que ja coneixen l'eina produeixen des del primer dia; Dependabot dóna l'escaneig de dependències sense muntar res; els repositoris privats són gratuïts a aquesta mida. Per desplegar a AWS, OIDC més CodeConnections ho cobreix tot sense emmagatzemar claus, i no hi ha restricció de residència que ho compliqui.

(c) Bitbucket. Criteri decisiu: la integració amb la resta d'eines. La vinculació nativa entre commits, branques i incidències de Jira és exactament el que consumeix la seva auditoria trimestral; amb GitHub caldria muntar i mantenir aquesta integració. GitLab seria superior en CI/CD, però aquí l'estalvi és al procés, no al pipeline; i amb 25 persones Bitbucket és a més el més barat.

Solució 2

(a) La condició està invertida. StringNotEqualsIfExists amb refs/heads/main significa «denega quan la referència no sigui main». El resultat és literalment el contrari del desitjat: prohibeix empènyer a qualsevol branca excepte main i deixa main completament oberta. És un error d'una sola paraula —Not— amb conseqüències oposades a la intenció, i per això les denegacions amb condicions negades cal llegir-les dues vegades en veu alta.

(b) La correcta fa servir StringEqualsIfExists per acotar la denegació a main, afegeix Null perquè no afecti operacions sense referència, i amplia les accions:

{
  "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 impedeix esborrar main sencera; PutFile i DeleteFile tanquen l'edició des de la consola web, que salta Git i és el forat més oblidat. Un push --force ja queda cobert perquè IAM no el distingeix d'un GitPush. Però falta el que cap política no pot donar: una regla d'aprovació associada al repositori que exigeixi almenys una aprovació a les PR cap a main, més —quan existeixi— la comprovació que la construcció està en verd. Sense això, la política només obliga a passar per una PR; no obliga que la PR sigui bona.

(d) El simulador de polítiques d'IAM de 04-01, o millor: aplicar-la primer a un repositori de proves i executar els quatre casos —push a branca de funcionalitat, push a main, esborrat de branca, edició des de la consola— comprovant que dos passen i dos fallen. Una denegació amb condicions mai no s'ha d'estrenar al repositori real: la manera de fallar és que bloqueja l'equip o, pitjor, que no bloqueja res i ningú no se n'assabenta.

Solució 3

(a) L'ordre importa perquè el primer pas fa irrellevant el temps dels altres. Un: rotar immediatament els secrets del .env —contrasenya de mfadmin des de Secrets Manager, clau de la passarel·la regenerada al tauler del proveïdor— abans de tocar el repositori; estan compromesos des de fa catorze mesos i l'esborrat no els descompromet. Dos: el CSV, decidir la reescriptura i no pujar res mentrestant. Tres: la branca experimento/pagos-bizum no es migra; si té valor s'exporta amb git format-patch i es desa fora. Migrar branques mortes és arrossegar soroll.

(b) El CSV. El .env és greu però acotat: es resol rotant, perquè un secret rotat ja no val res encara que continuï a la història. Un fitxer amb 4.200 noms, adreces i telèfons reals no es pot rotar. És dada personal sota el RGPD, i el principi de minimització més el dret de supressió fan inacceptable conservar-lo indefinidament en un repositori de codi, on es replicarà a cada clon, cada memòria cau de CI i cada còpia del proveïdor. Aquí reescriure deixa de ser una opció tècnica i passa a ser una obligació.

(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 passada, ja que es reescriu

git filter-repo canvia el hash dels commits afectats i de tots els seus descendents, així que cal avisar que qualsevol clon existent queda inservible i s'ha de tornar a clonar: fusionar un clon antic amb la història reescrita crea un desastre de commits duplicats. Aquí la migració encara no ha passat i només existeix el clon del Luis, així que l'impacte és nul —raó excel·lent per netejar abans de pujar—. Si ja fos al remot, a més caldria push --force, esborrar branques i etiquetes obsoletes del remot i demanar al proveïdor que purgui els objectes inabastables.

(d) Al responsable de protecció de dades (DPO o equivalent). Dades personals de 4.200 clients fora dels sistemes autoritzats durant nou mesos són un tractament no previst que cal documentar i avaluar. Potser no és una bretxa notificable a l'AEPD —el repositori era privat i local—, però aquesta valoració no la fa l'equip tècnic: s'aporta la informació completa (quines dades, quant temps, qui hi va tenir accés, què s'ha fet) i decideix qui en té la responsabilitat. És el mateix criteri que vam aplicar a 06-03 amb el clon aurora-mf-pruebas-luis.

Conclusió

El codi de MercadoFresco ha sortit del portàtil del Luis. Ja no és un git init amb una còpia USB de setembre: és un repositori remot, replicat, xifrat, amb permisos d'IAM en lloc d'usuaris de sistema i amb almenys tres còpies vives a l'equip. Els quatre primers riscos de l'inventari de la Marta estan tancats pel simple fet d'haver pujat el codi.

Però l'important d'aquesta lliçó no és l'allotjament, és el govern. Saps que CodeCommit ja no admet nous clients i per què això no invalida aprendre'l —te'l trobaràs—, i que la resposta actual és GitHub o GitLab connectats amb CodeConnections, amb la seva autorització manual al navegador que tantes accions d'origen trenca quan s'oblida. Tens el criteri d'elecció en ordre: primer l'exigència legal si existeix, després on és ja el teu equip, després on vols que visqui el CI/CD, i per últim l'ecosistema. Saps autenticar-te amb l'ajudant de credencials —amb credential.UseHttpPath true, que evita el 403 misteriós— i amb OIDC en lloc de claus de llarga durada per a les màquines. I saps escriure una política que protegeix main sense bloquejar l'equip, i per què la condició Null acompanya sempre StringEqualsIfExists.

Has triat trunk-based amb una branca desarrollo declarada com a bastida temporal, perquè una botiga web només té una versió —la que és en producció ara— i GitFlow resol un problema que MercadoFresco no té. Saps què és una PR que serveix —què, per què, com provar-ho, riscos—, quan fer servir squash i quan three-way, i com una plantilla de regla d'aprovació converteix la revisió en un control real gràcies al fet que l'autor no es pot aprovar a si mateix. Tens les convencions que un pipeline llegirà més endavant: commits convencionals, versió semàntica i etiquetes que responen en deu segons a «què hi ha en producció». I tens la regla que no admet excepcions: el .env no puja mai, perquè queda a la història per sempre i l'única resposta vàlida és rotar.

Ara hi ha un repositori amb regles. El que no hi ha és cap garantia que el que hi entra funcioni. L'aprovació de la PR significa que una persona ha llegit el diff un dimarts a la tarda, la qual cosa és molt més que res i força menys que suficient. Ningú no ha executat les proves: el Luis les llança al seu portàtil «gairebé sempre», i l'última vegada que en va fallar una va decidir que era cosa del seu entorn. Ningú no ha comprovat que les dependències no tinguin vulnerabilitats conegudes, ni que no s'hagi colat una clau al codi, ni que el projecte tan sols arrenqui en una màquina neta. La frase «funciona al meu portàtil» continua sent un argument admissible a MercadoFresco, i encara no existeix cap artefacte: el que es desplega és una carpeta copiada amb scp.

A 08-02, «AWS CodeBuild», això s'acaba. Veurem què és la integració contínua i per què converteix «funciona al meu portàtil» en una frase sense valor; l'anatomia d'un projecte de construcció amb el seu entorn, el seu rol de servei, la seva memòria cau i els seus artefactes; un buildspec.yml complet i comentat amb les fases install, pre_build, build i post_build; com injectar secrets des de Parameter Store i Secrets Manager sense que apareguin als registres; les proves unitàries amb pytest i els informes de cobertura com a porta de qualitat i no com a informe que ningú no llegeix; l'escaneig de dependències i de credencials filtrades; la construcció del paquet que anirà al bucket mercadofresco-artefactos; i com executar la construcció dins de vpc-mercadofresco per provar contra el clon aurora-mf-pruebas-luis, amb l'advertiment de cost que això comporta.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats