Featured image of post Moving Terraform State from OpenStack Swift to GitLab

Moving Terraform State from OpenStack Swift to GitLab

Explore the migration of Terraform state from OpenStack Swift to GitLab, detailing step-by-step procedures and considerations for seamless infrastructure as code management.

Originally published on the Consensus Enterprises blog.

For our cloud computing, we typically use an OpenStack provider because of its open-source nature: There’s no vendor lock-in, and the IaaS code is peer-reviewed unlike providers such as AWS, Azure, GCP, etc. (Shout out to Vexxhost for having great support!) As such, we’ve been using OpenStack’s Swift object storage service for storing Terraform’s state, which allows Terraform to track all of the resources it manages for automating infrastructure.

Recently, however, support for the Swift backend has been removed. If you’re still using Swift for this purpose, you’ll need to migrate your Terraform state files to another backend. Because the official migration documentation is sparse, I’ll describe how to migrate from Swift to GitLab-managed Terraform state. GitLab is a fantastic option because it can be used to manage so many other aspects of your project that you need anyway: Git repository hosting, issue tracking, CI/CD, etc. We use GitLab for all of our projects so it’s a great fit for us.

The actual step of migrating the data is well supported, but there’s some required set-up before and after.

When you change backends, Terraform gives you the option to migrate your state to the new backend. This lets you adopt backends without losing any existing state.

Prerequisities

  1. Downgrade to the latest pre-1.3 Terraform version.
    • e.g. sudo apt install terraform=1.2.9
  2. Navigate to your Terraform directory.
    • cd /path/to/git/repository/terraform
  3. Move your local state files out of the way as they could be set up for a different environment.
    • mv .terraform /tmp
  4. Set up your environment variables to connect to use your existing state backend.
    • e.g. source ../openstackrc/vexxhost-...-staging-ca-ymq-1.openrc.sh
  5. Initialize Terraform from the remote state.
    • terraform init
  6. Back up your current state.
    • cp .terraform/terraform.tfstate terraform.tfstate.backup-staging
  7. In your Terraform code, in your backend stanza, replace swift with http.
  8. Unset the old state environment variables.
    • export TF_CLI_ARGS_init=
  9. Fetch one of your Gitlab personal access tokens with the api permission. If you don’t have any that aren’t expired, create a new one in your settings.

Setting variables in your local environment

To actually migrate the data, the GitLab documentation says to set a single environment variable, and then manually run terraform init with many options. Given that this is error-prone and not easily repeatable, I’d recommend using a shell script (or similar) instead.

Create a file named setup-terraform-variables, and populate it like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env bash
#############################################################################
## Set up Terraform variables.
#############################################################################

# Set up the Gitlab.com state backend.
export OS_PROJECT_CLOUD="$OS_PROJECT_DESCRIPTION-$OS_PROJECT_ENVIRONMENT-$OS_REGION_NAME"
export TF_GITLAB_PROJECT_ID="<Copy this from your GitLab project page>"
echo "Please enter your gitlab.com username: "
read -r TF_GITLAB_USERNAME
export TF_GITLAB_USERNAME
echo "Please enter your gitlab.com personal access token: "
read -sr TF_GITLAB_PASSWORD
export TF_GITLAB_PASSWORD
export TF_STATE_ADDRESS="https://gitlab.com/api/v4/projects/${TF_GITLAB_PROJECT_ID}/terraform/state/${OS_PROJECT_CLOUD}"
export TF_CLI_ARGS_init="\
-backend-config='address=$TF_STATE_ADDRESS' \
-backend-config='lock_address=$TF_STATE_ADDRESS/lock' \
-backend-config='unlock_address=$TF_STATE_ADDRESS/lock' \
-backend-config='username=$TF_GITLAB_USERNAME' \
-backend-config='password=$TF_GITLAB_PASSWORD' \
-backend-config='lock_method=POST' \
-backend-config='unlock_method=DELETE' \
-backend-config='retry_wait_min=5'"

You can set other Terraform variables in here as well, and include it in other deployment-environment-specific shell scripts that you run to set up each one. For example, if you’re using OpenStack generally, these would be your openstackrc files, which contain your credentials for accessing the API.

For a further optimization, you can write the GitLab credentials to a local file so as not to have to enter them every time, but I’ll leave this as an exercise to the reader. (If I get a chance, I’ll come back here and update it.)

Changing the backend type

In your Terraform configuration files, it’s necessary to change the backend type from Swift to HTTP.

1
2
3
4
5
6
7
 terraform {
-   backend "swift" {
+   backend "http" {
     # Must be read from environment variable `TF_CLI_ARGS_init` because normal
     # variables cannot be used here.
   }
 }

If you’re wondering why we need to use TF_CLI_ARGS_init, and can’t use Terraform variables in the stanza, see my earlier article Setting Deployment Environments’ Terraform State Backends with Environment Variables .

Migrating the data

You can now run:

  • terraform init

You should now see something like this, which requires your confirmation part-way through.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Initializing the backend...
Terraform detected that the backend type changed from "swift" to "http".

Acquiring state lock. This may take a few moments...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "swift" backend to the
  newly configured "http" backend. No existing state was found in the newly
  configured "http" backend. Do you want to copy this state to the new "http"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

Releasing state lock. This may take a few moments...

Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
[...]

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Resetting your local environment to use the new state storage

This will purge your old state, and set it up to match the new remote state.

  1. Move your local state files out of the way as they reflect the old state backend.
    • mv .terraform /tmp
  2. Upgrade Terraform to the latest version.
    • e.g. sudo apt install terraform
  3. Set up your environment variables again, using the updated code, to connect to your desired cloud environment. This script must include setup-terraform-variables as discussed above.
    • source ../openstackrc/vexxhost-...-staging-ca-ymq-1.openrc.sh
  4. Initialize Terraform from the remote state.
    • terraform init

Confirming the presense of the remote state files

You can now see any of your state files in the GitLab Web UI on your Gitlab project’s page. Simply navigate to Infrastructure -> Terraform, and they’ll be listed.

Built with Hugo
Theme Stack designed by Jimmy