-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
That's one small step for mankind, one giant leap for a company
- Loading branch information
0 parents
commit 9f9ab7f
Showing
12 changed files
with
458 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.terraform | ||
.terraform.lock.hcl | ||
terraform.tfstate* | ||
*.tfvars | ||
!*.example.tfvars | ||
/generator.tftpl | ||
/out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# Spacelift Migration Kit | ||
|
||
This repository contains scripts to help you move from various vendors to Spacelift. | ||
|
||
There is no one-size-fits-all for this kind of migration so this kit aims at doing the heavy lifting and getting you 90% through. You will likely need to slightly tweak the generated Terraform code to fit your specific context. | ||
|
||
## Overview | ||
|
||
The migration process is as follows: | ||
|
||
- Export the definition for your resources at your current vendor. | ||
- Generate the Terraform code to recreate similar resources at Spacelift using the [Terraform provider](https://registry.terraform.io/providers/spacelift-io/spacelift/latest/docs). | ||
- Review and possibly edit the generated Terraform code. | ||
- Commit the Terraform code to a repository. | ||
- Create a manager Spacelift stack that points to the repository with the Terraform code. | ||
|
||
## Supported Source | ||
|
||
Currently, only Terraform Cloud/Enterprise is supported as a source. | ||
|
||
## Prerequisites | ||
|
||
- Terraform | ||
|
||
|
||
## Instructions | ||
|
||
### Preparation | ||
|
||
Use the `terraform login spacelift.io` command to ensure that Terraform can interact with your Spacelift account. | ||
|
||
Depending on the exporter used, you may need additional steps: | ||
|
||
- **Terraform Cloud/Enterprise**: Use the `terraform login` command to ensure that Terraform can interact with your Terraform Cloud/Enterprise account. | ||
|
||
### Pre-Migration Cleanup | ||
|
||
In order to start fresh, clean up files and folders from previous runs. | ||
|
||
```shell | ||
rm -rf ./out ./{exporters/tfc,generator,manager-stack}/.terraform ./{exporters/tfc,generator,manager-stack}/.terraform.lock.hcl ./{exporters/tfc,generator,manager-stack}/terraform.tfstate ./{exporters/tfc,generator,manager-stack}/terraform.tfstate.backup | ||
``` | ||
|
||
### Export the resource definitions and Terraform state | ||
|
||
- Choose an exporter and copy the example `.tfvars` file for it into `exporter.tfvars`. | ||
- Edit that file to match your context. | ||
- Run the following commands: | ||
|
||
```shell | ||
cd exporters/<EXPORTER> | ||
terraform init | ||
terraform apply -auto-approve -var-file=../../exporter.tfvars | ||
``` | ||
|
||
A new `out` folder should have been created. The `data.json` files contains the mapping of your vendor resources to the equivalent Spacelift resources, and the `state-files` folder contains the files for the Terraform state of your stacks, if the state export was enabled. | ||
|
||
Please note that once exported the Terraform state files can be imported into Spacelift or to any backend supported by Terraform. | ||
|
||
### Generate the Terraform code | ||
|
||
- If you want to customize the template that generates the Terraform code, run `cp ../../generator/generator.tftpl ../generator.tftpl`, and edit the `generator.tftpl` file at the root of the repository. If present, it will be used automatically. | ||
- Run the following commands: | ||
|
||
```shell | ||
cd ../../generator | ||
terraform init | ||
terraform apply -auto-approve -var-file=../out/data.json | ||
``` | ||
|
||
### Review and edit the generated Terraform code | ||
|
||
A `main.tf` should have been generated in the `out` folder. It contains all the Terraform code for your Spacelift resources. | ||
|
||
Mapping resources from a vendor to Spacelift resources is not an exact science. There are gaps in functionality and caveats in the mapping process. | ||
|
||
Please carefully review the generated Terraform code and make sure that it looks fine. If it does not, repeat the process with a different configuration or edit the Terraform code. | ||
|
||
### Commit the Terraform code | ||
|
||
When the Terraform code is ready, commit it to a repository. | ||
|
||
### Create a manager Spacelift stack | ||
|
||
It is now time to create a Spacelift stack that will point to the commited Terraform code that manages your Spacelift resources. | ||
|
||
- Copy the example `manager-stack.example.tfvars` file into `manager-stack.tfvars` . | ||
- Edit that file to match your context. | ||
- Run the following commands: | ||
|
||
```shell | ||
cd ../manager-stack | ||
terraform init | ||
terraform apply -auto-approve -var-file=../manager-stack.tfvars | ||
``` | ||
|
||
After the stack has been created, a tracked run will be triggered automatically. That run will create the defined Spacelift resources. | ||
|
||
### Post-Migration Cleanup | ||
|
||
Before you can use Spacelift to manage your infrastructure, you may need to make changes to the Terraform code for your infrastructure, depending on the Terraform state is managed. | ||
|
||
If the Terraform state is managed by Spacelift,perform the following actions, otherwise you can skip this section: | ||
|
||
- Remove any [backend](https://developer.hashicorp.com/terraform/language/settings/backends/configuration#using-a-backend-block)/[cloud](https://developer.hashicorp.com/terraform/language/settings/terraform-cloud) block from the Terraform code that manages your infrastructure to avoid a conflict with Spacelift's backend. | ||
- Delete the `import_state_file` arguments from the Terraform code that manages your Spacelift resources. | ||
- After the manager stack has successfully run, the mounted Terraform state files are not needed anymore and can be deleted by setting the `import_state` argument to `false` in the `manager-stack.tfvars` file and run `terraform apply -auto-approve -var-file=../manager-stack.tfvars` in the `manager-stack` folder. | ||
|
||
|
||
## Known Limitations | ||
|
||
### Terraform Cloud/Enterprise Exporter | ||
|
||
- The variable sets are not exposed so they cannot be listed and exported. | ||
- The name of the Version Control System (VCS) provider for a stack is not returned so it has to be set in the exporter configuration file. | ||
- When the branch for the stack is the repository default branch, the value is empty. You can set the value for the default branch in the exporter configuration file, or edit the generated Terraform code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Terraform Cloud/Enterprise organization name | ||
tfc_organization = "" | ||
|
||
# Export Terraform state to files? | ||
export_state = true | ||
|
||
# Terraform Cloud/Enterprise does not return the VCS provider name so we use the value below instead. | ||
vcs_provider = "github" | ||
|
||
# The name of the entity containing the repository. | ||
# The value should be empty for GitHub.com, the user/organization for GitHub (custom application), | ||
# the project for Bitbucket, and the namespace for Gitlab. | ||
vcs_namespace = "" | ||
|
||
# When the branch for the stack is the repository's default branch, | ||
# the value is empty so we use the value provided below instead | ||
vcs_default_branch = "main" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
locals { | ||
stack_ids = [for i, v in data.tfe_workspace_ids.all.ids : v] | ||
stack_names = [for i, v in data.tfe_workspace_ids.all.ids : i] | ||
stacks = [for i, v in data.tfe_workspace_ids.all.ids : { | ||
autodeploy = data.tfe_workspace.all[i].auto_apply | ||
env_vars = [for i, v in data.tfe_variables.all[v].variables : { | ||
name = v.category == "terraform" ? "TF_VAR_${v.name}" : v.name | ||
sensitive = v.sensitive | ||
value = v.value | ||
}] | ||
labels = data.tfe_workspace.all[i].tag_names | ||
manage_state = var.export_state | ||
name = data.tfe_workspace.all[i].name | ||
terraform_version = data.tfe_workspace.all[i].terraform_version | ||
vcs = { | ||
# The "identifier" argument contains the acccount/organization and the respository names, separated by a slash | ||
account = length(data.tfe_workspace.all[i].vcs_repo) > 0 ? split("/", data.tfe_workspace.all[i].vcs_repo[0].identifier)[1] : "" | ||
|
||
# When the branch for the stack is the repository's default branch, the value is empty so we use the value provided via the variable | ||
branch = length(data.tfe_workspace.all[i].vcs_repo) > 0 ? data.tfe_workspace.all[i].vcs_repo[0].branch != "" ? data.tfe_workspace.all[i].vcs_repo[0].branch : var.vcs_default_branch : var.vcs_default_branch | ||
|
||
namespace = var.vcs_namespace | ||
project_root = data.tfe_workspace.all[i].working_directory | ||
|
||
# TFC/TFE does not return the VCS provider name so we use the value provided via the variable | ||
provider = var.vcs_provider | ||
|
||
# The "identifier" argument contains the acccount/organization and the respository names, separated by a slash | ||
repository = length(data.tfe_workspace.all[i].vcs_repo) > 0 ? split("/", data.tfe_workspace.all[i].vcs_repo[0].identifier)[1] : "" | ||
} | ||
}] | ||
data = jsonencode({ | ||
"stacks" : local.stacks | ||
}) | ||
} | ||
|
||
data "tfe_workspace_ids" "all" { | ||
names = ["*"] | ||
organization = var.tfc_organization | ||
} | ||
|
||
data "tfe_workspace" "all" { | ||
for_each = toset(local.stack_names) | ||
|
||
name = each.key | ||
organization = var.tfc_organization | ||
} | ||
|
||
data "tfe_variables" "all" { | ||
for_each = toset(local.stack_ids) | ||
|
||
workspace_id = each.key | ||
} | ||
|
||
resource "local_file" "data" { | ||
content = local.data | ||
filename = "${path.module}/../../out/data.json" | ||
} | ||
|
||
resource "local_file" "generate_temp_tf_files" { | ||
for_each = var.export_state ? toset(local.stack_names) : [] | ||
|
||
content = templatefile("${path.module}/main.tftpl", { tfc_organization = var.tfc_organization, workspace = each.key }) | ||
filename = "${path.module}/../../out/tf-files/${each.key}/main.tf" | ||
} | ||
|
||
resource "null_resource" "export_state_files" { | ||
depends_on = [local_file.generate_temp_tf_files] | ||
for_each = var.export_state ? toset(local.stack_names) : [] | ||
|
||
provisioner "local-exec" { | ||
command = "mkdir -p ../../state-files && rm -rf .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup && terraform init -input=false && terraform state pull > ../../state-files/'${each.key}.tfstate'" | ||
working_dir = "${path.module}/../../out/tf-files/${each.key}" | ||
} | ||
} | ||
|
||
resource "null_resource" "delete_temp_tf_files" { | ||
count = var.export_state ? 1 : 0 | ||
depends_on = [null_resource.export_state_files] | ||
|
||
provisioner "local-exec" { | ||
command = "rm -rf tf-files" | ||
working_dir = "${path.module}/../../out/" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
terraform { | ||
cloud { | ||
organization = "${tfc_organization}" | ||
workspaces { | ||
name = "${workspace}" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
variable "export_state" { | ||
default = true | ||
description = "Export Terraform state to files?" | ||
type = bool | ||
} | ||
|
||
variable "tfc_organization" { | ||
description = "TFC/TFE organization name" | ||
type = string | ||
} | ||
|
||
variable "vcs_default_branch" { | ||
default = "main" | ||
description = "Name of the repositories' default branch" | ||
type = string | ||
} | ||
|
||
variable "vcs_namespace" { | ||
default = "" | ||
description = "The name of the entity containing the repository. The value should be empty for GitHub.com, the user/organization for GitHub (custom application), the project for Bitbucket, and the namespace for Gitlab." | ||
type = string | ||
} | ||
|
||
variable "vcs_provider" { | ||
default = "github" | ||
description = "Name of the Version Control System (VCS) provider to use" | ||
type = string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
terraform { | ||
required_providers { | ||
spacelift = { | ||
source = "spacelift-io/spacelift" | ||
version = "~> 0.1" | ||
} | ||
} | ||
} | ||
%{ for stack in stacks ~} | ||
|
||
resource "spacelift_stack" "${replace(lower(stack.name), "-", "_")}" { | ||
%{if stack.vcs.provider != "github" ~} | ||
${stack.vcs.provider} { | ||
namespace = "${stack.vcs.namespace}" | ||
} | ||
%{endif ~} | ||
autodeploy = ${stack.autodeploy} | ||
branch = "${stack.vcs.branch}" | ||
name = "${stack.name}" | ||
project_root = "${stack.vcs.project_root}" | ||
repository = "${stack.vcs.repository}" | ||
terraform_version = "${stack.terraform_version}" | ||
|
||
%{if stack.manage_state ~} | ||
# 8< -------------------------------------------------------------- | ||
# Delete the following line after the stack has been created | ||
import_state_file = "/mnt/workspace/state-import/${stack.name}.tfstate" | ||
# -------------------------------------------------------------- 8< | ||
%{endif ~} | ||
} | ||
|
||
%{ for env_var in stack.env_vars ~} | ||
resource "spacelift_environment_variable" "${replace(lower(env_var.name), "-", "_")}" { | ||
stack_id = spacelift_stack.${replace(lower(stack.name), "-", "_")}.id | ||
name = "${env_var.name}" | ||
value = "${env_var.value}" | ||
write_only = ${env_var.sensitive} | ||
} | ||
%{ endfor ~} | ||
%{ endfor ~} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
locals { | ||
# Use custom template file, if present | ||
template_file = fileexists("${path.module}/../generator.tftpl") ? "${path.module}/../generator.tftpl" : "${path.module}/generator.tftpl" | ||
} | ||
|
||
resource "local_file" "terraform" { | ||
content = templatefile(local.template_file, { stacks = var.stacks }) | ||
filename = "${path.module}/../out/main.tf" | ||
|
||
provisioner "local-exec" { | ||
command = "terraform fmt ${self.filename}" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
variable "stacks" { | ||
description = "Stacks to import" | ||
type = list(object({ | ||
autodeploy = bool | ||
env_vars = list(object({ | ||
name = string | ||
sensitive = bool | ||
value = string | ||
})) | ||
manage_state = bool | ||
name = string | ||
terraform_version = string | ||
vcs = object({ | ||
account = string | ||
branch = string | ||
namespace = string | ||
project_root = string | ||
provider = string | ||
repository = string | ||
}) | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Name of the manager stack | ||
stack_name = "Spacelift Manager" | ||
|
||
# Description for the stack | ||
stack_description = "Spacelift resources manager" | ||
|
||
# Name of the repository to associate with the stack | ||
repository = "spacelift" | ||
|
||
# Name of the branch to associate with the stack | ||
branch = "main" | ||
|
||
# Path to the folder containing the Terraform code, in case of a monorepo | ||
project_root = "" | ||
|
||
# Import the Terraform state for the managed stacks into Spacelift? | ||
import_state = false | ||
|
||
# Spacelift API endpoint | ||
spacelift_api_key_endpoint = "https://example.app.spacelift.io/" | ||
|
||
# Spacelift API key ID - Alternatively, you could pass that value via the SPACELIFT_API_KEY_ID env var | ||
spacelift_api_key_id = "" | ||
|
||
# Spacelift API key secret - Alternatively, you could pass that value via the SPACELIFT_API_KEY_SECRET env var | ||
spacelift_api_key_secret = "" |
Oops, something went wrong.