Docs

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.

QuestionMigrate modules to a new repoAttach an existing repo
When to use itThe target repo is new or emptyThe repo already holds your modules
Provider supportGitHub onlyAny provider, including GitLab and Bitbucket

Route 1: Migrate Modules to a New Repo

This route is GitHub-only.

  1. Select Connect repo, then Migrate modules to a new repo.
  2. 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.
  3. 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.
  4. 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:

  1. Resolve repository
  2. Check repository is empty
  3. Create CI service account
  4. Export published modules
  5. Push to repository
  6. Provision Actions secrets
  7. 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
        *.tf

Legacy 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:

SecretHolds
CONTROL_PLANE_URLThe control plane the workflow publishes to
FACETS_USERNAMEThe CI service account username
FACETS_TOKENThe 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.

  1. Select Connect repo, then Attach an existing repo.
  2. Enter the repository URL — it must start with http://, https://, or git@ — and select a VCS account.
  3. 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.

ModeWhat happens on a violationWhen to use it
OffFacets does not check provenanceBefore 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 warningWhile you roll the practice out and watch what trips
EnforceFacets rejects the publishOnce 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

PermissionGrants
MODULE_READView the module registry, fetch the modules repo, and read bootstrap job status
MODULE_REPO_MANAGEEvery mutating modules repo action: register, update, unregister, rotate the CI token, and bootstrap
MODULE_WRITEPublish and build modules
MODULE_DELETEDelete 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

ProblemCause and fix
GitHub App permissions pendingThe 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 failedFacets 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 provisionedNon-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 failedFacets 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.