# Bookstore — Part 01 ch.08 "Deployment strategies": MANUAL CANARY of catalog.
#
# Label/selector scheme (the whole trick):
#   * ONE Service `catalog` selects `app: catalog` ONLY (no `track`), so it
#     load-balances across BOTH Deployments' Pods.
#   * Two Deployments, catalog-stable and catalog-canary. Each Pod is labelled
#     `app: catalog` (Service-selected, shared) PLUS `track: stable|canary`
#     (lets us scale/observe each independently).
#   * Each Deployment's selector.matchLabels includes the track label so a
#     Deployment owns ONLY its own Pods (selectors must not overlap).
#   * Traffic split == replica ratio (stable:canary). Ramp/abort = `kubectl
#     scale` only — no traffic config. This is the MANUAL form; automated,
#     metric-gated, weighted progressive delivery is Part 07 ch.05.
#
# This is a teaching VARIANT of catalog, separate from 10-catalog-deploy.yaml
# (the canonical catalog). Do NOT run both stacks at once — both define
# `app: catalog` Pods in the `bookstore` namespace.
#
# Part 05 SECURITY LAYER (ch.01 + ch.02 — additive only; nothing above changed,
# only the fields below were ADDED to BOTH Pod templates). The image is the
# SAME distroless Go `bookstore/catalog:dev` as 10-catalog-deploy.yaml, so it
# takes the IDENTICAL restricted hardening (the `bookstore` namespace enforces
# pod-security.kubernetes.io/enforce: restricted, 00-namespace.yaml — every Pod
# in it, this canary variant included, must comply or PSA rejects it):
#  • ch.01 serviceAccountName: catalog-sa + automountServiceAccountToken: false
#    (same identity as 10-; the canary is still the catalog app, no API calls).
#  • ch.02 pod SC runAsNonRoot/runAsUser 65532 + seccomp RuntimeDefault;
#    container SC drop ALL + allowPrivilegeEscalation:false +
#    readOnlyRootFilesystem:true; + the `scratch` emptyDir at /tmp/cache that
#    the catalog binary needs under a read-only root FS (mirrors 10- exactly).
#
# Requires:
#   kubectl apply -f examples/bookstore/raw-manifests/00-namespace.yaml
#   kubectl apply -f examples/bookstore/raw-manifests/05-serviceaccounts-rbac.yaml  # ch.01
#   kind load docker-image bookstore/catalog:dev --name bookstore
# Apply:
#   kubectl apply -f examples/bookstore/raw-manifests/30-catalog-canary.yaml
apiVersion: v1
kind: Service
metadata:
  name: catalog
  namespace: bookstore
  labels:
    app: catalog
spec:
  selector:
    app: catalog            # selects BOTH stable & canary (deliberately no `track`)
  ports:
    - name: http
      port: 80
      targetPort: http      # the container's named port (8080)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-stable
  namespace: bookstore
  labels:
    app: catalog
    track: stable
spec:
  replicas: 9               # ~90% of traffic (9 of 10 Ready endpoints)
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: catalog
      track: stable         # owns ONLY stable Pods (no overlap with canary)
  template:
    metadata:
      labels:
        app: catalog        # selected by the shared Service
        track: stable       # distinguishes this Deployment's Pods
        component: backend
    spec:
      # --- Part 05 ch.01: same dedicated identity as 10- (no API token).
      serviceAccountName: catalog-sa
      automountServiceAccountToken: false
      # --- Part 05 ch.02: pod-level securityContext (PSA `restricted`),
      # identical to 10-catalog-deploy.yaml (same distroless image, UID 65532).
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: catalog
          image: bookstore/catalog:dev      # current "stable" image
          imagePullPolicy: IfNotPresent
          # --- Part 05 ch.02: container securityContext (PSA `restricted`).
          # The `scratch` emptyDir below is the only writable path the static
          # binary needs, so a read-only root FS holds (same as 10-).
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: PORT
              value: "8080"
          readinessProbe:
            httpGet: { path: /readyz, port: http }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 10
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 128Mi
          # --- Part 05 ch.02: writable scratch for the read-only root FS
          # (catalog uses /tmp/cache; mirrors 10-catalog-deploy.yaml).
          volumeMounts:
            - name: scratch
              mountPath: /tmp/cache
      volumes:
        - name: scratch
          emptyDir:
            sizeLimit: 64Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-canary
  namespace: bookstore
  labels:
    app: catalog
    track: canary
spec:
  replicas: 1               # ~10% of traffic (1 of 10 Ready endpoints)
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: catalog
      track: canary         # owns ONLY canary Pods
  template:
    metadata:
      labels:
        app: catalog        # ALSO selected by the SAME Service
        track: canary       # but independently scalable / observable
        component: backend
    spec:
      # --- Part 05 ch.01: same dedicated identity as 10- (no API token).
      serviceAccountName: catalog-sa
      automountServiceAccountToken: false
      # --- Part 05 ch.02: pod-level securityContext (PSA `restricted`),
      # identical to 10-catalog-deploy.yaml (same distroless image, UID 65532).
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: catalog
          image: bookstore/catalog:dev      # in real life: the NEW candidate image
          imagePullPolicy: IfNotPresent
          # --- Part 05 ch.02: container securityContext (PSA `restricted`).
          # Same as stable / 10-: read-only root FS + the scratch emptyDir.
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: PORT
              value: "8080"
            - name: LOG_LEVEL
              value: "debug"                # stand-in marker for "this is v2"
          readinessProbe:
            httpGet: { path: /readyz, port: http }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 10
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 128Mi
          # --- Part 05 ch.02: writable scratch for the read-only root FS
          # (catalog uses /tmp/cache; mirrors 10-catalog-deploy.yaml).
          volumeMounts:
            - name: scratch
              mountPath: /tmp/cache
      volumes:
        - name: scratch
          emptyDir:
            sizeLimit: 64Mi
