Modules Repo
Connect a git repository as the source of truth for your custom modules, migrate published modules into it, and provenance-check every publish.
A modules repo is the single git repository your control plane treats as the source of truth for custom IaC modules. Modules reach the registry through reviewed git history instead of ad-hoc uploads, and Facets can check every publish against it. Each control plane holds exactly one, and Facets rejects a second registration.
Prerequisite: a linked version-control account — see Integrating Git Accounts. GitHub App accounts need extra permission approval first; see Troubleshooting.
Connecting a Repo: Choosing a Route
On the Module Registry page, above the module tables, select Connect repo — or Manage if a repo is attached. Two mutually exclusive routes follow.
| Question | Migrate modules to a new repo | Attach an existing repo |
|---|---|---|
| When to use it | The target repo is new or empty | The repo already holds your modules |
| Provider support | GitHub only | Any provider, including GitLab and Bitbucket |
Route 1: Migrate Modules to a New Repo
This route is GitHub-only.
- Select Connect repo, then Migrate modules to a new repo.
- Choose a new repository or an existing empty one. A new one takes a repository name, a VCS account, and an organization if it has one. An existing one takes its URL and account.
- Under Advanced Options, set a path prefix to place the modules tree under a subdirectory such as
infra/modules/…. Facets rejects absolute paths and traversal. - Start the migration and watch progress until it reports success.
A default branch applies only to the existing-repository option; a new repository keeps the provider's own, so Facets rejects a branch name supplied with a repository name.
What Migration Does
Migration runs as an async job with per-step progress. A failure names the step that stopped, and the job is re-runnable:
- Resolve repository
- Check repository is empty
- Create CI service account
- Export published modules
- Push to repository
- Provision Actions secrets
- Register modules repo
Export unzips every module at the Published stage into the tree below, beneath any path prefix. Each leaf holds facets.yaml and the module's Terraform files. Preview-stage modules are skipped.
modules/
<intent>/
<flavor>/
<version>/
facets.yaml
*.tfLegacy modules registered by git URL before the zip upload flow have no stored file. Facets skips them and lists them on the step detail — re-upload as needed.
Migration also commits the Facets Module CI workflow at .github/workflows/facets-module-ci.yml — see Continuous Integration — plus a README naming the secrets it needs. The bootstrap commit carries [skip ci], so CI starts on your first change.
Facets mints the CI service account before export, validating its role for module read, write, and delete, then provisions three repository secrets:
| Secret | Holds |
|---|---|
CONTROL_PLANE_URL | The control plane the workflow publishes to |
FACETS_USERNAME | The CI service account username |
FACETS_TOKEN | The CI service account token |
Route 2: Attach an Existing Repo
Use this route on any provider for a repository that already holds your modules. It registers the repository and nothing else — no export, push, workflow, or secrets.
- Select Connect repo, then Attach an existing repo.
- Enter the repository URL — it must start with
http://,https://, orgit@— and select a VCS account. - Set the default branch, a path prefix, and an enforcement mode, then save. See Provenance Enforcement.
Your repository must already follow modules/<intent>/<flavor>/<version>/ beneath the path prefix. You wire up CI yourself — see Continuous Integration.
Managing an Attached Repo
Select Manage to review the repository URL, CI user, default branch, path prefix, and enforcement mode; you can edit the last three. The last bootstrap job replays the seven-step view, showing which modules the export skipped.
Provenance Enforcement
Provenance ties a published module to the registered repository. At publish time, Facets checks that the module carries a git ref — a commit SHA — and a git URL matching the registered repo. Both are captured at upload from a git work tree, not at publish. URL matching is normalized, so SSH versus HTTPS or a trailing .git still matches. Upload from a git work tree and publish from a clone of the registered repo, and both checks pass.
| Mode | What happens on a violation | When to use it |
|---|---|---|
| Off | Facets does not check provenance | Before your team publishes from clones |
| Warn (default, recommended) | The publish succeeds. Facets logs the violation, returns it to the caller, and stores it on the module as a publish warning | While you roll the practice out and watch what trips |
| Enforce | Facets rejects the publish | Once every publish comes from a clone of the registered repo |
Rotating the CI Token
Select Rotate CI token to regenerate the CI service account's token. Facets updates the GitHub secrets in place where reachable and shows the raw token once — copy it before closing. During migration it appears only when automatic provisioning failed.
Detaching the Repo
Select Detach repo and confirm.
Detaching is non-destructive. The git repository, the CI service account, and every published module stay intact. Detaching removes only the registration, so publishes stop being provenance-checked.
Permissions
| Permission | Grants |
|---|---|
MODULE_READ | View the module registry, fetch the modules repo, and read bootstrap job status |
MODULE_REPO_MANAGE | Every mutating modules repo action: register, update, unregister, rotate the CI token, and bootstrap |
MODULE_WRITE | Publish and build modules |
MODULE_DELETE | Delete modules |
Without MODULE_READ the page tells you to contact your admin; without MODULE_REPO_MANAGE controls are disabled. Facets audit-logs every mutating action.
Troubleshooting
| Problem | Cause and fix |
|---|---|
| GitHub App permissions pending | The installation lacks the workflow-push or Actions-secrets permission; a pre-flight catches this before any step runs. Approve them, then re-check — the job continues. |
| Repository-is-empty check failed | Facets tolerates only README.md, LICENSE, and .gitignore; the step detail lists what else it found. Empty the repository, or use the attach route. |
| Some repo secrets not provisioned | Non-fatal; the job succeeds. Set them by hand under Settings → Secrets and variables → Actions, using the committed README's names. For the token, use Rotate CI token. |
| Push failed | Facets surfaces the remote's sideband message. Read it — a branch protection rule is the usual cause. |
Tip: You can also perform this operation programmatically. See the API Reference for details.
Related
- Module Registry - The publish target
- Continuous Integration - Publishing from the repo
- Versioning Strategy - Version-per-directory rationale
- Integrating Git Accounts - Linking the VCS account
Defining Module Resource Actions
Define custom resource Actions for any Facets module using containerised Tekton workflows, with auto-injected credentials, RBAC scoping, and UI integration.
Continuous Integration
How Facets module repos preview modules on pull requests and publish them on merge, using the generated Facets Module CI workflow or your own CI.