# Connecting a Repository to ArgoCD Cluster-side operations that make an **already-rendered** GitOps repo reachable by an **already-installed** ArgoCD: trusting the git host key, registering the repository Secret, applying the app-of-apps bootstrap, and granting SSO users read access. Read this when onboarding a repo to a cluster for the first time, rotating or scoping the repository credential, or debugging a connect run — kube context, namespaces, repo credentials, bootstrap paths. Every other reference in this skill concerns *authoring* the repo; this one concerns *operating* ArgoCD against it. Driven by the wrapper-first `.scripts/argo-cd/` shell module (`Taskfile -> api -> lib`; see the `development-scripts` skill for that architecture). Variables and CLI flags: `references/connect-env-vars.md`. --- ## 1. Operations | Operation | Task | Purpose | |---|---|---| | Trust the git repo SSH host key | `task argo-cd:0000-trust-repo-host` | run once per git host, before connect | | Onboard / connect a repo | `task argo-cd:0100-connect-one` | preflight → repo Secret → bootstrap apply | | Let SSO users view apps (RBAC) | `task argo-cd:0200-grant-sso-readonly` | ArgoCD install config, not repo state | The numbered task names encode run order. The module is expandable — sync, app management, UI, and uninstall operations belong here as they are built. ## 2. Trust the repo host (run once, before connect) For SSH repos, ArgoCD's repo-server verifies the git server's host key against `argocd-ssh-known-hosts-cm`. A self-hosted host is not there by default, so `connect-one` fails with: ```text ssh: handshake failed: knownhosts: key is unknown ``` ```bash task argo-cd:0000-trust-repo-host # parses host+port from the repo URL ``` `lib/-trust-repo-host.sh` parses host and port from the repo URL, `ssh-keyscan`s it, **appends** to `argocd-ssh-known-hosts-cm` — never overwriting the shipped defaults — and restarts `argocd-repo-server` so the mounted ConfigMap reloads. Idempotent: it skips when the host is already present. A ConfigMap patch alone can take ~60s to remount. The restart makes it deterministic. ## 3. Onboard / connect ```bash task argo-cd:0000-trust-repo-host # once per host task argo-cd:0100-connect-one # reads the .env layers; override with --repo-url=… etc. ``` Entry chain: `api/connect-one.sh` → `lib/--index.sh` (base loader → argo-cd reader → api index) → `_argocd_connect_one`: ```text require KUBE_CONFIG_PATH KUBE_CONFIG_CONTEXT ARGOCD_NAMESPACE ARGOCD_TARGET_DIR ARGOCD_GIT_REPO_URL export KUBECONFIG= preflight → repo-register → bootstrap-apply ``` ### Step 1 — preflight - `command -v kubectl` - `kubectl --context get namespace ` — ArgoCD reachable - the bootstrap directory exists in the target repo — the render must already have run ### Step 2 — repo-register Builds a `kubectl create secret generic`, pipes it to `kubectl apply -f -`, then labels it `argocd.argoproj.io/secret-type=repository`: ```text --from-literal=type=git --from-literal=url= # SSH form: git@host:owner/repo.git [--from-literal=project=] # when set → project-scoped [--from-file=sshPrivateKey=] # when set ``` | ssh key | project | Result | |---|---|---| | none | none | skip Secret (public, global repo) | | none | set | create Secret (type + url + project) — project-scoped public repo | | set | any | create Secret with `sshPrivateKey` | | set, file missing | any | fail fast | The matching **public** key must be a read-only deploy key on the repository. ### Step 3 — bootstrap-apply Applies the bootstrap manifests in filename order — the AppProject (`0000-*`) before the app-of-apps (`0100-*`). ArgoCD then reconciles everything else **from Git**. The app-of-apps source must set `directory.recurse: true`, because feature Applications live in nested subdirectories. Without it only the top-level manifest is read and children never appear. ## 4. Project-scoped repositories Setting an AppProject name makes the repo a **project-scoped repository**: the Secret carries a `project` field, so only Applications in that AppProject may use it. - The SSH key is optional (public repo); if a key path is set, the file must exist. - The Secret is skipped **only** when there is neither a key nor a project scope. - A public repo **with** a project scope still gets a Secret — type, url, project, no key. The AppProject's `sourceRepos` must permit the repo URL, and the app-of-apps `spec.project` must match. ## 5. Let SSO users view apps (RBAC) With an empty `argocd-rbac-cm` `policy.default`, non-admin SSO/OIDC users see an **empty** Applications list. `admin` bypasses RBAC, so the apps exist but are invisible to SSO logins — which reads as "my apps disappeared" rather than as a permissions problem. ```bash task argo-cd:0200-grant-sso-readonly # sets policy.default (default role:readonly) ``` Idempotent; argocd-server reloads live, no restart needed. This is ArgoCD **install** configuration living on the cluster — not part of the GitOps repo, so it is neither rendered by Copier nor reconciled from Git. `role:readonly` grants **all** authenticated users read access to **all** applications. Scope a role to a specific SSO group when you need tighter control. ## 6. Prerequisites the scripts do NOT handle - **ArgoCD must already be installed.** Preflight only *checks* that the namespace exists. - **The rendered repo must be pushed.** bootstrap-apply applies local manifests, but those manifests point ArgoCD at a `repoURL` + `path` **in Git**. Unpushed → the app-of-apps cannot find its path. - **The public deploy key must be on the repo**, read-only, for SSH auth to work. - **A successful render must exist first** — the target's bootstrap directory has to be on disk. See `references/environments.md` for rendering an environment. - `kubectl` reachable at the configured kubeconfig and context. ## 7. Bootstrap paths — two repository shapes The connect scripts reference the rendered repo's cluster folders by name, so those names are a contract between the template and the scripts. Two shapes are in use: | Purpose | `cluster/argocd/` shape (SKILL.md) | numbered shape | |---|---|---| | Bootstrap — AppProject + app-of-apps | root app applies it | `cluster/0000-bootstrap` | | Child Application manifests | `cluster/argocd//` | `cluster/0300-applications` | | Helm values / resources | `cluster/resources//` | `cluster/0500-resources` | The authoring rules in `SKILL.md` describe the first shape. Whichever a repo uses, preflight and bootstrap-apply must point at its **real** bootstrap directory — if the template renames cluster folders, update both step scripts in the same change. The deployed surface is only the manifests under paths an ArgoCD Application actually references. ## 8. Verify without a cluster Render the repository Secret client-side to confirm project scoping before applying: ```bash kubectl create secret generic \ --from-literal=type=git --from-literal=url= \ --from-literal=project= \ --from-file=sshPrivateKey= \ --dry-run=client -o yaml # decode data.project → must equal the AppProject name ``` ## 9. Post-onboard checks ```bash kubectl -n get appproject kubectl -n get applications kubectl -n get secret -o jsonpath='{.data.project}' | base64 -d argocd app list # if the argocd CLI is available ``` ## 10. Anti-patterns - Committing the SSH private key instead of keeping it a local file referenced by path. - Letting `.env*` values reach the deployed `cluster/**` manifests — they are tooling inputs. - Adding a `:default` task wrapper; the env layers already supply values. Keep one env-driven task and override with flags after `--`. - Hardcoding cluster folder names in step scripts without keeping them in sync with the template (§7). - Keeping two connect modules. If a duplicate exists, prefer the wired one and delete the other.