Global InfraGlobal Infra Cloud

repository (repo)

Manage GitLab infrastructure repositories.

Edit

Manage GitLab infrastructure repositories. Handles creation, branch setup, access controls, CI/CD pipeline wiring, and local state management for OpenTofu-based infrastructure repositories.

Background

Why OpenTofu

Infrastructure repositories use OpenTofu instead of Terraform. OpenTofu is a community-driven open-source fork of Terraform maintained by the Linux Foundation, created after HashiCorp relicensed Terraform from MPL 2.0 to the Business Source License (BSL 1.1) in 2023. OpenTofu remains under MPL 2.0 and is fully compatible with existing Terraform configurations.

Resource reference and provider documentation: https://core.pages.core.emea.giservices.io/code/console/terraform-provider-gi/.

State management

OpenTofu state is stored in the GitLab-managed Terraform state backend — no external storage is required. Each branch has its own isolated state, named after the branch slug (e.g. emea-sandbox). See GitLab documentation on Terraform state for details on how the backend works.

State is encrypted at rest using OpenTofu’s native encryption (PBKDF2 + AES-GCM). The encryption passphrase is unique per environment and stored as a protected, masked CI/CD variable — it never appears in logs or pipeline output.

Users are fully responsible for their OpenTofu state, whether operations are performed through the CI/CD pipeline or locally. Corrupted, diverged, or accidentally modified state can cause infrastructure drift, failed deployments, or data loss. Always understand what an operation does before running it.

State commands are helpers, not requirements

The gi repo state subcommands are convenience wrappers — everything they do can be performed manually without the gi CLI:

  • state pull retrieves the OPENTOFU_ENCRYPTION_PASSPHRASE CI/CD variable from GitLab (accessible via the GitLab UI or API), writes the HTTP backend configuration (__gitlab-opentofu-backend.tf), and generates .envrc with the TF_HTTP_* and TF_ENCRYPTION variables. All of this can be done by hand by copying the variable values and writing the files yourself.
  • state import runs tofu init and tofu plan -generate-config-out=<file> against the GitLab-managed backend. The same result can be achieved by setting up the environment manually and running those tofu commands directly.

Use the gi CLI when you want to avoid the manual steps. Use tofu directly when you need more control or when the CLI does not cover your use case.

CI/CD pipeline

The generated .gitlab-ci.yml uses the official GitLab OpenTofu component (components/opentofu) via the shared console-gitlab-ci wrapper. This component provides standard validate, plan, and apply jobs with automatic backend configuration and state encryption. You do not need to configure the backend or encryption manually in your Terraform files.

Command-level Flags

These flags are available to all repository subcommands.

FlagEnv VarDefaultDescription
--gitlab-tokenGITLAB_TOKENGitLab private token
--gitlab-urlGITLAB_URLhttps://gitlab.core.emea.giservices.ioGitLab instance URL

Authentication

Authentication is nominative across every repo subcommand and the CI/CD pipeline generated by init. Calls to GitLab and Global Infra APIs are performed under the identity of the user who triggered the action — there is no shared technical user shadowing these operations. As a consequence, the caller must already have read and update rights on every resource being manipulated (GitLab groups, projects, branches, variables, and the target infrastructure resources). Missing rights surface as 401/403 errors from the upstream API; that is expected, not a CLI bug.

Subcommands

SubcommandAliasesDescription
initcreateInitialize a new repository with branches, protections, and CI/CD variables
state pullSet up local OpenTofu environment for a GitLab-managed encrypted state
state importImport an existing resource into the OpenTofu state and generate its configuration

init

Creates a new GitLab project for infrastructure-as-code, then fully configures it: commits default files, creates all environment branches, protects them, and sets up per-environment OpenTofu state encryption.

Flags

FlagRequiredDefaultDescription
--tenantYesTenant name (used to resolve the GitLab group path)
--path-template{'{tenant}'}/infrastructure/{'{repo_name}'}GitLab path template. Supports {'{tenant}'} and {'{repo_name}'} placeholders
--providergitlabGit provider
--cloneinteractive promptClone the new repository locally once it’s created. Accepts ssh, https, or none. When omitted, the command prompts for a choice; pass none to skip both the prompt and the clone.

The repository name is passed as the first positional argument.

What it creates

GitLab project

The project is created at the path resolved from --path-template. With the default template and --tenant acme, a repository named my-infra is created at:

acme/infrastructure/my-infra

Parent GitLab groups are created automatically if they don’t exist.

Default files

The following files are committed to the default branch (emea/prod) in an initial [skip-ci] commit. All files include a header noting they were generated by gi repository init and are safe to edit.

FileDescription
.gitlab-ci.ymlIncludes the shared OpenTofu pipeline (console-gitlab-ci)
main.tfConfigures the gi Terraform provider
variables.tfDeclares tenant, region, and platform input variables
.gitignoreIgnores local dev files (.envrc, __gitlab-opentofu-backend.tf, .terraform/, state files)
README.mdMinimal readme with the repository name

Branches

One branch is created per region x platform combination. The default branch is emea/prod.

BranchRegionPlatform
emea/prod (default)emeaprod
emea/preprodemeapreprod
emea/sandboxemeasandbox
apac/prodapacprod
apac/preprodapacpreprod
apac/sandboxapacsandbox

Branch protections

PatternPushMerge
*/sandboxMaintainerMaintainer
*/preprodMaintainerMaintainer
*/prodNobodyMaintainer

Protected environments

EnvironmentTierAllowed to deploy
emea-prodproductionMaintainer, svc-console
apac-prodproductionMaintainer, svc-console

The svc-console service account is explicitly allowed to deploy to the prod environments so the change-process pipeline can run deploy jobs on behalf of the release manager.

Merge request settings

SettingValue
Pipelines must succeedYes
Squash commitsAlways required
Approvals required on */prod1

The approval rule is scoped to the */prod protected branch, which matches emea/prod and apac/prod.

CI/CD variables

One OPENTOFU_ENCRYPTION_PASSPHRASE variable is created per branch, scoped to that branch’s environment. Each value is a unique randomly generated 20-character password.

VariableScopeProtectedMasked
OPENTOFU_ENCRYPTION_PASSPHRASEemea-prodYesYes
OPENTOFU_ENCRYPTION_PASSPHRASEemea-preprodYesYes
OPENTOFU_ENCRYPTION_PASSPHRASEemea-sandboxYesYes
OPENTOFU_ENCRYPTION_PASSPHRASEapac-prodYesYes
OPENTOFU_ENCRYPTION_PASSPHRASEapac-preprodYesYes
OPENTOFU_ENCRYPTION_PASSPHRASEapac-sandboxYesYes

These passphrases are used by the CI/CD pipeline to encrypt and decrypt OpenTofu state at rest in the GitLab-managed HTTP backend.

Examples

# Create a new infrastructure repository for tenant acme
gi repo init my-infra --tenant acme --gitlab-token "$GITLAB_TOKEN"

# Custom GitLab instance
gi repo init my-infra \
  --tenant acme \
  --gitlab-token "$GITLAB_TOKEN" \
  --gitlab-url https://gitlab.example.com

# Custom path template
gi repo init my-infra \
  --tenant acme \
  --gitlab-token "$GITLAB_TOKEN" \
  --path-template "{tenant}/custom/{repo_name}"

# Clone over SSH immediately after creation (skips the interactive prompt)
gi repo init my-infra --tenant acme --gitlab-token "$GITLAB_TOKEN" --clone ssh

# Create the repository without cloning it locally
gi repo init my-infra --tenant acme --gitlab-token "$GITLAB_TOKEN" --clone none

state pull

For advanced users only. Direct state manipulation bypasses the normal CI/CD pipeline workflow. Only use this if you fully understand OpenTofu state and the consequences of local operations on shared infrastructure. Corrupted or diverged state can cause infrastructure drift, failed deployments, or data loss.

Retrieves the OpenTofu state encryption passphrase from GitLab CI/CD variables and generates the environment files needed to run tofu locally against the GitLab-managed state backend. Must be run from inside the cloned repository — the GitLab project is resolved automatically from the git remote.

The primary use case is read-only inspection: running tofu plan locally to debug or understand the current state without going through a pipeline.

Prerequisites

  • tofu CLI must be installed locally.
  • GITLAB_TOKEN must have the api scope (required for state locking).
  • Run from inside the cloned repository directory.

Branch auto-detection

--branch is optional. When omitted, the command inspects the local git history to determine the most relevant state branch:

  • If the current git branch exactly matches a known state branch (e.g. emea/sandbox), it is used directly.
  • Otherwise, the command finds the nearest state branch by counting how many commits ahead each remote branch is and picking the one the current branch diverged from most recently.

In both cases you are prompted to confirm before proceeding:

Current git branch:    feat/my-feature
Nearest state branch:  emea/sandbox
Proceed? [Y/n] (override with --branch <branch>):

Use --branch to skip detection and force a specific branch.

Flags

FlagDefaultDescription
--branchauto-detected from gitBranch/environment to set up (e.g. emea/sandbox)
--state-nameBranch slug (e.g. emea-sandbox)Terraform state name as stored in GitLab
--env-file.envrcPath of the env file to write
--remoteoriginGit remote name used to identify the GitLab project

What it generates

__gitlab-opentofu-backend.tf

Configures OpenTofu to use the GitLab HTTP backend. Equivalent to what auto_define_backend generates in CI:

terraform {
  backend "http" {}
}

.envrc

Sets the environment variables required to authenticate against the backend and decrypt the state. Sensitive values are referenced as environment variables — nothing secret is written to disk.

export TF_HTTP_ADDRESS='https://gitlab.../api/v4/projects/.../terraform/state/emea-sandbox'
export TF_HTTP_LOCK_ADDRESS='.../lock'
export TF_HTTP_LOCK_METHOD='POST'
export TF_HTTP_UNLOCK_ADDRESS='.../lock'
export TF_HTTP_UNLOCK_METHOD='DELETE'
export TF_HTTP_USERNAME='<your-gitlab-username>'
export TF_HTTP_PASSWORD="${GITLAB_TOKEN}"
export TF_ENCRYPTION='key_provider "pbkdf2" "gitlab_tofu_auto_encryption" {
  passphrase = "${OPENTOFU_ENCRYPTION_PASSPHRASE}"
}
...'

Both __gitlab-opentofu-backend.tf and .envrc are listed in the generated .gitignore and must not be committed.

Workflow

# 1. Generate the local setup files (from inside the cloned repo)
#    Branch is auto-detected from git history — confirm the prompt or use --branch to force one
gi repo state pull --gitlab-token "$GITLAB_TOKEN"

# 2. Export the passphrase printed by the command above
export OPENTOFU_ENCRYPTION_PASSPHRASE=<value from command output>

# 3. Load the backend and encryption config
source .envrc

# 4. Initialise and plan
tofu init
tofu plan

Examples

# Auto-detect branch from git history (prompts for confirmation)
gi repo state pull

# Force a specific branch
gi repo state pull --branch emea/sandbox

# Explicit state name (if it differs from the branch slug)
gi repo state pull --branch emea/sandbox --state-name my-custom-state

# Write env file to a custom path
gi repo state pull --branch emea/prod --env-file .env.tofu

# Use a non-default git remote
gi repo state pull --branch emea/sandbox --remote upstream

state import

For advanced users only. Importing state directly bypasses the normal CI/CD pipeline workflow. Only use this if you fully understand OpenTofu state and the consequences of local operations on shared infrastructure. The apply step must always go through the pipeline on a protected branch.

Imports an existing infrastructure resource into the OpenTofu state and generates its HCL configuration locally. The import block is written to _imports.tf and the resource configuration is generated in the file you specify.

The primary use case is bringing an existing resource under OpenTofu management without recreating it — for example, a resource that was provisioned manually or by another tool.

Prerequisites

  • tofu CLI must be installed locally.
  • gi repo state pull must have been run first to configure the local backend.
  • OPENTOFU_ENCRYPTION_PASSPHRASE must be exported in your shell.
  • .envrc must be sourced.

Arguments

gi repo state import <resource_type>.<resource_name> <resource_id>[,<resource_id2>,...]
ArgumentDescription
<resource_type>.<resource_name>OpenTofu resource address (e.g. gi_proxy_acl_proxy_infra.github)
<resource_id>Provider-specific ID of the existing resource (e.g. sre/sandbox/emea/github). Pass a comma-separated list to import several resources of the same type in one call — see Bulk import

Flags

FlagDefaultDescription
--out<resource_name>.tfPath of the file to generate the resource configuration into
--excluderegion,platform,tenant,service,synchronizationAttributes to strip from the generated resource block

What it does

  1. Writes an import block for the resource to _imports.tf (created if absent). The file is idempotent — re-running with the same resource skips the write.
  2. Runs tofu init to configure the HTTP backend.
  3. Runs tofu plan -generate-config-out=<out> to generate the HCL configuration from the live resource.
  4. Post-processes the generated file to strip platform-managed attributes (region, platform, tenant, service, synchronization) that are set automatically by the provider.

Bulk import

Multiple resources of the same type can be imported in a single call by passing a comma-separated list of IDs. Because each OpenTofu resource must have a unique address, the base <resource_name> is suffixed with _1, _2, … for each ID in order.

gi repo state import gi_proxy_acl_proxy_infra.github sre/sandbox/emea/github_test1,sre/sandbox/emea/github_test2

The command above writes two import blocks to _imports.tf:

import {
  to = gi_proxy_acl_proxy_infra.github_1
  id = "sre/sandbox/emea/github_test1"
}
import {
  to = gi_proxy_acl_proxy_infra.github_2
  id = "sre/sandbox/emea/github_test2"
}

Both resource configurations are generated into the same --out file (default github.tf) in a single tofu plan -generate-config-out run. You can rename the generated resources afterwards — see Refactoring the generated configuration.

Extracting IDs from gi list commands

Any gi list command that emits JSON (-o json) can be piped through jq to produce the comma-separated list expected by bulk import. Combined with $(...) command substitution, this lets you import every matching resource without hand-copying IDs.

# List all ACL proxy infrastructures for sre/emea/sandbox, then join their IDs with commas
gi -o json proxy acl-proxy-infra list -f tenant=sre -f region=emea -f platform=sandbox \
  | jq -r '[.[].id] | join(",")'
# -> sre/sandbox/emea/github,sre/sandbox/emea/gitlab,sre/sandbox/emea/artifactory

# Bulk import them in one call
gi repo state import gi_proxy_acl_proxy_infra.github \
  "$(gi -o json proxy acl-proxy-infra list -f tenant=sre -f region=emea -f platform=sandbox | jq -r '[.[].id] | join(",")')"

The jq manual and jq playground are useful for building more selective filters (e.g. .[] | select(.name | startswith("prod"))).

# 1. Set up the local OpenTofu environment (branch auto-detected, or use --branch to force one)
gi repo state pull --gitlab-token "$GITLAB_TOKEN"
export OPENTOFU_ENCRYPTION_PASSPHRASE=<value from command output>
source .envrc

# 2. Import the resource and generate its configuration
gi repo state import gi_proxy_acl_proxy_infra.github sre/sandbox/emea/github

# 3. Validate locally — there should be no diff if the import is clean
tofu plan

# 4. Optionally refactor github.tf (see below), then create a branch and open an MR
git checkout -b feat/import-github-acl
git add github.tf _imports.tf
git commit -m "feat: import gi_proxy_acl_proxy_infra.github"
git push origin feat/import-github-acl
# Open a merge request — the pipeline will run tofu validate

# 5. Once the MR is merged to a protected branch, tofu apply runs automatically via CI/CD

Do not run tofu apply locally. The apply must go through the CI/CD pipeline on a protected branch to ensure proper access controls, auditability, and consistent state management.

After the apply

Once the pipeline has successfully applied, the import block in _imports.tf has no further effect. You may keep the file committed as a historical record of which resources were brought under management — it is harmless to leave in place.

Refactoring the generated configuration

The resource block generated in --out can be freely reorganised after the import:

  • Split across multiple .tf files.
  • Moved into a shared module.
  • Renamed and restructured to match the conventions of your repository.

There is no constraint on where the resource block lives, as long as it remains within the same OpenTofu working directory (or is properly referenced if moved to a module).

Examples

# Import a resource — configuration is written to github.tf by default
gi repo state import gi_proxy_acl_proxy_infra.github sre/sandbox/emea/github

# Write the generated configuration to a custom file
gi repo state import gi_proxy_acl_proxy_infra.github sre/sandbox/emea/github --out acl.tf

# Import a DNS record
gi repo state import gi_dns_record.api sre/sandbox/emea/api.example.com

# Bulk import — several resources of the same type in one call.
# Resources are named github_1 and github_2 in the generated HCL.
gi repo state import gi_proxy_acl_proxy_infra.github \
  sre/sandbox/emea/github_test1,sre/sandbox/emea/github_test2

On this page