--- - name: Verify the committed vendored chart checksum ansible.builtin.command: argv: ["{{ ansible_playbook_python }}", "{{ argocd_source }}/devops/platform/scripts/platform_gitops/check_chart.py"] changed_when: false - name: Prepare the development infrastructure namespace kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' definition: apiVersion: v1 kind: Namespace metadata: name: argocd labels: {pod-security.kubernetes.io/enforce: restricted, istio-injection: disabled} - name: Read the durable ownership record kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: v1 kind: ConfigMap namespace: istio-system name: pharos-platform-ownership register: argocd_owner - name: Read the self-management Application ansible.builtin.command: argv: [kubectl, '--kubeconfig', '{{ argocd_kubeconfig }}', -n, argocd, get, applications.argoproj.io, argocd, '--ignore-not-found', -o, json] register: argocd_application failed_when: >- argocd_application.rc != 0 and 'resource type' not in argocd_application.stderr and 'the server could not find' not in argocd_application.stderr changed_when: false - name: Reject unrecorded self-management before considering Helm ansible.builtin.assert: that: - argocd_owner.resources | length > 0 or argocd_application.stdout | trim == '' fail_msg: Existing Application without ownership authority requires explicit recovery - name: Select only the recorded bootstrap writer ansible.builtin.set_fact: argocd_helm_allowed: >- {{ argocd_owner.resources | length == 0 or argocd_owner.resources[0].data.argocd == 'bootstrap' }} - name: Initialise ownership before installing the admission fence kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' definition: apiVersion: v1 kind: ConfigMap metadata: {name: pharos-platform-ownership, namespace: istio-system} data: {routes: ansible, argocd: bootstrap} when: argocd_owner.resources | length == 0 - name: Require explicit quiescence before recovering a recorded bootstrap writer ansible.builtin.assert: that: - >- not argocd_helm_allowed or argocd_application.stdout | trim == '' or (argocd_application.stdout | from_json).spec.syncPolicy.automated.enabled | default(true) == false fail_msg: Suspend root and children with recover_platform_gitops.py before Argo bootstrap recovery - name: Resolve the serving dependency for this environment ansible.builtin.set_fact: argocd_serving_enabled: '{{ argocd_environment == "distributed" or argocd_kserve_enabled | default(false) | bool }}' - name: Preserve an existing KServe installation's owner and selection ansible.builtin.command: argv: - '{{ ansible_playbook_python }}' - '{{ argocd_source }}/devops/platform/scripts/kserve_installation.py' - --kubeconfig - '{{ argocd_kubeconfig }}' - '{{ "--enabled" if argocd_serving_enabled else "--disabled" }}' changed_when: false - name: Ensure certificate infrastructure before KServe delivery ansible.builtin.command: argv: - '{{ ansible_playbook_python }}' - '{{ argocd_source }}/devops/platform/scripts/cert_manager.py' - --kubeconfig - '{{ argocd_kubeconfig }}' register: argocd_cert_manager changed_when: (argocd_cert_manager.stdout | from_json).changed when: argocd_serving_enabled | bool - name: Ensure Gateway API before KServe delivery ansible.builtin.command: argv: - '{{ ansible_playbook_python }}' - '{{ argocd_source }}/devops/platform/scripts/gateway_api.py' - --kubeconfig - '{{ argocd_kubeconfig }}' register: argocd_gateway_api changed_when: (argocd_gateway_api.stdout | from_json).changed when: argocd_serving_enabled | bool - name: Prepare the private Redis credential once kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: v1 kind: Secret namespace: argocd name: argocd-redis register: argocd_redis - name: Create the credential only when absent kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' definition: apiVersion: v1 kind: Secret metadata: {name: argocd-redis, namespace: argocd} type: Opaque stringData: auth: "{{ lookup('ansible.builtin.password', '/dev/null length=64 chars=ascii_letters,digits') }}" when: argocd_redis.resources | length == 0 no_log: true - name: Preserve the Argo server credential Secret across rendering kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' definition: apiVersion: v1 kind: Secret metadata: name: argocd-secret namespace: argocd labels: {app.kubernetes.io/part-of: argocd} type: Opaque no_log: true - name: Remove the controllers whose fixed selectors the pinned chart changes # A label selector cannot be patched or replaced, so the four legacy controllers have to be # removed before the chart recreates them. Their pods hold no Argo state: the ConfigMaps, # Secrets and claims keep it and the chart patches those in place. kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' state: absent api_version: apps/v1 kind: '{{ item.kind }}' name: '{{ item.name }}' namespace: argocd loop: - {kind: Deployment, name: argocd-applicationset-controller} - {kind: Deployment, name: argocd-repo-server} - {kind: Deployment, name: argocd-server} - {kind: StatefulSet, name: argocd-application-controller} when: argocd_helm_allowed | bool - name: Install or recover Helm only before Argo adoption # An adopted site already serves Argo from an unmanaged installation, so the pinned chart has to # take ownership of those objects: --take-ownership adds Helm's ownership metadata, and --force # replaces the four controllers whose immutable label selectors cannot be patched. Their pods hold # no Argo state; the ConfigMaps, Secrets and claims keep it and are patched in place. ansible.builtin.command: argv: - helm - upgrade - --install - --take-ownership - --force - --reset-values # Helm only replaces a resource whose rendered manifest differs from the stored release, so # the adopting upgrade changes one pod label. That makes --force replace the controllers whose # immutable selectors differ and drop the legacy fields a merge patch would keep. - --set-string - global.podLabels.pharos-adopt=true - --wait - --timeout - 300s - --kubeconfig - '{{ argocd_kubeconfig }}' - --namespace - argocd - --values - '{{ argocd_source }}/gitops/manifests/argocd/base/values.yaml' - --values - '{{ argocd_source }}/gitops/manifests/argocd/overlays/{{ argocd_environment }}/values.yaml' - argocd - '{{ argocd_source }}/gitops/manifests/argocd/chart' changed_when: true when: argocd_helm_allowed | bool - name: Select the target architecture's pinned Git daemon vars: argocd_pins: '{{ lookup("file", argocd_source + "/gitops/manifests/argocd/pins.yaml") | from_yaml }}' ansible.builtin.set_fact: argocd_git_package: '{{ argocd_pins.gitDaemonArm64 if argocd_architecture | default("amd64") == "arm64" else argocd_pins.gitDaemon }}' - name: Read the checksum-verified Git daemon package ansible.builtin.slurp: src: '{{ argocd_source }}/gitops/manifests/argocd/artifacts/{{ argocd_git_package.file }}' register: argocd_daemon - name: Deliver only the public verified executable package kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' definition: apiVersion: v1 kind: ConfigMap metadata: {name: pharos-platform-git-daemon, namespace: argocd} data: sha256: '{{ argocd_git_package.sha256 }} /package/git-daemon.apk' binaryData: git-daemon.apk: '{{ argocd_daemon.content }}' - name: Observe GPUCluster workloads before applying the reduced operator grant kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: nvidia.com/v1alpha1 kind: GPUCluster register: argocd_gpu_clusters - name: Require the reviewed conventional GPU profile ansible.builtin.assert: that: - argocd_gpu_clusters.resources | default([]) | length == 0 fail_msg: Existing GPUCluster/DRA workloads need a separate admission-permission review - name: Install GPU admission before granting controller rights kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' src: '{{ argocd_source }}/gitops/manifests/argocd/bootstrap/{{ item }}' loop: [gpu-crd-admission.yaml, gpu-role-admission.yaml] - name: Wait for GPU admission policy compilation kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy name: '{{ item }}' loop: [pharos-platform-gpu-crd-scope, pharos-platform-gpu-role-scope] register: argocd_gpu_admission until: >- argocd_gpu_admission.resources | length == 1 and argocd_gpu_admission.resources[0].status.observedGeneration | default(0) == argocd_gpu_admission.resources[0].metadata.generation and argocd_gpu_admission.resources[0].status.typeChecking is defined retries: 30 delay: 2 - name: Reject GPU admission type errors before granting rights ansible.builtin.assert: that: - item.resources[0].status.typeChecking.expressionWarnings | default([]) | length == 0 fail_msg: GPU admission failed type checking; controller grants remain unchanged loop: '{{ argocd_gpu_admission.results }}' - name: Maintain bootstrap-owned project, transport, scoped cache and route rights kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' src: '{% if item == "build.yaml" and argocd_build_manifest is defined %}{{ argocd_build_manifest }}{% else %}{{ argocd_bootstrap_source | default(argocd_source + "/gitops/manifests/argocd/bootstrap") }}/{{ item }}{% endif %}' # services.yaml creates the service namespaces that route-rbac.yaml and site-api-review.yaml grant into. # site-runtime-admission.yaml installs the Runtime's admission policies before any Project grant; gpu-operator.yaml # declares the operator's namespaces and project before its Application names them. # ownership.yaml only initialises the record: an installation that already has one may have handed routes # or Argo to Argo, and re-applying it would give them back to the edge role and to Helm. loop: [project.yaml, default-project.yaml, ownership.yaml, git.yaml, build.yaml, services.yaml, gpu-operator.yaml, gpu-dra.yaml, site-api-review.yaml, site-agent.yaml, kubeflow.yaml, infrastructure.yaml, aai.yaml, api-auth.yaml, mcp.yaml, route-rbac.yaml, admission.yaml, site-runtime-admission.yaml] when: item != 'ownership.yaml' or argocd_owner.resources | length == 0 - name: Read node and Calico tunnel addresses for cross-node registry pulls ansible.builtin.command: argv: [kubectl, '--kubeconfig', '{{ argocd_kubeconfig }}', get, nodes, -o, json] register: argocd_node_list changed_when: false - name: Resolve exact node pull sources without permitting pod or arbitrary networks ansible.builtin.command: argv: ['{{ ansible_playbook_python }}', '{{ argocd_source }}/devops/platform/scripts/platform_gitops/nodes.py'] stdin: '{{ argocd_node_list.stdout }}' register: argocd_nodes changed_when: false # Re-run after adding a node: its pulls from a remote registry pod are otherwise denied. - name: Admit node pulls to the registry from every current node kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/registry-network.yaml.j2' - name: Wait for the private Git transport ansible.builtin.command: argv: [kubectl, '--kubeconfig', '{{ argocd_kubeconfig }}', -n, argocd, rollout, status, deployment/pharos-platform-git, '--timeout=300s'] changed_when: false - name: Read only public Kubernetes trust for the separate route cache kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: v1 kind: ConfigMap namespace: argocd name: kube-root-ca.crt register: argocd_root_ca - name: Configure route authentication using only the caller's projected identity ansible.builtin.set_fact: argocd_route_config: execProviderConfig: command: sh args: - -ec - >- printf '{"apiVersion":"client.authentication.k8s.io/v1beta1","kind":"ExecCredential","status":{"token":"'; cat /var/run/secrets/kubernetes.io/serviceaccount/token; printf '"}}' apiVersion: client.authentication.k8s.io/v1beta1 tlsClientConfig: insecure: false serverName: kubernetes.default.svc.cluster.local caData: "{{ argocd_root_ca.resources[0].data['ca.crt'] | b64encode }}" - name: Register the route cache with its caller's rotating projected identity kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/route-cluster.yaml.j2' - name: Register the service cache, limited to the service namespaces, with the same identity kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/services-cluster.yaml.j2' - name: Register the Site Agent cache, limited to its namespaces and named cluster objects, with the same identity kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/site-agent-cluster.yaml.j2' - name: Register self-management with the same verified API and private namespace kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/root-cluster.yaml.j2' - name: Read the actual Kubernetes API endpoints for infrastructure egress kubernetes.core.k8s_info: kubeconfig: '{{ argocd_kubeconfig }}' api_version: v1 kind: Endpoints namespace: default name: kubernetes register: argocd_api - name: Read the controller's effective DNS servers, including NodeLocal DNS ansible.builtin.command: argv: [kubectl, '--kubeconfig', '{{ argocd_kubeconfig }}', -n, argocd, exec, statefulset/argocd-application-controller, --, cat, /etc/resolv.conf] register: argocd_resolver changed_when: false - name: Resolve exact DNS egress blocks without permitting arbitrary networks ansible.builtin.command: argv: ['{{ ansible_playbook_python }}', '{{ argocd_source }}/devops/platform/scripts/platform_gitops/dns.py'] stdin: '{{ argocd_resolver.stdout }}' register: argocd_dns changed_when: false - name: Restrict Argo network access to its peers, DNS and the actual API kubernetes.core.k8s: kubeconfig: '{{ argocd_kubeconfig }}' template: '{{ role_path }}/templates/network.yaml.j2'