diff --git a/.gitignore b/.gitignore index 9fe17bc..e78f621 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,37 @@ venv.bak/ dmypy.json # Pyre type checker -.pyre/ \ No newline at end of file +.pyre/ + +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +out/* +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index f691dd7..cff8560 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,38 @@ Migrate workloads from other platforms to [StackGuardian Platform](https://app.s - Terraform - terraform login to ensure that Terraform can interact with your Terraform Cloud/Enterprise account. - [sg-cli](https://github.com/StackGuardian/sg-cli/tree/main/shell) + +### Perform terraform login +`terraform login` + +### Export the resource definitions and Terraform state + +- Choose the transformer and locate the example of `terraform.tfvars`. +- Edit that file ( terraform.tfvars) to match your context. +- Run the following commands: + +```shell +cd transformer/tfc +terraform init +terraform apply -auto-approve -var-file=terraform.tfvars +``` + +A new `out` folder should have been created. The `sg-payload.json` file contains the definition for each workflow that will be created for each Terraform Workspace, and the `state-files` folder contains the files for the Terraform state for each of your workspaces, if the state export was enabled. + +After completing the export , edit the `sg-payload.json` file to provide tune each workflow configuration with the following: +- `DeploymentPlatformConfig` - (Used to authenticate against a cloud provider using a StackGuardian Integration), Create the relevant integration in StackGuardian platform and update `DeploymentPlatformConfig.kind` from the following "AZURE_STATIC", "AWS_STATIC","GCP_STATIC", "AWS_RBAC". Update `DeploymentPlatformConfig.config.integrationId` with "/integrations/INTEGRRATION_NAME" and `DeploymentPlatformConfig.config.profileName` with the name of the integration used upon creation. +- `VCSConfig` - Provide full path to the `repo` like as well the relevant `sourceConfigDestKind` from the following "GITHUB_COM", "BITBUCKET_ORG", "GITLAB_COM", "AZURE_DEVOPS". + +### Bulk import workflows to StackGuardian Platform + +- Fetch sg-cli (https://github.com/StackGuardian/sg-cli.git) and set up sg-cli locally (documentation present in repo) +- Run the following commands and pass the `sg-payload.json` as payload (represented below) + +```shell +cd ../../out + +Get your SG API Key here: https://app.stackguardian.io/orchestrator/orgs//settings?tab=api_key + +export SG_API_TOKEN= +wget -q "$(wget -qO- "https://api.github.com/repos/stackguardian/sg-cli/releases/latest" | jq -r '.tarball_url')" -O sg-cli.tar.gz && tar -xf sg-cli.tar.gz && rm -f sg-cli.tar.gz && /bin/cp -rf StackGuardian-sg-cli*/shell/sg-cli . && rm -rfd StackGuardian-sg-cli* && ./sg-cli workflow create --bulk --org "stackguardian" -- sg-payload.json +``` \ No newline at end of file diff --git a/transformer/tfc/.gitignore b/transformer/tfc/.gitignore new file mode 100644 index 0000000..cfdfe96 --- /dev/null +++ b/transformer/tfc/.gitignore @@ -0,0 +1,29 @@ +# Local .terraform directories +**/.terraform/* + +# Terraform lockfile +.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Exclude all .tfvars files, which are likely to contain sentitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc \ No newline at end of file diff --git a/transformer/tfc/main.py b/transformer/tfc/main.py deleted file mode 100644 index b8f0e3d..0000000 --- a/transformer/tfc/main.py +++ /dev/null @@ -1,70 +0,0 @@ -import json - -def create_new_json(input_file1, input_file2): - try: - # Load data from the first input JSON file - with open(input_file1, 'r') as f1: - data1 = json.load(f1) - - - - #patch the data to the sg_payload - resources = (data1["resources"]) - env = [] - resource_names=[] - for i in resources: - if i["type"] == "tfe_variables": - instances = i["instances"] - for j in instances: - if j["attributes"]["env"]: - for k in j["attributes"]["env"]: - if k["category"] == "env": - env.append({k["name"] : k["value"]}) - if j["attributes"]["variables"]: - for k in j["attributes"]["variables"]: - if k["category"] == "env": - env.append({k["name"] : k["value"]}) - - if i["type"] == "tfe_workspace": - workspace_names = i["instances"] - for j in workspace_names: - workspace_name = j["index_key"] - description = j["attributes"]["description"] - tags = j["attributes"]["tag_names"] - env = env - resource_names.append({"ResourceName": workspace_name, "Description" : description, "Tags" : tags, "EnvironmentVariables" : env}) - - - - for i in resource_names: - # Load data from the second input JSON file - with open(input_file2, 'r') as f2: - data2 = json.load(f2) - - data2["ResourceName"] = i["ResourceName"] - data2["Description"] = i["Description"] - data2["Tags"] = i["Tags"] - data2["EnvironmentVariables"] = i["EnvironmentVariables"] - - with open(i["ResourceName"] + ".json", 'w') as out_f: - json.dump( data2, out_f, indent=4) - print(f"New JSON file {i['ResourceName']}.json created successfully.") - - # Write the data from the second input JSON file to the output JSON file - # with open(output_file, 'w') as out_f: - # json.dump(data2, out_f, indent=4) - - - - except FileNotFoundError: - print("One or both input files not found.") - except json.JSONDecodeError: - print("Error decoding JSON data from the input file.") - -# Provide the paths of the input JSON files and the output JSON file -input_json_file1 = input("Enter the location the state file from Terraform cloud: ") -print(input_json_file1) -input_json_file2 = 'sg_payload.json' - - -create_new_json(input_json_file1,input_json_file2) \ No newline at end of file diff --git a/transformer/tfc/main.tf b/transformer/tfc/main.tf index 98e9a82..a7af889 100644 --- a/transformer/tfc/main.tf +++ b/transformer/tfc/main.tf @@ -23,75 +23,81 @@ locals { workflow_ids = [for i, v in data.tfe_workspace_ids.all.ids : v] workflow_names = [for i, v in data.tfe_workspace_ids.all.ids : i] workflows = [for i, v in data.tfe_workspace_ids.all.ids : { - ResourceName = data.tfe_workspace.all[i].name - wfgrpName = "" - Description = "" - Tags = data.tfe_workspace.all[i].tag_names - EnvironmentVariables = [for i, v in data.tfe_variables.all[v].variables : { - hcl = v.hcl - name = v.category == "terraform" ? "TF_VAR_${v.name}" : v.name - sensitive = v.sensitive - value = v.value - }] + CLIConfiguration = { + "WorkflowGroup":{ + "name": data.tfe_workspace.all[i].project_id + }, + "TfStateFilePath" : "${abspath(path.root)}/../../out/state-files/${data.tfe_workspace.all[i].name}.tfstate" + } + ResourceName = data.tfe_workspace.all[i].name + wfgrpName = "" + Description = "" + Tags = data.tfe_workspace.all[i].tag_names + EnvironmentVariables = [for i, v in data.tfe_variables.all[v].variables : + { "config" : { + "textValue" : v.value, + "varName" : v.name + }, + "kind" : "PLAIN_TEXT" } if v.category == "env" && v.sensitive == false] DeploymentPlatformConfig = [] - RunnerConstraints = {"type": "shared"} + RunnerConstraints = { "type" : "shared" } VCSConfig = { - "iacVCSConfig": { - "useMarketplaceTemplate": false, - "customSource": { - "sourceConfigDestKind": "", - "config": { - "includeSubModule": false, - "ref": 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, - "isPrivate": true, - "auth": "/integrations/", - "workingDir": "", - "repo": length(data.tfe_workspace.all[i].vcs_repo) > 0 ? split("/", data.tfe_workspace.all[i].vcs_repo[0].identifier)[1] : "" + "iacVCSConfig" : { + "useMarketplaceTemplate" : false, + "customSource" : { + "sourceConfigDestKind" : "PLEASE PROVIDE A VALUE", + "config" : { + "includeSubModule" : false, + "ref" : 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 : "" : "", + "isPrivate" : true, + "auth" : "PLEASE PROVIDE A VALUE", + "workingDir" : "", + "repo" : length(data.tfe_workspace.all[i].vcs_repo) > 0 ? split("/", data.tfe_workspace.all[i].vcs_repo[0].identifier)[1] : "" } } }, - "iacInputData": { - "schemaType": "RAW_JSON", - "data": {} + "iacInputData" : { + "schemaType" : "RAW_JSON", + "data" : { for i, v in data.tfe_variables.all[v].variables : v.name => v.value if v.category == "terraform" } } } - + MiniSteps = { - "wfChaining": { - "ERRORED": [], - "COMPLETED": [] + "wfChaining" : { + "ERRORED" : [], + "COMPLETED" : [] }, - "notifications": { - "email": { - "ERRORED": [], - "COMPLETED": [], - "APPROVAL_REQUIRED": [], - "CANCELLED": [] + "notifications" : { + "email" : { + "ERRORED" : [], + "COMPLETED" : [], + "APPROVAL_REQUIRED" : [], + "CANCELLED" : [] } } } - + Approvers = [] - + TerraformConfig = { - "managedTerraformState": var.export_state, - "terraformVersion": data.tfe_workspace.all[i].terraform_version + "managedTerraformState" : var.export_state, + "terraformVersion" : data.tfe_workspace.all[i].terraform_version } - WfType = "" - UserSchedules = [] + WfType = "TERRAFORM" + UserSchedules = [] }] - data = jsonencode({ - "workflows" : local.workflows - }) + data = jsonencode( + local.workflows + ) } data "tfe_workspace_ids" "all" { - exclude_tags = var.tfc_workspace_exclude_tags names = var.tfc_workspace_names organization = var.tfc_organization tag_names = var.tfc_workspace_include_tags + exclude_tags = var.tfc_workspace_exclude_tags } data "tfe_workspace" "all" { @@ -109,7 +115,7 @@ data "tfe_variables" "all" { resource "local_file" "data" { content = local.data - filename = "${path.module}/../../out/data.json" + filename = "${path.module}/../../out/sg-payload.json" } resource "local_file" "generate_temp_tf_files" { diff --git a/transformer/tfc/variables.tf b/transformer/tfc/variables.tf index 34c2325..0972cbe 100644 --- a/transformer/tfc/variables.tf +++ b/transformer/tfc/variables.tf @@ -26,21 +26,3 @@ variable "tfc_workspace_include_tags" { description = "List of TFC/TFE workspace tags to include when exporting. Excluded tags take precedence over included ones. Wildcards are not supported." type = list(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 -} \ No newline at end of file