Pular para o conteúdo principal

Desafio 09: Gerenciamento de repositório

Platform: ADO-first

Habilidades do exame

  • Configurar permissões no repositório de controle de código-fonte
  • Configurar tags para organizar o repositório de controle de código-fonte

Cenário

O monorepo da Contoso Ltd cresceu para 200 contribuidores em 8 equipes. Atualmente, todos têm o mesmo nível de acesso. No último sprint, um estagiário acidentalmente fez push de uma alteração de configuração nos manifestos de deploy de produção em /deploy/prod/, causando uma interrupção. As tags Git estão inconsistentes (algumas usam v1.2.3, outras usam release-20240115, e algumas são tags leves sem metadados). A equipe de DevOps precisa implementar controles de acesso adequados e estabelecer uma estratégia de tags que se integre com o pipeline de CI/CD.

Tarefas

Tarefa 1: Configurar permissões do repositório GitHub

Configure o acesso baseado em funções usando os níveis de permissão do GitHub:

# Create teams with appropriate repository access
# Admin: DevOps leads (full control including settings and branch protection)
gh api orgs/contoso/teams --method POST \
-f name="platform-admins" \
-f description="Platform administrators - full repo access" \
-f privacy="closed"

# Grant admin access to the repo
gh api orgs/contoso/teams/platform-admins/repos/contoso/platform-monorepo \
--method PUT -f permission="admin"

# Maintain: Tech leads (manage issues, PRs, but can't change settings)
gh api orgs/contoso/teams --method POST \
-f name="tech-leads" \
-f description="Technical leads - maintain access" \
-f privacy="closed"

gh api orgs/contoso/teams/tech-leads/repos/contoso/platform-monorepo \
--method PUT -f permission="maintain"

# Write: Senior developers (push to non-protected branches)
gh api orgs/contoso/teams --method POST \
-f name="senior-developers" \
-f description="Senior developers - write access" \
-f privacy="closed"

gh api orgs/contoso/teams/senior-developers/repos/contoso/platform-monorepo \
--method PUT -f permission="push"

# Triage: Junior developers (manage issues, can't push code)
gh api orgs/contoso/teams --method POST \
-f name="junior-developers" \
-f description="Junior developers and interns - triage access" \
-f privacy="closed"

gh api orgs/contoso/teams/junior-developers/repos/contoso/platform-monorepo \
--method PUT -f permission="triage"

# Read: Stakeholders and external auditors
gh api orgs/contoso/teams --method POST \
-f name="stakeholders" \
-f description="Business stakeholders - read only" \
-f privacy="closed"

gh api orgs/contoso/teams/stakeholders/repos/contoso/platform-monorepo \
--method PUT -f permission="pull"

Verifique as permissões das equipes:

# List all teams with access to the repository
gh api repos/contoso/platform-monorepo/teams | jq '.[] | {name: .name, permission: .permission}'

# Check a specific user's effective permissions
gh api repos/contoso/platform-monorepo/collaborators/intern-jane/permission \
| jq '.permission'

Tarefa 2: Configurar acesso baseado em equipes com GitHub Teams

Organize as equipes hierarquicamente com equipes aninhadas:

# Create parent team for engineering org
gh api orgs/contoso/teams --method POST \
-f name="engineering" \
-f description="All engineering staff" \
-f privacy="closed"

# Create child teams under engineering
PARENT_ID=$(gh api orgs/contoso/teams/engineering --jq '.id')

gh api orgs/contoso/teams --method POST \
-f name="billing-team" \
-f description="Billing engine team" \
-f privacy="closed" \
-f parent_team_id="$PARENT_ID"

gh api orgs/contoso/teams --method POST \
-f name="api-team" \
-f description="API platform team" \
-f privacy="closed" \
-f parent_team_id="$PARENT_ID"

gh api orgs/contoso/teams --method POST \
-f name="frontend-team" \
-f description="Frontend/UI team" \
-f privacy="closed" \
-f parent_team_id="$PARENT_ID"

# Add members to teams
gh api orgs/contoso/teams/billing-team/memberships/sarah-billing --method PUT -f role="maintainer"
gh api orgs/contoso/teams/billing-team/memberships/dev-bob --method PUT -f role="member"
gh api orgs/contoso/teams/billing-team/memberships/dev-carol --method PUT -f role="member"

# List team members
gh api orgs/contoso/teams/billing-team/members | jq '.[].login'

Tarefa 3: Configurar permissões do Azure Repos

Configure permissões granulares no Azure DevOps incluindo segurança em nível de caminho:

# Set repository-level permissions
# Grant contribute permission to the development team
az devops security permission update \
--namespace-id "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" \
--subject "vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMjA0NDAwOTY5" \
--token "repoV2/contoso-project-id/repo-id" \
--allow-bit 4 \
--deny-bit 0

# Deny force push to all non-admin users
az devops security permission update \
--namespace-id "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" \
--subject "vssgp.Uy0xLTktMTU1MTM3NDI0NS0xMjA0NDAwOTY5" \
--token "repoV2/contoso-project-id/repo-id" \
--deny-bit 8

# Configure branch-level permissions (protect release branches)
# Only release managers can push to release/* branches
az repos policy approver-count create \
--project "Contoso-Platform" \
--repository-id <repo-id> \
--branch "release/*" \
--minimum-approver-count 2 \
--blocking true \
--enabled true

# Path-level permissions using Azure DevOps security namespaces
# Deny interns write access to /deploy/prod/ path
# This requires the Git Repositories namespace and path-scoped tokens
az devops security permission update \
--namespace-id "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" \
--subject "vssgp.intern-group-descriptor" \
--token "repoV2/project-id/repo-id/refs/heads/main//deploy/prod" \
--deny-bit 4

Crie um documento de matriz de permissões para a equipe:

Níveis de permissão do Azure Repos para o monorepo da Contoso

PermissãoAdminsTech leadsSenioresJunioresEstagiários
LeituraSimSimSimSimSim
ContribuirSimSimSimSimNão
Criar branchSimSimSimSimNão
Force pushSimNãoNãoNãoNão
Gerenciar permissõesSimNãoNãoNãoNão
Push para mainNão*Não*Não*Não*Não*
Push para release/*SimSimNãoNãoNão
Push para deploy/prodSimNãoNãoNãoNão

*main é protegida - requer PR com aprovações

Tarefa 4: Implementar tags Git para releases

Crie tags anotadas adequadas para releases:

# Annotated tag (stores tagger info, date, message - preferred for releases)
git tag -a v1.2.0 -m "Release v1.2.0 - Q1 2024 billing engine update

Changes:
- Add multi-currency support
- Fix EU VAT calculation
- Improve invoice PDF generation performance

Breaking changes:
- Currency field is now required on all invoice endpoints"

# Push the tag to remote
git push origin v1.2.0

# Lightweight tag (just a pointer to a commit - for temporary/internal use)
git tag build-2024.01.15-rc1
git push origin build-2024.01.15-rc1

# Tag a specific past commit (retroactive tagging)
git tag -a v1.1.5 abc1234 -m "Patch release v1.1.5 - hotfix for payment timeout"
git push origin v1.1.5

# List all tags
git tag --list

# List tags matching a pattern
git tag --list "v1.2.*"

# Show tag details
git show v1.2.0

# Verify a signed tag (if GPG signing is configured)
git tag -v v1.2.0

Compare tags anotadas vs tags leves:

# See the difference in object types
git cat-file -t v1.2.0 # Output: tag (annotated - full object)
git cat-file -t build-2024.01.15-rc1 # Output: commit (lightweight - just a ref)

# Annotated tags show in `git describe`
git describe --tags
# Output: v1.2.0-14-g2414721 (14 commits after v1.2.0)

Tarefa 5: Convenções de nomenclatura de tags

Estabeleça um padrão de tags para a Contoso:

# Production releases: Semantic Versioning
# Format: v{MAJOR}.{MINOR}.{PATCH}
git tag -a v2.0.0 -m "Major release: new API version"
git tag -a v2.1.0 -m "Minor release: add search feature"
git tag -a v2.1.1 -m "Patch release: fix search pagination"

# Pre-release versions
git tag -a v2.2.0-alpha.1 -m "Alpha: new dashboard (unstable)"
git tag -a v2.2.0-beta.1 -m "Beta: new dashboard (feature complete, testing)"
git tag -a v2.2.0-rc.1 -m "Release candidate: final testing"

# Date-based releases (for services with continuous delivery)
# Format: release-YYYY.MM.DD
git tag -a release-2024.01.15 -m "Weekly release 2024-01-15"

# Build tags (CI-generated, lightweight is acceptable)
git tag ci-build-1847
git tag deploy-prod-2024.01.15.1

Documente a convenção:

Convenção de nomenclatura de tags

PadrãoCaso de usoExemploTipo
v{MAJOR}.{MINOR}.{PATCH}Releases versionadasv2.1.0Anotada
v{X}.{Y}.{Z}-{pre}.{N}Pré-releasesv2.2.0-beta.1Anotada
release-{YYYY.MM.DD}Releases baseadas em datarelease-2024.01.15Anotada
ci-build-{N}Artefatos de build CIci-build-1847Leve
deploy-{env}-{date}.{N}Marcadores de deploydeploy-prod-2024.01.15.1Leve

Regras:

  • Todas as releases de produção DEVEM usar tags anotadas com mensagens descritivas
  • Pré-releases seguem a sintaxe de pré-release do SemVer
  • Tags de CI/build podem ser leves (geradas automaticamente, alto volume)
  • Nunca exclua ou mova uma tag de release publicada

Tarefa 6: Tags automatizadas via pipeline de CI no merge para main

Crie um workflow do GitHub Actions que cria tags de releases automaticamente:

# .github/workflows/auto-tag-release.yml
name: Auto-tag release on merge
on:
push:
branches: [main]

jobs:
tag-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Determine version bump from commit messages
id: version
run: |
# Get the latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "latest_tag=$LATEST_TAG" >> "$GITHUB_OUTPUT"

# Parse current version
VERSION=${LATEST_TAG#v}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"

# Determine bump type from conventional commit messages since last tag
COMMITS=$(git log "$LATEST_TAG"..HEAD --pretty=format:"%s")

if echo "$COMMITS" | grep -q "^BREAKING CHANGE\|^.*!:"; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif echo "$COMMITS" | grep -q "^feat"; then
MINOR=$((MINOR + 1))
PATCH=0
elif echo "$COMMITS" | grep -q "^fix"; then
PATCH=$((PATCH + 1))
else
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
echo "new_tag=$NEW_TAG" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"

- name: Create annotated tag
if: steps.version.outputs.skip == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Generate changelog from commits
CHANGELOG=$(git log ${{ steps.version.outputs.latest_tag }}..HEAD \
--pretty=format:"- %s (%h)" --no-merges)

git tag -a "${{ steps.version.outputs.new_tag }}" \
-m "Release ${{ steps.version.outputs.new_tag }}

Changes since ${{ steps.version.outputs.latest_tag }}:
$CHANGELOG"

git push origin "${{ steps.version.outputs.new_tag }}"

- name: Create GitHub release
if: steps.version.outputs.skip == 'false'
run: |
gh release create "${{ steps.version.outputs.new_tag }}" \
--title "Release ${{ steps.version.outputs.new_tag }}" \
--generate-notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Tarefa equivalente no Azure Pipelines para criação automatizada de tags:

# azure-pipelines.yml (tag on merge to main)
trigger:
branches:
include:
- main

pool:
vmImage: 'ubuntu-latest'

steps:
- checkout: self
fetchDepth: 0
persistCredentials: true

- script: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
VERSION=${LATEST_TAG#v}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
MINOR=$((MINOR + 1))
PATCH=0
NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"

git config user.email "azure-pipelines@contoso.com"
git config user.name "Azure Pipelines"
git tag -a "$NEW_TAG" -m "Release $NEW_TAG (automated)"
git push origin "$NEW_TAG"

echo "##vso[task.setvariable variable=releaseTag]$NEW_TAG"
displayName: 'Create release tag'

Tarefa 7: Configurações do repositório

Configure as definições em nível de repositório para consistência:

# Set default branch to main
gh api repos/contoso/platform-monorepo --method PATCH \
-f default_branch="main"

# Configure allowed merge strategies
gh api repos/contoso/platform-monorepo --method PATCH \
--input - << 'EOF'
{
"allow_squash_merge": true,
"allow_merge_commit": false,
"allow_rebase_merge": true,
"squash_merge_commit_title": "PR_TITLE",
"squash_merge_commit_message": "PR_BODY",
"delete_branch_on_merge": true,
"allow_auto_merge": true,
"allow_update_branch": true
}
EOF

# Enable vulnerability alerts and automated security fixes
gh api repos/contoso/platform-monorepo/vulnerability-alerts --method PUT
gh api repos/contoso/platform-monorepo/automated-security-fixes --method PUT

# Set repository topics for discoverability
gh repo edit contoso/platform-monorepo --add-topic "monorepo,contoso,platform,typescript"

# Configure repository visibility and features
gh api repos/contoso/platform-monorepo --method PATCH \
--input - << 'EOF'
{
"has_issues": true,
"has_projects": true,
"has_wiki": false,
"has_discussions": true,
"web_commit_signoff_required": true
}
EOF

Exercícios de quebra e conserto

Cenário 1: Estagiário faz push para o caminho de produção

Um estagiário fez alterações em /deploy/prod/kubernetes.yaml e fez push diretamente para uma branch de feature que foi então mesclada sem revisão adequada porque o arquivo CODEOWNERS não cobria os manifestos de deploy.

Diagnóstico:

# Find who changed the production config
git log --oneline --all -- deploy/prod/kubernetes.yaml

# Check if CODEOWNERS covers this path
cat .github/CODEOWNERS | grep -i deploy

# Verify branch protection requires CODEOWNERS review
gh api repos/contoso/platform-monorepo/branches/main/protection/required_pull_request_reviews \
| jq '.require_code_owner_reviews'
Mostrar solução

Correção: Adicione o caminho de deploy ao CODEOWNERS e garanta que a revisão por code owner seja obrigatória:

# Add to CODEOWNERS
echo '/deploy/prod/ @contoso/platform-admins @contoso/sre-team' >> .github/CODEOWNERS
git add .github/CODEOWNERS
git commit -m "fix: add production deploy path to CODEOWNERS"
git push origin main

# Revert the intern's change
git revert <merge-commit-sha> --mainline 1
git push origin main

Cenário 2: Tags estão inconsistentes e o CI não consegue determinar a versão mais recente

O comando git describe retorna resultados inesperados por causa da nomenclatura mista de tags:

# The problem: inconsistent tags break automation
git tag --list | head -20
# Output shows:
# 1.0.0 (no v prefix)
# V1.1.0 (uppercase V)
# v1.2.0 (correct)
# release-20231115
# v1.3
# 1.4.0-beta

# git describe picks wrong tag
git describe --tags
# Output: release-20231115-47-gabc1234 (wrong! not a semver tag)
Mostrar solução

Correção: Limpe as tags e configure o git describe para filtrar corretamente:

# Use glob pattern to only match semver tags
git describe --tags --match "v[0-9]*"

# Rename inconsistent tags (requires coordination with team)
# Delete old tag locally and remotely, create new one
git tag -a v1.0.0 $(git rev-list -n 1 1.0.0) -m "Release v1.0.0 (re-tagged)"
git tag -d 1.0.0
git push origin :refs/tags/1.0.0
git push origin v1.0.0

git tag -a v1.1.0 $(git rev-list -n 1 V1.1.0) -m "Release v1.1.0 (re-tagged)"
git tag -d V1.1.0
git push origin :refs/tags/V1.1.0
git push origin v1.1.0

# Add a tag protection rule to prevent non-compliant tags
gh api repos/contoso/platform-monorepo/rulesets --method POST --input - << 'EOF'
{
"name": "Tag naming enforcement",
"target": "tag",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["~ALL"],
"exclude": ["refs/tags/v*"]
}
},
"rules": [
{ "type": "creation" }
]
}
EOF

Verificação de conhecimento

1. : No GitHub, qual é a diferença entre os níveis de permissão "Maintain" e "Write"?

2. : Qual é a diferença principal entre uma tag Git anotada e uma tag leve?

3. : No Azure DevOps, como você restringe um grupo específico de modificar arquivos em um caminho particular dentro de um repositório?

4. : Um pipeline de CI usa 'git describe --tags' para determinar a versão da aplicação. O comando retorna 'v1.2.0-47-g2414721'. O que essa saída significa?

Limpeza

# Remove teams created during this challenge
gh api orgs/contoso/teams/platform-admins --method DELETE
gh api orgs/contoso/teams/tech-leads --method DELETE
gh api orgs/contoso/teams/senior-developers --method DELETE
gh api orgs/contoso/teams/junior-developers --method DELETE
gh api orgs/contoso/teams/stakeholders --method DELETE
gh api orgs/contoso/teams/engineering --method DELETE

# Delete practice tags
git tag -d v1.2.0 v1.1.5 v2.0.0 v2.1.0 v2.1.1 2>/dev/null
git tag -d v2.2.0-alpha.1 v2.2.0-beta.1 v2.2.0-rc.1 2>/dev/null
git tag -d release-2024.01.15 ci-build-1847 2>/dev/null
git push origin --delete v1.2.0 v1.1.5 v2.0.0 2>/dev/null

# Remove workflow files
rm -f .github/workflows/auto-tag-release.yml

# Reset repository settings to defaults
gh api repos/contoso/platform-monorepo --method PATCH \
--input - << 'EOF'
{
"allow_squash_merge": true,
"allow_merge_commit": true,
"allow_rebase_merge": true,
"delete_branch_on_merge": false
}
EOF

# Verify clean state
git tag --list
gh api repos/contoso/platform-monorepo/teams | jq '.[].name'