hashicorp / terraform-cdk

Define infrastructure resources using programming constructs and provision them using HashiCorp Terraform
https://www.terraform.io/cdktf
Mozilla Public License 2.0
4.78k stars 441 forks source link

`terraform.workspace` equivalent #376

Open damienpontifex opened 3 years ago

damienpontifex commented 3 years ago

Community Note

Description

Searching around and I couldn't find an equivalent for Current Workspace Interpolation in terraform-cdk. Currently we use this in terraform for environment suffixes and would be nice to transfer that capability across to our usage with cdktf

cmclaughlin commented 3 years ago

I was chatting with @jsteinich about this yesterday... I was going to open an issue this morning, but you beat me to it :)

Not sure if you're seeing the exact same thing as me, but there are situations where the cdktf-cli hangs when Terraform prompts about workspaces. Here's an example:

$ CDKTF_LOG_LEVEL=DEBUG TF_WORKSPACE=qa cdktf deploy
[2020-09-17T15:40:32.112] [DEBUG] default - Terraform v0.12.29

[2020-09-17T15:40:32.578] [DEBUG] default - + provider.aws v2.70.0

[2020-09-17T15:40:32.579] [DEBUG] default -
Your version of Terraform is out of date! The latest version
is 0.13.2. You can update by downloading from https://www.terraform.io/downloads.html

⠼ synthesizing ...
[2020-09-17T15:40:34.689] [DEBUG] default - REGION is not set. Defaulting to "us-east-1".

⠹ initializing my-stack-qa-us-east-1...
[2020-09-17T15:40:35.369] [DEBUG] default - 2020/09/17 15:40:35 [DEBUG] Using modified User-Agent: Terraform/0.12.29 cdktf/0.0.17 (+https://github.com/hashicorp/terraform-cdk)

2020/09/17 15:40:35 [DEBUG] Using modified User-Agent: Terraform/0.12.29 cdktf/0.0.17 (+https://github.com/hashicorp/te⠸ initializing my-stack-qa-us-east-1...
[2020-09-17T15:40:35.389] [DEBUG] default -

[2020-09-17T15:40:35.389] [DEBUG] default - Initializing the backend...
⠸ initializing my-stack-qa-us-east-1...
[2020-09-17T15:40:37.103] [DEBUG] default -

[2020-09-17T15:40:37.103] [DEBUG] default - The currently selected workspace (qa) does not exist.
  This is expected behavior when the selected workspace did not have an

[2020-09-17T15:40:37.103] [DEBUG] default -   existing non-empty state. Please enter a number to select a workspace:

  1. default

[2020-09-17T15:40:37.103] [DEBUG] default -
⠴ initializing my-stack-qa-us-east-1...
# This hangs...

Sorry to conflate these, but I think this is generally related to another problem I've seen initializing state with cdktf deploy - if there are changes to the state, Terraform prompts and cdktf-cli hangs on this too:

[2020-09-17T15:39:22.236] [DEBUG] default - Do you want to copy existing state to the new backend?

[2020-09-17T15:39:22.236] [DEBUG] default -   Pre-existing state was found while migrating the previous "s3" backend to the

[2020-09-17T15:39:22.236] [DEBUG] default -   newly configured "s3" backend. No existing state was found in the newly

[2020-09-17T15:39:22.236] [DEBUG] default -   configured "s3" backend. Do you want to copy this state to the new "s3"

[2020-09-17T15:39:22.236] [DEBUG] default -   backend? Enter "yes" to copy and "no" to start with an empty state.
# This hangs
skorfmann commented 3 years ago

@cmclaughlin

Sorry to conflate these, but I think this is generally related to another problem I've seen initializing state with cdktf deploy - if there are changes to the state, Terraform prompts and cdktf-cli hangs on this too:

Hm, I thought -input=false should have fixed these kind of issues (see here https://github.com/hashicorp/terraform-cdk/pull/343)

Which version are you running?

skorfmann commented 3 years ago

@damienpontifex

Searching around and I couldn't find an equivalent for Current Workspace Interpolation in terraform-cdk. Currently we use this in terraform for environment suffixes and would be nice to transfer that capability across to our usage with cdktf

The interpolation itself should just work:

new Instance(this, 'aws instance', {
  name:  "web - ${terraform.workspace}"
})

Could you elaborate on the use-case you're trying to solve?

cmclaughlin commented 3 years ago

Debug output shows I was using:

[DEBUG] Using modified User-Agent: Terraform/0.12.29 cdktf/0.0.17
skorfmann commented 3 years ago

Debug output shows I was using:

[DEBUG] Using modified User-Agent: Terraform/0.12.29 cdktf/0.0.17

Hm, interesting. Well, that's something to investigate then, but it's probably a separate issue?

damienpontifex commented 3 years ago

@skorfmann I understand the interpolation - maybe it's just that I couldn't find the terraform variable/global as appropriate to access this property as ${terraform.workspace}.

My workaround was that I was setting up the RemoteBackend as below and if custom workspace needing run it as WORKSPACE=ponti-dev cdktf deploy

const workspaceName = process.env.WORKSPACE || 'dev'
new Instance(this, 'aws instance', {
  name:  "web - ${workspaceName}"
})
new RemoteBackend(stack, {
  organization: 'pontifex',
  workspaces: {
    name: workspaceName
  },
});

Then I can reuse the workspaceName local. This works fine for our use cases. Probably I was looking to how some of our existing code would translate.

Just seeing if this was the current recommended? Or similar to how we would do terraform workspace select ponti-dev && terraform apply is there an equivalent with cdktf

jsteinich commented 3 years ago

@damienpontifex TF_WORKSPACE is supported by Terraform, but as @cmclaughlin noted, there are currently some issues with it.

skorfmann commented 3 years ago

@damienpontifex what you're looking for is 1st class support Terraform workspaces in cdktf?

damienpontifex commented 3 years ago

Something like that @skorfmann. We use workspace naming to differentiate developer environments from UAT and prod etc so use the naming for unique suffixes across resources. So we could adapt the terraform.workspace variable that's available in HCL terraform to something different here, just looking to see what was the recommended approach in Typescript. It might be that we do something like const workspaceName = process.env.TF_WORKSPACE if that's the recommended approach with cdktf

nick0lay commented 3 years ago

We are using the following approach to access workspace in the code:

const workspace = Token.asString("${terraform.workspace}");
new Instance(this, `${workspace}-app-instance`, {
  name:  "Application instance"
})
jsteinich commented 3 years ago

This would be pretty simple to add after #525 is completed.

javier-caylent commented 2 years ago

We are using the following approach to access workspace in the code:

const workspace = Token.asString("${terraform.workspace}");
new Instance(this, `${workspace}-app-instance`, {
  name:  "Application instance"
})

Hi, on which folder are you executing the cdk synth command?, I mean in the cdktf.out folder or if you have an example about the folder structure to have this approach, I tried but the workspace that I'm getting is default

jsteinich commented 2 years ago

Hi, on which folder are you executing the cdk synth command?, I mean in the cdktf.out folder or if you have an example about the folder structure to have this approach, I tried but the workspace that I'm getting is default

cdktf synth won't actually know the workspace. It will just generate access to the interpolation. cdktf diff/deploy will be able to access it (assuming the workspace already exists), but it can be a bit tricky to setup. The simplest approach is probably using the TF_WORKSPACE environment variable (if workspace exists from elsewhere), or running Terraform commands directly if the workspace doesn't exist. Note that when running Terraform commands directly, you'll need to navigate to the specific stack directory.

I think this is something that would be good to have better built in support for, but I'm not aware of any planned work as of yet. @javier-caylent how are you hoping to use workspaces?

nick0lay commented 2 years ago

We are using the following approach to access workspace in the code:

const workspace = Token.asString("${terraform.workspace}");
new Instance(this, `${workspace}-app-instance`, {
  name:  "Application instance"
})

Hi, on which folder are you executing the cdk synth command?, I mean in the cdktf.out folder or if you have an example about the folder structure to have this approach, I tried but the workspace that I'm getting is default

Hi @javier-caylent! Since CDKTF generate stack to run it with terraform it's important in which folder to run command to switch workspace, for example terraform workspace new my_workspace. Terraform put generated stacks in cdktf.out/stacks. For example if you have stack my_stack you can find generated assets in cdktf.out/stacks/my_stack, hence if you want to change workspace for my_stack you should navigate to cdktf.out/stacks/my_stack and switch to required workspace in this folder. After changing workspace you should see cdktf.out/stacks/my_stack/.terraform/environment following file created.

The full flow:

  1. cdktf synth - generate stack
  2. cd cdktf.out/stacks/my_stack - navigate to stack folder
  3. terraform workspace new my_workspace - switch to required workspace
  4. cdktf deploy my_stack
javier-caylent commented 2 years ago

Awesome, You help me a lot with this information. Thank you very much!!! @nick0lay.

nick0lay commented 2 years ago

Awesome, You help me a lot with this information. Thank you very much!!! @nick0lay.

Welcome!

There is one more thing which you might be aware of. Even though that you have access to workspace in cdktf, workspaces works differently in terraform and cdktf in terms of state isolation.

Terrafrom If you switch workspace to my_workspace terraform will save state to terraform.tfstate.d/my_workspace/terraform.tfstate it you switch workspace to my_new_workspace hence new state will be saved to project_root/terraform.tfstate.d/my_new_workspace/terraform.tfstate

CDKTF For this case workspaces works a bit differently. If you switch workspace you will be able to see folder project_root/cdktf.out/stacks/cdktf_example/terraform.tfstate.d/my_workspace but terraform.tfstate won't be there. Instead cdktf create it in the following place project_root/terraform.cdktf_example.tfstate where cdktf_example is stack name.

const app = new App();
new MyStack(app, "cdktf_example");
app.synth();

Hence when you use cdktf it's better separate states by creating different stacks rather than use workspaces. For example:

const app = new App();
new MyStack(app, "cdktf_example_stack");
new MyStack(app, "cdktf_example_test_stack");
app.synth();

But switching workspace still might be useful if you use cdktf with terraform modules which rely on workspace variable ${terraform.workspace} inside it.

javier-caylent commented 2 years ago

Hi @nick0lay

hmm, got it, I think that we can "emulate" the ${terraform.workspace}, sending the environment for each stack and get the variables values in the same way.

I'll test it and see, how works this approach and the s3 remote state (S3Backend) and where will be store the tfstate file for each stack.

from imports.my_compute_module import MyComputeModule
from variables import Variables

class MyStackConfig(object):
    def __init__(self, environment: str, region: str):
        self.environment = environment
        self.region = region 

class MyStack(TerraformStack):
    def __init__(self, scope: Construct, ns: str, config: MyStackConfig):
        myec2 = MyComputeModule(self,
            'my-ec2', 
            ami = Variables.get("ami").get(config.environment),
            instance_type = Variables.get("instance_type").get(config.environment),
            vpc_security_group_ids = Variables.get("vpc_security_group_ids").get(config.environment),
            subnet_id = Variables.get("vpc").get("public_subnets_ids").get(config.environment),
            key_name = Variables.get("key_name").get(config.environment),
            additional_tags = { "Name": f"python-{config.environment}" }
        )

app = App();
MyStack(app, "cdktf_example_dev", {"environment": "dev", "region": "us-east-1"});
MyStack(app, "cdktf_example_stg", {"environment": "stg", "region": "us-east-1"});
app.synth();