added files
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# ADR-0001: Single Shared Library Consumed by All Services
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Project inception
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Meesho runs 100+ microservices across multiple teams. Each service needs CI/CD: build, test, sonar analysis, Docker image push, and ArgoCD deployment. Before devops-lib, teams owned their own Jenkinsfiles and copy-pasted pipeline definitions from each other, resulting in drift, inconsistent policy enforcement, and no central control over who could bypass sonar gates or deploy directly to production.
|
||||
|
||||
## Decision
|
||||
|
||||
All Meesho microservices consume a single Jenkins Shared Library — `devops-lib` — via `@Library('devops-lib@main')` in their Jenkinsfile. The library exposes a single entry point (`eksCICD`) that handles the full CI/CD lifecycle. Service teams do not write pipeline logic; they only provide a `config.yaml`.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Per-team Jenkinsfiles**: Rejected because teams were already copy-pasting pipelines and the resulting drift made it impossible to enforce sonar gates, whitelist checks, or deployment policies uniformly.
|
||||
- **Per-language pipeline templates**: Rejected in favour of a single entry point that dispatches to language-specific stages internally — cross-cutting concerns (sonar, notifications, ArgoCD) remain in one place.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Policy enforcement (sonar gates, whitelist checks, Ringmaster gating) is applied uniformly to every service on every build — no team can accidentally opt out.
|
||||
- Infrastructure changes (new deploy strategy, new ArgoCD step, new policy) roll out to all 100+ services by merging one PR to devops-lib.
|
||||
- New service onboarding is reduced to writing a `config.yaml` — no pipeline expertise needed from the service team.
|
||||
|
||||
**Negative:**
|
||||
- The library becomes a dependency for every service build — a broken devops-lib main branch blocks all CI/CD.
|
||||
- Teams needing custom pipeline behaviour have limited escape hatches; they must go through the DevOps Platform team.
|
||||
|
||||
**Neutral:**
|
||||
- devops-lib must support all build types (Maven, Go, Node, Python, Gradle, PHP, Rust) internally, increasing the library's surface area.
|
||||
|
||||
## Constraints
|
||||
|
||||
Meesho's scale (100+ services) made per-service pipeline ownership operationally unsustainable. The decision was also driven by a need for auditable deployment policy — the DevOps team needed a single control plane for all CI/CD.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `vars/eksCICD.groovy` (entry point), `src/com/meesho/stages/buildObjHelper.groovy` (language dispatch)
|
||||
- Consumer services call the library with a single line: `@Library('devops-lib@main') _`
|
||||
@@ -0,0 +1,58 @@
|
||||
# ADR-0002: Branch Name as the Sole Environment Selector
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Project inception
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Each Meesho service needs to deploy to different environments (staging, production, integration, feature). A naive approach would let each service declare its target environment in config, but this creates a risk: a misconfigured service could accidentally deploy to production from a feature branch, or fail to promote through the standard develop → stg → main → prd path.
|
||||
|
||||
## Decision
|
||||
|
||||
`cicd_environment` is derived entirely and deterministically from the branch name. No per-service environment configuration exists. The mapping is:
|
||||
|
||||
| Branch | PR target | `cicd_environment` |
|
||||
|---|---|---|
|
||||
| `main` / `master` / `gcp-main` | — | `prd` |
|
||||
| `develop` | — | `stg` |
|
||||
| any | `main` | `int` |
|
||||
| any | `develop` | `ftr` |
|
||||
| `hotfix/*` | — | `prd` (sonar + tests skipped) |
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Per-service environment configuration**: Rejected because it creates a class of misconfiguration bugs (wrong env in config.yaml → wrong deploy target) and makes it impossible to enforce the standard branch promotion strategy uniformly.
|
||||
- **Environment as a Jenkins parameter**: Rejected because it relies on the engineer picking the right environment at trigger time — error-prone and not auditable.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Services cannot accidentally deploy to prd from a feature branch — the branch IS the environment contract.
|
||||
- Standard develop → stg → main → prd promotion is enforced uniformly across all services.
|
||||
- Environment logic lives in one place (`constructParam.groovy`) — easy to audit and change centrally.
|
||||
|
||||
**Negative:**
|
||||
- Teams with non-standard branch strategies (e.g., release/* branches) cannot use the standard pipeline without DevOps involvement.
|
||||
- The hotfix path (prd with sonar skipped) requires following the `hotfix/*` naming convention exactly.
|
||||
|
||||
**Neutral:**
|
||||
- The `int` and `ftr` environments are determined by PR target, not branch name — this is the one case where branch name alone is insufficient.
|
||||
|
||||
## Constraints
|
||||
|
||||
Meesho's deployment policy required a standard promotion path. Allowing per-service environment configuration would have made it impossible to enforce this uniformly across 100+ services.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0001: Single Shared Library](0001-single-shared-library-for-all-services.md) — The shared library enforces this mapping; per-service Jenkinsfiles could override it.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/utilities/constructParam.groovy` — environment mapping logic
|
||||
- PR context is detected via `env.CHANGE_ID` (set by GitHub Branch Source plugin), not by `env.BRANCH_NAME =~ /PR-/`
|
||||
@@ -0,0 +1,60 @@
|
||||
# ADR-0003: Policy Exceptions Controlled by a Separate Meesho/whitelists Repo
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
The shared CI/CD library enforces several policies by default: SonarQube quality gate, AppConfig validation, CAC config validation, multizone deployment restrictions. Some services legitimately need to bypass these policies (e.g., a legacy service that cannot pass sonar without significant rework, or a service that doesn't use AppConfig). The question is where to store these exceptions and who can grant them.
|
||||
|
||||
## Decision
|
||||
|
||||
Policy exceptions are controlled by YAML files in a separate `Meesho/whitelists` repository, not by flags in each service's `config.yaml`. There are five active whitelist files:
|
||||
|
||||
| Whitelist | Controls |
|
||||
|---|---|
|
||||
| `skip-sonar-whitelist` | Repos that skip SonarQube scanning |
|
||||
| `app-config-disabled` | Repos that skip AppConfig validation |
|
||||
| `multizone-enabled-repos` | Repos that must deploy via Ringmaster (not Jenkins) |
|
||||
| `allowedNonDevelopPrDeploymentToInt` | Repos allowed to deploy non-develop PRs to int |
|
||||
| `ValidateCacConfig` | Repos that run CAC config validation |
|
||||
|
||||
Adding or removing a repo from any whitelist requires a PR to `Meesho/whitelists` reviewed and merged by the DevOps Platform team.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Flags in each service's config.yaml** (e.g., `skip_sonar: true`): Rejected because service teams could self-grant bypass without DevOps review, making it impossible to audit or enforce policy. Note: `config.yaml` does contain a `skip_sonar` field, but `constructParam.groovy` explicitly throws an exception if a repo sets it to `true` without being on the skip-sonar-whitelist — the whitelist is the authoritative gate.
|
||||
- **Hardcoded exceptions in devops-lib source**: Rejected because adding an exception would require a devops-lib PR, which is heavier than a whitelists PR and conflates policy with pipeline logic.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- DevOps Platform team retains ownership of all policy exception grants — service teams cannot bypass sonar or multizone enforcement unilaterally.
|
||||
- All exceptions are visible in one repo — easy to audit who has what bypass and why.
|
||||
- Policy can be tightened or relaxed without touching devops-lib or any service repo.
|
||||
|
||||
**Negative:**
|
||||
- Adding a legitimate exception requires a separate PR to a different repo, adding friction for service teams.
|
||||
- The whitelist repo is a single point of dependency — if it's unreachable, every build that checks whitelists fails.
|
||||
|
||||
**Neutral:**
|
||||
- The whitelist is re-cloned fresh on every build (see ADR-0004), so changes take effect immediately without a devops-lib release.
|
||||
|
||||
## Constraints
|
||||
|
||||
Audit requirements and DevOps policy ownership drove the decision to separate exception management from service config.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0004: Fresh Whitelist Clone Per Build](0004-fresh-whitelist-clone-per-build.md) — How whitelist changes take effect immediately.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/utilities/constructParam.groovy:getWhitelistedRepos()` — all whitelist lookups go through this method
|
||||
- `config.yaml` has a `skip_sonar` field but it is rejected by `constructParam.groovy` unless the repo is also on the whitelist
|
||||
@@ -0,0 +1,50 @@
|
||||
# ADR-0004: Whitelist Cloned Fresh on Every Build — No Caching
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
`constructParam.groovy` checks several whitelists (sonar skip, multizone, AppConfig, CAC, non-develop PR deploy) on every build. Jenkins agents are long-lived processes that handle many builds sequentially. If the whitelist were cached in agent memory or on disk, a policy change (adding or removing a repo from a whitelist) would not take effect until the agent restarted or the cache expired.
|
||||
|
||||
## Decision
|
||||
|
||||
`getWhitelistedRepos()` clones `Meesho/whitelists` fresh from GitHub on every build invocation. No in-memory cache, no disk cache, no TTL — each build gets the current live state of the whitelist at that moment.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Cache with TTL (e.g., 5 minutes)**: Rejected because a DevOps engineer who merges a critical policy change (adding a repo to skip-sonar during an incident) would have to wait for the cache to expire — unacceptable for incident response.
|
||||
- **Cache per Jenkins agent restart**: Rejected for the same reason — agents can run for hours/days, making cache invalidation unpredictable.
|
||||
- **Webhook-triggered cache invalidation**: Not evaluated — the simplicity of a fresh clone was preferred over building an invalidation mechanism.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Policy changes take effect on the very next build after the whitelist PR is merged — no devops-lib release required.
|
||||
- No cache invalidation complexity; the whitelist state is always authoritative.
|
||||
|
||||
**Negative:**
|
||||
- Each build that checks a whitelist incurs a `git clone` of `Meesho/whitelists`. At high build throughput, this is measurable latency.
|
||||
- If `Meesho/whitelists` is temporarily unreachable (GitHub outage, network partition), all builds that check whitelists fail.
|
||||
|
||||
**Neutral:**
|
||||
- Multiple whitelist checks in a single build (sonar + AppConfig + multizone) each clone the repo separately — there is no deduplication within a single build.
|
||||
|
||||
## Constraints
|
||||
|
||||
Incident response requirements made immediate policy enforcement non-negotiable. The extra clone latency was accepted as the cost of correctness.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0003: Policy Exceptions in Separate Whitelist Repo](0003-policy-exceptions-in-separate-whitelist-repo.md) — The whitelist repo this decision is about.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/utilities/constructParam.groovy:getWhitelistedRepos()`
|
||||
- **Never refactor this to cache across calls** — the fresh-clone behaviour is load-bearing for incident response. This is documented in CLAUDE.md under NEVER DO.
|
||||
@@ -0,0 +1,46 @@
|
||||
# ADR-0005: Config-Only Change Detection — Skip Binary Build, Reuse Latest Image
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Mid-project
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Meesho services store both application code and deployment configuration (Helm values, AppConfig YAML) in the same repository. Teams frequently push config-only changes — tweaking memory limits, updating feature flags, changing environment variables — that do not require recompiling the binary or rebuilding the Docker image. Without detection, every such commit triggers a full 10–15 minute CI run: compile, test, sonar scan, Docker build, image push — then deploy the same binary that was already running.
|
||||
|
||||
## Decision
|
||||
|
||||
Each build stage checks whether the Git diff contains only `*.yaml` file changes (no source code). If so, the binary build, Docker build, and image push are entirely skipped. The latest image tag is fetched from Google Artifact Registry (GAR) and used directly for the ArgoCD deployment. The full build runs only when source code changes are present.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Always run the full build**: Rejected — config rollouts would take 10–15 minutes when the only change is a YAML file, causing friction and delaying incident response (e.g., bumping a memory limit during an OOM incident).
|
||||
- **Separate repos for code and config**: Considered but rejected — splitting config into a separate repo adds operational complexity (two PRs for one change, out-of-sync risk) without proportional benefit.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Config rollouts (AppConfig changes, Helm value tweaks, memory limit bumps) complete in ~2 minutes instead of 10–15 minutes.
|
||||
- Reduces unnecessary Docker image churn — no new SHA for a commit that didn't change the binary.
|
||||
- Faster incident response: an engineer can bump `memory_request` during an OOM and see it deployed in minutes.
|
||||
|
||||
**Negative:**
|
||||
- The detection is heuristic — it checks file extensions, not semantic content. A YAML file that configures build behaviour (e.g., a hypothetical `.github/workflows/` file) would be misclassified as a config-only change.
|
||||
- The latest image tag from GAR must exist; if the previous build failed before pushing an image, a config-only change will fail to find a tag to deploy.
|
||||
|
||||
**Neutral:**
|
||||
- The config-only path still triggers the ArgoCD deployment steps — only the build and image push are skipped.
|
||||
|
||||
## Constraints
|
||||
|
||||
Build time was the primary constraint. Teams were complaining about slow feedback cycles for config changes. The detection logic was the minimal implementation that addressed this without a repo restructure.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/stages/buildGo.groovy:is_config_only_change_and_should_deploy_argo()` (reference implementation; similar logic exists in other build stages)
|
||||
- The TODO comment in `buildMaven.groovy:70` notes that appConfig changes also currently trigger a build — this is a known gap
|
||||
@@ -0,0 +1,48 @@
|
||||
# ADR-0006: Ringmaster as Mandatory Build Trigger Gate
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Jenkins is accessible to all engineers in the organisation. Without a trigger gate, any engineer can click "Build Now" in Jenkins and kick off a build — including a production deployment — without any audit trail, approval, or callback to downstream systems. Ringmaster is Meesho's internal deployment orchestration system that tracks every deployment: who triggered it, what tag was deployed, when, and whether it succeeded. Turbo-Turtle is the CI callback system that receives the build result and updates deployment state.
|
||||
|
||||
## Decision
|
||||
|
||||
`eksCICD.groovy` hard-rejects any build not triggered by `ringmaster-bot`, `turbo-turtle`, or a hardcoded DevOps allowedUsers list. The rejection is immediate and explicit — the build errors with a message directing the engineer to use Ringmaster instead. The string `"ringmaster-bot"` is the sole signal that routes CI callbacks correctly between Ringmaster and Turbo-Turtle.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Allow direct Jenkins triggers with a warning**: Rejected — a warning is easily ignored; the deployment would still bypass Ringmaster's tracking and the Turbo-Turtle callback would have no caller to report to.
|
||||
- **Restrict Jenkins UI access via RBAC**: Considered but not implemented — coarse-grained Jenkins RBAC would still allow authorized engineers to trigger builds directly, and doesn't solve the callback problem.
|
||||
- **Audit log only (no rejection)**: Rejected — audit-only does not prevent the problem; it only discovers it after the fact.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Every production deployment is tracked in Ringmaster — who triggered it, what tag, when — creating a full deployment ledger.
|
||||
- Turbo-Turtle always receives the CI result callback because the trigger is always one of the known callers.
|
||||
- Engineers cannot bypass deployment holds or approval flows by triggering Jenkins directly.
|
||||
|
||||
**Negative:**
|
||||
- DevOps engineers must maintain the hardcoded `allowedUsers` list for emergency access (e.g., debugging a pipeline issue directly from Jenkins).
|
||||
- The gate adds a hard dependency on Ringmaster being operational for any build to run.
|
||||
|
||||
**Neutral:**
|
||||
- The string `"ringmaster-bot"` is load-bearing — changing it without coordinating with the Ringmaster team would break the callback routing between Ringmaster and Turbo-Turtle.
|
||||
|
||||
## Constraints
|
||||
|
||||
Meesho's deployment audit and compliance requirements necessitated a full deployment ledger. The Ringmaster/Turbo-Turtle architecture was already in place; the gate was added to enforce its use.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `vars/eksCICD.groovy:12-26` — allowedUsers list and trigger validation
|
||||
- **Never change the string `"ringmaster-bot"`** without coordinating with the Ringmaster team — documented in CLAUDE.md under NEVER DO
|
||||
- Ringmaster UI: `https://ringmaster.meeshogcp.in/applications/cicd/home`
|
||||
@@ -0,0 +1,56 @@
|
||||
# ADR-0007: GitOps Deployments via Strict 4-Step ArgoCD Sync Sequence
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** INFRA
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Meesho migrated GCP service deployments from direct `kubectl apply` / Helm install to GitOps via ArgoCD. The key requirement was continuous reconciliation — the cluster state should always reflect what's in Git, and any manual `kubectl` changes should be automatically reverted. The deployment pipeline needed to update two separate Git repositories (argo-config for ArgoCD Application manifests, helm-repo for Helm chart values) and trigger ArgoCD to sync, without leaving the cluster in an inconsistent intermediate state.
|
||||
|
||||
## Decision
|
||||
|
||||
All GCP service deployments go through a strict 4-step sequence in `deployArgoCD.groovy`:
|
||||
|
||||
1. **`update_argo_repo`** — Push the updated ArgoCD Application manifest to devops-argo-config
|
||||
2. **`refresh_app_of_apps`** — Trigger ArgoCD to sync the app-of-apps, creating any new Application objects
|
||||
3. **`update_helm_repo`** — Push the new Helm chart (with the new image tag) to the Helm repo
|
||||
4. **`refresh_and_sync`** — Trigger ArgoCD to sync the specific application
|
||||
|
||||
The order is non-interchangeable. Steps 2 and 4 cannot be swapped.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Direct `kubectl apply`**: Rejected — any manual change to the cluster would persist indefinitely; no drift detection or automatic reconciliation.
|
||||
- **Helm install from Jenkins directly**: Rejected — Helm state would live only in the cluster's release history, not in Git; no GitOps audit trail or rollback via git revert.
|
||||
- **Skipping step 2 (app-of-apps refresh)**: Not a conscious alternative — the hard requirement emerged from debugging. For new services, step 2 must run before step 3 because the ArgoCD Application object doesn't exist yet; if step 3 (Helm push) runs first, step 4 (sync) targets a non-existent application and fails silently.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Every deployment is a Git commit — rollback is a git revert, and the cluster state is always reproducible from Git history.
|
||||
- ArgoCD continuously reconciles cluster state — manual `kubectl apply` changes are automatically reverted, preventing configuration drift.
|
||||
- Deployment failures are localised: the 4-step sequence makes it clear which step failed (argo-config push? app-of-apps refresh? Helm push? sync?) for faster debugging.
|
||||
|
||||
**Negative:**
|
||||
- The 4-step sequence is opaque without documentation — engineers debugging a deploy failure must know which step corresponds to which operation.
|
||||
- Steps 2 and 4 being non-interchangeable is tribal knowledge; swapping them for new services causes a silent sync failure that is hard to diagnose.
|
||||
- ArgoCD dependency: if ArgoCD is degraded, all deployments are blocked regardless of build success.
|
||||
|
||||
**Neutral:**
|
||||
- The sequence touches two separate Git repositories (devops-argo-config and helm-repo) in a single pipeline run — partial failures leave one repo updated and the other stale.
|
||||
|
||||
## Constraints
|
||||
|
||||
ArgoCD was the organisational standard for GCP deployments. The 4-step sequence was designed to handle both the case of existing services (steps 1, 3, 4 are the hot path) and new services being onboarded for the first time (step 2 is required to create the Application object before step 4 can sync it).
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/stages/deployArgoCD.groovy` (480+ lines) — all 4 steps are defined here
|
||||
- The step ordering constraint is documented in CLAUDE.md and `docs/tribal-knowledge.md` (TK#10) as load-bearing tribal knowledge
|
||||
- See also: `docs/wiki/pages/deploy/argocd-sync.md` for a detailed walkthrough of each step
|
||||
@@ -0,0 +1,56 @@
|
||||
# ADR-0008: Canary Deploy Mandatory for Tier-1 (sp0/up0) Services in Production
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** RELIABILITY
|
||||
**Date decided:** Mid-project
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Meesho's services are classified by priority tier (`sp0`, `up0`, `sp1`, `up1`, etc.). Tier-1 services (`sp0`/`up0`) handle the highest traffic volumes and are critical to core business flows. A bad deploy that hits 100% of production traffic on a Tier-1 service has a catastrophic blast radius — full outage, revenue impact, and customer-facing failure. Teams were inconsistently configuring canary rollouts: some enabled them, some skipped them, and some configured them with `skipAnalysis: true` which bypasses the automated rollout analysis.
|
||||
|
||||
This inconsistency was the contributing factor in at least one production incident where a bad deploy on a Tier-1 service reached full traffic before the issue was detected.
|
||||
|
||||
## Decision
|
||||
|
||||
`deployArgoCD.groovy` enforces canary deployment as a hard requirement for all services with `priority_v2: sp0` or `priority_v2: up0` deploying to the `prd` environment. The enforcement checks:
|
||||
1. `canary.enabled: true` must be set
|
||||
2. `canary.skipAnalysis: false` — analysis cannot be bypassed
|
||||
3. `canary.enableManualPromotion: true` — a human must promote the canary to full traffic
|
||||
|
||||
If any of these conditions are not met, the deployment is blocked with an explicit error: `"Enable canary and retry"`. Enforcement is applied at the library level — service teams cannot override it.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Documentation and guidelines only**: Rejected — teams were already aware of canary best practices but inconsistently applied them; a documented recommendation had failed to produce uniform behaviour.
|
||||
- **Enforcement in Ringmaster only**: Considered but rejected — enforcement at the library level means it applies to all deploy paths, including any future tooling that calls `deployArgoCD.groovy`.
|
||||
- **Enforce for all services, not just sp0/up0**: Considered but rejected as too disruptive — lower-priority services have smaller blast radii and the overhead of canary analysis was not justified for all tiers.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Tier-1 bad deploys cannot reach 100% of production traffic without a human promotion step.
|
||||
- Canary analysis (metrics, error rate) runs automatically before promotion, catching regressions before they impact all users.
|
||||
- Enforcement is consistent across all Tier-1 services — no team can skip it.
|
||||
|
||||
**Negative:**
|
||||
- Canary rollouts add time to Tier-1 deployments — promotion requires human action, which can delay hotfixes.
|
||||
- The hotfix path (`hotfix/*` branches) sets `skipAnalysis: true` to allow bypassing canary analysis in emergencies, which re-introduces the risk for the hotfix scenario.
|
||||
- Services that newly cross the sp0/up0 threshold must configure canary before their next prd deploy or they will be blocked.
|
||||
|
||||
**Neutral:**
|
||||
- The enforcement only applies to non-cron, non-worker, non-scheduler, non-consumer deployments — background jobs are excluded.
|
||||
|
||||
## Constraints
|
||||
|
||||
A production incident on a Tier-1 service drove this decision. Post-incident, the risk of leaving canary configuration to team discretion was deemed unacceptable. The library-level enforcement was the fastest way to guarantee coverage across all affected services without requiring each team to update their configuration proactively.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/stages/deployArgoCD.groovy:407-429` — canary enforcement block
|
||||
- Hotfix bypass: `value_binding1['canary']['skipAnalysis'] = (env.hot_fix) ? true : ...` at line 355 — hotfixes can bypass canary analysis
|
||||
- The `addHeadless` flag and Node services are also excluded from enforcement
|
||||
@@ -0,0 +1,53 @@
|
||||
# ADR-0009: JVM Heap Auto-Derived from Pod memory_request
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** RELIABILITY
|
||||
**Date decided:** Mid-project
|
||||
**Date documented:** 2026-05-12
|
||||
|
||||
## Context
|
||||
|
||||
Java services running in Kubernetes pods are subject to two memory limits: the pod's `memory_limit` (enforced by the kubelet — exceed it and the pod is OOM-killed) and the JVM's heap size (`-Xmx`). By default, the JVM sets heap to 1/4 of the physical RAM it detects — but inside a container, it detects the node's physical RAM, not the pod's memory limit. A Java service in a pod with `memory_limit: 2Gi` running on a 64Gi node would default to a 16Gi heap, far exceeding its limit and triggering immediate OOM kill.
|
||||
|
||||
Meesho had multiple incidents where Java services were OOM-killed because:
|
||||
1. The JVM was using the wrong default (node RAM, not pod limit)
|
||||
2. Teams were setting `-Xmx` manually but forgetting to update it when `memory_request` changed
|
||||
3. Teams were setting `-Xmx` too high, causing heap to exceed the pod limit
|
||||
|
||||
## Decision
|
||||
|
||||
`deployArgoCD.groovy` automatically computes `xms` and `xmx` from the service's `memory_request` value in `deployment.yaml`. The derived values are injected into the Helm chart at deploy time. Services do not need to set `-Xmx` in `JAVA_OPTS` manually. The escape hatch `jvm_memory_override: true` in `deployment.yaml` allows a service to opt out and set its own JVM flags.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Require teams to set -Xmx manually**: Rejected — teams consistently forgot to update `-Xmx` when changing `memory_request`, causing OOM kills after pod resource changes. This happened across multiple services.
|
||||
- **JVM container awareness flag (-XX:+UseContainerSupport)**: This flag (available in JDK 11+) allows the JVM to read the cgroup limit instead of physical RAM. Not adopted as the primary solution because it requires all services to use JDK 11+ and the flag needs to be explicitly set in each service's startup config — still a per-service manual step.
|
||||
- **Fixed default heap values**: Rejected — services have wildly different memory requirements; a fixed default would be wrong for most.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Java services cannot be OOM-killed due to JVM heap misconfiguration — the heap is always proportional to the pod's actual memory allocation.
|
||||
- Teams never need to update `-Xmx` manually when changing `memory_request` — the library keeps them in sync automatically.
|
||||
- Eliminates a whole class of incident: "service OOM-killed because someone bumped memory_request but forgot to update -Xmx."
|
||||
|
||||
**Negative:**
|
||||
- The auto-derived heap may not be optimal for services with unusual heap vs non-heap memory ratios (e.g., services with large off-heap caches). These services must use `jvm_memory_override: true`.
|
||||
- The calculation logic is not immediately transparent to service teams — they may not know why their `-Xmx` is what it is.
|
||||
|
||||
**Neutral:**
|
||||
- If `deployment_args` already contains an `-Xmx` or `-Xms` flag, the auto-derive reads and preserves those values rather than overwriting them. `jvm_memory_override` is the clean opt-out for services that need full control.
|
||||
|
||||
## Constraints
|
||||
|
||||
Repeated OOM incidents across multiple Java services drove this decision. The library-level fix was preferred over per-service remediation because the root cause was systemic (wrong JVM defaults in containers) and would recur as long as teams configured heap manually.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
**Still appropriate** — no changes needed.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key file: `src/com/meesho/stages/deployArgoCD.groovy:205-268` — `xms`/`xmx` calculation
|
||||
- Opt-out: set `jvm_memory_override: true` in `deployment.yaml` to manage JVM flags manually
|
||||
- Documented in CLAUDE.md: "JVM memory: `deployArgoCD.groovy` auto-calculates `xmx`/`xms` from pod `memory_request`."
|
||||
@@ -0,0 +1,59 @@
|
||||
# ADR-0010: Cloud-and-branch namespaced artifact paths
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** DATA
|
||||
**Date decided:** Project inception
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Every service build in `devops-lib` produces a deployable artifact (Maven JAR / language-equivalent) plus a Docker image; the pipeline must decide where to store these and whether a re-run on the same commit can short-circuit a rebuild. The library was written when both AWS and GCP backends were on the table, so the storage layer was parameterised by provider scheme; in practice the platform has since standardised on GCP and the `s3://` code paths are vestigial. PRs and release branches share the same Jenkins jobs but have very different trust levels — a PR artifact must never be promotable to a production tag.
|
||||
|
||||
## Decision
|
||||
|
||||
Artifacts are stored under fully-namespaced paths of the form `<scheme>://<bucket>/<repo_name>/<branch_name>/<TAG>/`, where `<scheme>` is `gs://` in active use (`s3://` branches remain in the codebase but are not executed today). Release branches (`main` / `master` / `gcp-main`) and `develop` reuse cached artifacts on re-run via `checkS3()`; PR builds intentionally bypass that check and force a fresh build every time.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated by the team during this interview. The branch-namespaced layout was the day-one design and has not been revisited.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- A PR's image cannot be confused with a release image at the bucket-path level — provides a structural guarantee against accidental promotion.
|
||||
- Re-running a build on the same release branch is free (artifact reuse), keeping incremental commits cheap.
|
||||
- Multi-cloud scheme prefix is harmless even when only one cloud is active; switching back would be a config change, not a rewrite.
|
||||
|
||||
**Negative:**
|
||||
- `s3://` code paths sit unused in `buildMaven.groovy` and friends — invisible tech debt that confuses new readers and grows the surface area for stale-config bugs.
|
||||
- The `(branch_name, TAG)` key means renaming a branch or rebasing a PR can leak artifacts into the wrong namespace if `branch_name` is computed loosely.
|
||||
|
||||
**Neutral:**
|
||||
- Cache reuse is implicit (artifact-exists ⇒ skip build) rather than declared — see ADR-0005 for the config-only fast-path that uses the same mechanism.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Branch identity is the cache key, so `branch_name` must be a stable string for the lifetime of a build chain. PR-target detection (`env.CHANGE_ID`) is load-bearing here.
|
||||
- AWS code paths exist for historical reasons; today's platform is GCP-only and the team has not undertaken a cleanup pass.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Adequate with caveats** — the strategy is sound; the vestigial AWS branches are noise that should be removed in a separate cleanup.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0005: Config-Only Change Detection — Skip Binary Build, Reuse Latest Image](0005-config-only-change-detection-skip-build.md) — the same artifact-existence check powers the config-only fast-path.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/buildMaven.groovy`
|
||||
- The `s3://` branches in `buildMaven.groovy` are dead code in current production — flag for cleanup, not for documentation as an alternative.
|
||||
- Discovery id: DATA-1
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: DATA-1
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:10:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,66 @@
|
||||
# ADR-0011: Build-user identity routes post-build callbacks
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** COMMUNICATION
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Once a build completes, the pipeline must (a) notify the service team, and (b) tell the deployment-orchestration layer that a new image is ready to roll out. Multiple orchestrators consume this signal — Ringmaster and Turbo-Turtle (the actively maintained CD systems), the legacy Deployment Tracker (still backing some downstream tooling), and a toolchain-environment build-callback service used by Node toolchain builds. Each consumer has a different API contract, a different Slack message shape, and a different policy on whether the team channel should be notified at all. The shared library is invoked by both bot-driven CD (Ringmaster / Turbo-Turtle trigger builds via dedicated bot users) and human-driven Jenkins runs (manual deploys, hotfixes, retries by anyone with allowedUsers), so there is no single contract that fits all callers.
|
||||
|
||||
## Decision
|
||||
|
||||
`notify.groovy` routes by the Jenkins `build_user`, which is set by whoever triggered the build:
|
||||
|
||||
- `build_user == "ringmaster-bot"` or `"turbo-turtle"` → call `deployRingmaster.run()` and emit a Ringmaster-flavored Slack message (production gets a richer message with the deploy URL); the generic team notification is suppressed.
|
||||
- Any other user (manual deploy, hotfix, retry) → fall through to a generic `slackSend` on the team's notify channel.
|
||||
- `INFRA_ENV == 'toolchain'` → take a completely separate path that POSTs to the toolchain build-callback service and returns early, bypassing the build-user routing entirely.
|
||||
|
||||
In parallel, for release branches (`gcp-main` / `main` / `gcp-master` / `master` / `farmiso-main`), the legacy Deployment Tracker (`postTrackingApi`) is always called, and the Ringmaster history-DB (`postTrackingRingmasterApi`) is called when `env.SERVICES` is set.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated during this interview. Routing on `build_user` was chosen because it is the only signal available without coordinating an extra config flag with every consumer service.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- New consumers can plug in without re-wiring every consumer service's `config.yaml` — they only need to claim a dedicated bot identity.
|
||||
- The legacy Deployment Tracker path stays intact for backward compatibility while new traffic flows through Ringmaster.
|
||||
- Manual / human-triggered builds get the generic Slack notification path so engineers always see a team-channel message regardless of CD orchestrator.
|
||||
|
||||
**Negative:**
|
||||
- The routing is implicit and not documented in `config.yaml` — a reader of a service's pipeline cannot tell which CD orchestrator will get the callback without grepping `notify.groovy`.
|
||||
- Renaming or replacing either bot identity is breaking: the strings `"ringmaster-bot"` and `"turbo-turtle"` are hard-coded compare targets (the existing `NEVER DO` list in `CLAUDE.md` calls this out for `ringmaster-bot`).
|
||||
|
||||
**Neutral:**
|
||||
- The `INFRA_ENV == 'toolchain'` branch sits outside the build-user routing — it is a parallel routing axis (build environment, not trigger identity).
|
||||
|
||||
## Constraints
|
||||
|
||||
- Caller identity is the only signal available at notify time — no config flag is in scope.
|
||||
- Strict string comparison on bot usernames couples this code to Ringmaster / Turbo-Turtle naming.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Adequate with caveats** — the routing works and survives new CD orchestrators being added, but the dispatch should ideally be table-driven rather than chained `if`s, and the bot-name strings should be configurable rather than hard-coded.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0006: Ringmaster as Mandatory Build Trigger Gate](0006-ringmaster-mandatory-build-trigger-gate.md) — Ringmaster also gates the *trigger* side of builds; this ADR covers the *callback* side.
|
||||
- [ADR-0007: GitOps Deployments via Strict 4-Step ArgoCD Sync Sequence](0007-gitops-via-argocd-4-step-sync-sequence.md) — the deploy mechanism that Ringmaster invokes downstream.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/notify.groovy`, `src/com/meesho/stages/deployRingmaster.groovy`
|
||||
- The release-branch dual-write (`postTrackingApi` + `postTrackingRingmasterApi`) coexists with the build-user routing but is orthogonal to it (gated on `BRANCH_NAME` and `env.SERVICES`).
|
||||
- Discovery id: COMMUNICATION-1
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: COMMUNICATION-1
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:15:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,61 @@
|
||||
# ADR-0012: String-interpolated Helm values from user config.yaml
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** COMMUNICATION
|
||||
**Date decided:** Project inception
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
ArgoCD deploys are driven by Helm charts whose `values.yaml` files are produced per-build by `deployArgoCD.groovy`. The inputs are a service's `deployment.yaml` plus a handful of pipeline-derived fields (image tag, pod resources, JVM heap, etc.). Helm chart `values.yaml` is itself a templating surface — Helm's own `{{ .Values.x }}` syntax reads these files at install time — so anything produced here is interpreted as a template by the next layer, not as plain data.
|
||||
|
||||
## Decision
|
||||
|
||||
`deployArgoCD.groovy` builds the rendered `values.yaml` by string-substituting fields into a stub template via chained `.replaceAll()` calls, rather than constructing it via a YAML library or a typed DTO and re-serialising. The team treats Helm's chart-values surface as a template-on-template stack: re-serialising via a YAML library would re-introduce template-syntax escaping problems (quoting `{{ }}`, preserving multi-line string semantics, handling Helm-specific structural tags) — keeping the entire pipeline string-native is the simpler invariant.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated by the team during this interview. The "use a typed YAML library / DTO and re-serialise" approach was acknowledged as the obvious counter-proposal but was rejected on the template-on-template grounds above.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- The renderer stays a one-layer string substitution — easy to read, easy to debug from a Jenkins console log.
|
||||
- No risk of a typed re-serialisation silently re-escaping Helm `{{ }}` templates or stripping comments.
|
||||
- Matches the rest of the pipeline, which is string-and-`sh` heavy.
|
||||
|
||||
**Negative:**
|
||||
- User-supplied config values are not validated against a schema before substitution — a service's `deployment.yaml` containing unescaped quotes, colons, or newlines can produce a malformed `values.yaml` (review-learnings PR #343 flagged this).
|
||||
- The implicit YAML-injection risk depends on every consumer service writing well-formed `deployment.yaml` — there is no guardrail in the library itself.
|
||||
- Adding a new field requires editing the template stub AND the substitution chain in `deployArgoCD.groovy` together — easy to drift.
|
||||
|
||||
**Neutral:**
|
||||
- The decision lives entirely in `deployArgoCD.groovy`; switching strategies in future would be local to that file.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Helm chart values are interpreted as templates downstream — any solution must preserve template literals without escaping them.
|
||||
- The shared library runs in the Jenkins sandbox, which constrains which Java / Groovy serialisation APIs are safely callable.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Adequate with caveats** — the strategy is defensible, but the lack of input-validation guardrails (the YAML-injection surface called out in review learnings) remains an open risk. A targeted schema-validation pass before substitution would mitigate it without changing the rendering strategy.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0007: GitOps Deployments via Strict 4-Step ArgoCD Sync Sequence](0007-gitops-via-argocd-4-step-sync-sequence.md) — the deploy mechanism that consumes the rendered `values.yaml`.
|
||||
- [ADR-0009: JVM Heap Auto-Derived from Pod memory_request](0009-jvm-heap-auto-derived-from-pod-memory-request.md) — another pipeline-derived input to the same `values.yaml`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/deployArgoCD.groovy`
|
||||
- Open risk: review-learnings PR #343 flagged the YAML-injection surface — input validation is the recommended mitigation.
|
||||
- Discovery id: COMMUNICATION-2
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: COMMUNICATION-2
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:18:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,62 @@
|
||||
# ADR-0013: Multi-zone deployables gated out of direct Jenkins ArgoCD
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** RELIABILITY
|
||||
**Date decided:** Alongside the multi-zone initiative
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Meesho ran a multi-zone initiative to deploy production services across two GCP zones in parallel and split traffic between them. The initiative immediately exposed that not every workload type is safe to multiply across zones: schedulers, cron jobs, and consumers cause correctness issues when more than one zone runs them at the same time; `cache` and `database` service types are explicitly blocked by Turbo-Turtle's `ValidateDeploymentConfigActivity`; some workloads (e.g. Deepgram, with volume affinity that pins it to a single zone) can't go multi-zone at all. Meanwhile, `deployArgoCD.groovy` in this shared library only knows how to call `argocd sync` against a single application — it has no concept of split-by-service-type or per-zone sequencing.
|
||||
|
||||
## Decision
|
||||
|
||||
`deployArgoCD.groovy` fetches the `multizone-enabled-repos` whitelist from `Meesho/whitelists` at the start of every build. If the current deployable is on the list, the pipeline aborts with `"Multi-zone enabled for this deployable. Please use Ringmaster for deployment."` rather than attempting a sync. Multi-zone-enabled services are required to go through Ringmaster, which owns the split-deploy logic, per-service-type validation, and zone-affinity awareness.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated by the team during this interview. The gate was introduced alongside the multi-zone initiative itself, not retrofitted after an incident.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Direct Jenkins ArgoCD is structurally incapable of getting multi-zone wrong because it never gets the chance — the gate fails closed.
|
||||
- The split-deploy logic and the service-type-validation logic live in exactly one place (Ringmaster); we do not maintain two copies.
|
||||
- A team can flip a service to multi-zone by adding it to `multizone-enabled-repos.yaml` without modifying `devops-lib` — the policy change takes effect on the next build.
|
||||
|
||||
**Negative:**
|
||||
- Engineers who routinely use `cicd-` Jenkins jobs see an unfamiliar refusal once their service is added to the whitelist; the error message is the only signal pointing them at Ringmaster.
|
||||
- The library has no way to attempt a partial deploy or to surface what would-have-been-deployed; the gate is binary.
|
||||
|
||||
**Neutral:**
|
||||
- The decision lives across two repos — this code in `devops-lib`, the source-of-truth list in `Meesho/whitelists`. See ADR-0003 for the broader policy-in-a-separate-repo pattern.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Multi-zone correctness requires service-type awareness that the Jenkins ArgoCD path does not have.
|
||||
- The whitelist is fetched fresh per build (ADR-0004), so policy changes in `Meesho/whitelists` take effect on the next build with no library release.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Still appropriate** — fail-closed gating is the right posture for a capability the library cannot safely implement.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0003: Policy Exceptions Controlled by a Separate Meesho/whitelists Repo](0003-policy-exceptions-in-separate-whitelist-repo.md) — explains the whitelist-source-of-truth pattern this gate uses.
|
||||
- [ADR-0004: Whitelist Cloned Fresh on Every Build — No Caching](0004-fresh-whitelist-clone-per-build.md) — why this list takes effect immediately.
|
||||
- [ADR-0006: Ringmaster as Mandatory Build Trigger Gate](0006-ringmaster-mandatory-build-trigger-gate.md) — Ringmaster's parallel role on the trigger side.
|
||||
- [ADR-0011: Build-user identity routes post-build callbacks](0011-build-user-identity-routes-post-build-callbacks.md) — the callback flow that Ringmaster uses on success.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/deployArgoCD.groovy`
|
||||
- Whitelist source: `https://github.com/Meesho/whitelists/blob/main/multizone-enabled-repos.yaml`
|
||||
- Discovery id: RELIABILITY-1
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: RELIABILITY-1
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:21:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,58 @@
|
||||
# ADR-0014: Open Dependabot CRITICAL alerts block builds
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** RELIABILITY
|
||||
**Date decided:** Mid-project
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Every Meesho service ships its source dependencies as part of its container image. GitHub's Dependabot continuously scans those dependency manifests against the GitHub Advisory Database and surfaces alerts by severity. Without an enforcement teeth in the pipeline, CRITICAL CVEs can sit open for arbitrary time — merge gates can be bypassed (admin merges, hotfixes, repos that don't enforce branch protection), and a periodic audit only tells security org-wide rather than putting accountability on the team that's actively trying to ship.
|
||||
|
||||
## Decision
|
||||
|
||||
`deployArgoCD.groovy:dependabotCriticalCheck()` calls the GitHub Dependabot Alerts API for the repo on every build. If the API returns any open alert with severity `CRITICAL`, the pipeline aborts with `"Critical vulnerabilities found in repo: <name>. Please resolve the alerts marked with CRITICAL here and retry: <github dependabot URL>"`. There is no whitelist, no override flag, and no manual bypass available from the pipeline side.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
N/A — organizational mandate. The Meesho security org required hard enforcement of the CRITICAL-CVE SLA on shipping code; the build-time gate is the implementation choice that satisfies it.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- The deploying team is forced to act on the CVE (fix, escalate, or coordinate with the dependency owner) before they can ship — accountability lands on whoever is actively trying to push, not on a central security team.
|
||||
- Confirmed firing in practice on real services (Farmiso-Backend, supplier_platform_insights, meesho-web-reels, supplier_platform_payouts as seen in #devops-tech) — the gate is not theoretical.
|
||||
|
||||
**Negative:**
|
||||
- Engineers blocked by a CRITICAL alert mid-deploy have no in-pipeline bypass — even for hotfixes that are unrelated to the vulnerable dependency.
|
||||
- The check depends on GitHub Dependabot's classification; a false-positive at CRITICAL would force a real outage detour.
|
||||
|
||||
**Neutral:**
|
||||
- The check is per-build, not per-PR, so a recently-disclosed CVE can suddenly start blocking deploys for repos that have not changed.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Meesho security org policy: open CRITICAL Dependabot alerts must be resolved before code is shipped. The build-time gate is the enforcement surface for that policy.
|
||||
- Dependabot must remain enabled on every repo for the gate to be meaningful; that side of the policy lives in repo settings, not in this library.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Still appropriate** — the mandate stands and the gate is the correct enforcement point.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0006: Ringmaster as Mandatory Build Trigger Gate](0006-ringmaster-mandatory-build-trigger-gate.md) — another build-time hard gate; same architectural pattern of "fail closed at the pipeline boundary."
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/deployArgoCD.groovy`
|
||||
- The check is unconditional — there is no whitelist of repos exempted from this gate.
|
||||
- Discovery id: RELIABILITY-2
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: RELIABILITY-2
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:23:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,59 @@
|
||||
# ADR-0015: Node install logic paired across buildNode.groovy and Dockerfile
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** PATTERN
|
||||
**Date decided:** Early on
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Meesho Node services use both `npm` and `pnpm` depending on the repo. Some teams need to pass extra flags (e.g. `--legacy-peer-deps`) and the shared library cannot anticipate every combination. The install step has to run inside the Docker build (the image needs `node_modules` baked in), but the choice of *which* package manager and *which* flags to run is information the Dockerfile alone can't recover from a clean container — it has to be told.
|
||||
|
||||
## Decision
|
||||
|
||||
The install step is split deliberately across the two files. `src/com/meesho/stages/buildNode.groovy` does the detection: it reads `config.yaml`, sees whether the repo uses `npm` or `pnpm`, and resolves the optional `npm_install_arg` override; it then passes the resolved values to `resources/com/meesho/node-Dockerfile` as Docker build-args. The Dockerfile does the execution: it consumes those build-args and runs the actual install, falling back to a sane default in an `else` branch when no value was passed. Detection lives at the layer that can see the config; execution lives at the layer that runs inside the container.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated by the team during this interview. The split mirrors what is naturally separable — config interpretation vs runtime install — and has not been revisited.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Teams can override the install command for their service through `npm_install_arg` in `config.yaml` without forking the library Dockerfile — observed in #devops-tech (Nov 2025) when a team added `--legacy-peer-deps` purely via config.
|
||||
- The Dockerfile's `else` fallback means a developer can `docker build` locally without Jenkins or `config.yaml` and still get a working install.
|
||||
- Neither layer needs to know more than it actually does: Groovy doesn't run `npm`, the Dockerfile doesn't read YAML.
|
||||
|
||||
**Negative:**
|
||||
- Any change to install behaviour has to land in both files at once (tribal-knowledge #11). The pair-edit invariant is real but has not yet caused a production incident.
|
||||
- Build-args are stringly-typed, so a typo on either side fails late — at install time inside the Docker build.
|
||||
|
||||
**Neutral:**
|
||||
- The "detection in Groovy, execution in Dockerfile" split is mirrored elsewhere in `devops-lib` (e.g. Maven, Go); Node just happens to be the most config-driven and therefore the most visible example.
|
||||
|
||||
## Constraints
|
||||
|
||||
- The package-manager choice has to be visible at Docker-build time inside the container; build-args are the cleanest way to inject it.
|
||||
- The library must support repos that build locally (no Jenkins, no `config.yaml`) for developer ergonomics.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Still appropriate** — the split works, the override knob is being used as intended, and no incident has surfaced. A future hardening could add a CI check that any PR touching the install logic on one side also touches the other, but it is not load-bearing today.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
None directly. The "detection in Groovy, execution in container" split is a pattern repeated in other build stages (Maven, Go), but each has its own per-language ADR scope.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/stages/buildNode.groovy`, `resources/com/meesho/node-Dockerfile`
|
||||
- `npm_install_arg` in `config.yaml` is a documented public extension point — see service-team usage in #devops-tech (Nov 2025) for the `--legacy-peer-deps` case.
|
||||
- Discovery id: PATTERN-2
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: PATTERN-2
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:28:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,60 @@
|
||||
# ADR-0016: Per-environment Helm chart versioning (values_v2 vs values_v3)
|
||||
|
||||
**Status:** Accepted
|
||||
**Category:** INFRA
|
||||
**Date decided:** During the multi-zone initiative
|
||||
**Date documented:** 2026-05-13
|
||||
|
||||
## Context
|
||||
|
||||
Production was the first (and so far only) environment where Meesho rolled out multi-zone deployments — running services in two GCP zones in parallel with split-deploy and per-service-type validation (see ADR-0013). Multi-zone awareness required a new shape of Helm chart values: per-zone overlay files (`gcp-ase1a-values.yaml`, etc.), service-type metadata, and additional tuning. Lower environments (`stg`, `int`, `ftr`) do not run multi-zone today and have no plan to. Forcing them onto the multi-zone-shaped chart would either require multi-zone setup they don't have, or carry chart fields they never use.
|
||||
|
||||
## Decision
|
||||
|
||||
`constructParam.groovy` picks the Helm chart values path per environment: `helmChartsPath = (env == 'prd') ? 'values_v3' : 'values_v2'`. Production reads from `values_v3/` in `devops-helm-charts` (multi-zone-aware); every other environment reads from `values_v2/` (single-zone). Both versions are actively maintained — `values_v2` is the live chart for non-prod, not a deprecated legacy path.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
No alternatives were explicitly evaluated by the team during this interview. A unified chart that handles both single-zone and multi-zone via conditional logic was the obvious counter-proposal but was not taken; the team kept the two versions side-by-side instead.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Non-prod chart stays simple — no multi-zone-only fields polluting the values tree for engineers and reviewers who don't need them.
|
||||
- Multi-zone schema can evolve in `values_v3` without coordinating breaking changes against non-prod chart consumers.
|
||||
- Production deploys exercise a chart shape that matches production's runtime topology exactly; no "unused config" surface.
|
||||
|
||||
**Negative:**
|
||||
- A service that exists in prd has its values defined in two places (`values_v2/<bu>/.../values.yaml` and `values_v3/<bu>/.../values.yaml`); a config change relevant to both has to land in both, and drift between them is silent.
|
||||
- Engineers debugging an issue have to know which version their environment uses before they can find the right file.
|
||||
|
||||
**Neutral:**
|
||||
- The decision lives in one line of `constructParam.groovy`; flipping a new env onto `values_v3` is trivial when its multi-zone story is ready.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Multi-zone requires per-zone overlay files and service-type validation that `values_v2` does not have.
|
||||
- Non-prod environments do not run multi-zone and have no roadmap to; the v3 shape would carry dead configuration there.
|
||||
|
||||
## Current Assessment
|
||||
|
||||
- **Adequate with caveats** — splitting by environment maps cleanly to the runtime topology, but the dual maintenance burden grows linearly with active services and is invisible to anyone not in the DevOps loop. Worth revisiting if/when non-prod gains multi-zone.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-0013: Multi-zone deployables gated out of direct Jenkins ArgoCD](0013-multi-zone-deployables-gated-out-of-direct-jenkins-argocd.md) — the multi-zone initiative that drove the `values_v3` rollout.
|
||||
- [ADR-0007: GitOps Deployments via Strict 4-Step ArgoCD Sync Sequence](0007-gitops-via-argocd-4-step-sync-sequence.md) — the deploy mechanism that consumes whichever chart values path is selected here.
|
||||
|
||||
## Notes
|
||||
|
||||
- Key files: `src/com/meesho/utilities/constructParam.groovy`
|
||||
- Chart source: `https://github.com/Meesho/devops-helm-charts` — `values_v2/` and `values_v3/` are sibling top-level directories.
|
||||
- Discovery id: INFRA-1
|
||||
|
||||
<!-- adr-generator-meta
|
||||
discovery_id: INFRA-1
|
||||
run_id: 90b9a400-266c-4786-b793-d811efa99276
|
||||
last_completed_at: 2026-05-13T12:31:00Z
|
||||
mode: create
|
||||
cache_uri: gs://ai-blitz-agent-readability/adr/devops-lib/adr-discovery.json
|
||||
-->
|
||||
@@ -0,0 +1,43 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
This directory contains Architecture Decision Records (ADRs) for devops-lib.
|
||||
These were retroactively documented on 2026-05-12 through codebase analysis and developer interviews.
|
||||
|
||||
## What is an ADR?
|
||||
|
||||
An Architecture Decision Record captures an important architectural decision along with its context, alternatives, and consequences. They help new team members understand WHY the system is built the way it is — not just what it does.
|
||||
|
||||
## Decisions
|
||||
|
||||
| # | Decision | Category | Status | Assessment |
|
||||
|---|----------|----------|--------|------------|
|
||||
| [0001](0001-single-shared-library-for-all-services.md) | Single shared library consumed by all services | PATTERN | Accepted | Still appropriate |
|
||||
| [0002](0002-branch-name-as-sole-environment-selector.md) | Branch name as sole environment selector | PATTERN | Accepted | Still appropriate |
|
||||
| [0003](0003-policy-exceptions-in-separate-whitelist-repo.md) | Policy exceptions in separate Meesho/whitelists repo | PATTERN | Accepted | Still appropriate |
|
||||
| [0004](0004-fresh-whitelist-clone-per-build.md) | Whitelist cloned fresh on every build — no caching | PATTERN | Accepted | Still appropriate |
|
||||
| [0005](0005-config-only-change-detection-skip-build.md) | Config-only change detection — skip build, reuse latest image | PATTERN | Accepted | Still appropriate |
|
||||
| [0006](0006-ringmaster-mandatory-build-trigger-gate.md) | Ringmaster as mandatory build trigger gate | PATTERN | Accepted | Still appropriate |
|
||||
| [0007](0007-gitops-via-argocd-4-step-sync-sequence.md) | GitOps via strict 4-step ArgoCD sync sequence | INFRA | Accepted | Still appropriate |
|
||||
| [0008](0008-canary-mandatory-for-tier1-services-in-prd.md) | Canary deploy mandatory for Tier-1 (sp0/up0) services in prd | RELIABILITY | Accepted | Still appropriate |
|
||||
| [0009](0009-jvm-heap-auto-derived-from-pod-memory-request.md) | JVM heap auto-derived from pod memory_request | RELIABILITY | Accepted | Still appropriate |
|
||||
| [0010](0010-cloud-and-branch-namespaced-artifact-paths.md) | Cloud-and-branch namespaced artifact paths | DATA | Accepted | Adequate with caveats |
|
||||
| [0011](0011-build-user-identity-routes-post-build-callbacks.md) | Build-user identity routes post-build callbacks | COMMUNICATION | Accepted | Adequate with caveats |
|
||||
| [0012](0012-string-interpolated-helm-values-from-user-config.md) | String-interpolated Helm values from user config.yaml | COMMUNICATION | Accepted | Adequate with caveats |
|
||||
| [0013](0013-multi-zone-deployables-gated-out-of-direct-jenkins-argocd.md) | Multi-zone deployables gated out of direct Jenkins ArgoCD | RELIABILITY | Accepted | Still appropriate |
|
||||
| [0014](0014-open-dependabot-critical-alerts-block-builds.md) | Open Dependabot CRITICAL alerts block builds | RELIABILITY | Accepted | Still appropriate |
|
||||
| [0015](0015-node-install-logic-paired-across-buildnode-groovy-and-dockerfile.md) | Node install logic paired across buildNode.groovy and Dockerfile | PATTERN | Accepted | Still appropriate |
|
||||
| [0016](0016-per-environment-helm-chart-versioning.md) | Per-environment Helm chart versioning (values_v2 vs values_v3) | INFRA | Accepted | Adequate with caveats |
|
||||
|
||||
## How to use
|
||||
|
||||
- **New to the project?** Read these to understand why things are the way they are before touching the code.
|
||||
- **Making a change?** Check if an existing ADR covers the area you're modifying — especially ADR-0003 (whitelists), ADR-0004 (whitelist caching), ADR-0007 (ArgoCD sequence order), ADR-0013 (multi-zone gating), ADR-0014 (Dependabot CRITICAL gate).
|
||||
- **Making a new architectural decision?** Create a new ADR using the template in an existing file as a guide.
|
||||
|
||||
## Adding a new ADR
|
||||
|
||||
1. Copy an existing ADR as a template
|
||||
2. Use the next sequential number (currently: 0017)
|
||||
3. Fill in all sections — if you don't have info for a section, say so rather than leaving it blank
|
||||
4. Get a review from the DevOps Platform team lead
|
||||
5. Add a row to the table above
|
||||
Reference in New Issue
Block a user