Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate optimisation opportunities for workspaces w/ symlinks #1398

Open
radeksimko opened this issue Sep 12, 2023 · 8 comments
Open

Investigate optimisation opportunities for workspaces w/ symlinks #1398

radeksimko opened this issue Sep 12, 2023 · 8 comments
Labels
enhancement New feature or request performance Gotta go fast

Comments

@radeksimko
Copy link
Member

radeksimko commented Sep 12, 2023

Originally posted by @briceburg in #1375 (comment)


Ever since switching to a monolithic structure (where all terraform configurations under a single repo) the vs code extension has become unresponsive/unusable for me. even when using v0.31.5 of the language server.

Repo structure looks like;

.
├── CODEOWNERS
├── README.md
├── acme
│   ├── bar
│   │   ├── README.md
│   │   ├── __global.tf
│   │   ├── docker-compose.yml
│   │   ├── environments
│   │   │   ├── _common.tf
│   │   │   ├── _variables-legacy.tf
│   │   │   ├── dev
│   │   │   │   ├── __global.tf -> ../../__global.tf
│   │   │   │   ├── _common.tf -> ../_common.tf
│   │   │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
│   │   │   │   ├── main.tf
│   │   │   │   ├── service_account.yaml -> ../../service_account.yaml
│   │   │   │   ├── vgs.auto.tfvars -> ../../secrets/acme-secrets/vgs.auto.tfvars
│   │   │   │   └── vgs.dev.sh -> ../../secrets/acme-secrets/vgs.dev.sh
│   │   │   ├── prod
│   │   │   │   ├── __global.tf -> ../../__global.tf
│   │   │   │   ├── _common.tf -> ../_common.tf
│   │   │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
│   │   │   │   ├── main.tf
│   │   │   │   ├── migration-20230731.tf
│   │   │   │   ├── service_account.yaml -> ../../service_account.yaml
│   │   │   │   ├── vgs.auto.tfvars -> ../../secrets/acme-secrets/vgs.auto.tfvars
│   │   │   │   └── vgs.prod.sh -> ../../secrets/acme-secrets/vgs.prod.sh
│   │   │   ├── sandbox
│   │   │   │   ├── __global.tf -> ../../__global.tf
│   │   │   │   ├── _common.tf -> ../_common.tf
│   │   │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
│   │   │   │   ├── main.tf
│   │   │   │   ├── migration-20230730.tf
│   │   │   │   ├── service_account.yaml -> ../../service_account.yaml
│   │   │   │   ├── vgs.auto.tfvars -> ../../secrets/acme-secrets/vgs.auto.tfvars
│   │   │   │   └── vgs.sandbox.sh -> ../../secrets/acme-secrets/vgs.sandbox.sh
│   │   │   └── stg
│   │   │       ├── __global.tf -> ../../__global.tf
│   │   │       ├── _common.tf -> ../_common.tf
│   │   │       ├── _variables-legacy.tf -> ../_variables-legacy.tf
│   │   │       ├── main.tf
│   │   │       ├── service_account.yaml -> ../../service_account.yaml
│   │   │       ├── vgs.auto.tfvars -> ../../secrets/acme-secrets/vgs.auto.tfvars
│   │   │       └── vgs.stg.sh -> ../../secrets/acme-secrets/vgs.stg.sh
│   │   ├── modules
│   │   │   └── main
│   │   │       ├── locals.tf
│   │   │       ├── main.tf
│   │   │       ├── outputs.tf
│   │   │       └── variables.tf
│   │   ├── secrets
│   │   └── service_account.yaml
│   └── foo
│       ├── README.md
│       ├── __global.tf
│       ├── environments
│       │   ├── _common.tf
│       │   ├── _variables-legacy.tf
│       │   ├── review
│       │   │   ├── __global.tf -> ../../__global.tf
│       │   │   ├── _common.tf -> ../_common.tf
│       │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
│       │   │   ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
│       │   │   ├── datadog_secrets.auto.tfvars -> ../../secrets/acme-secrets/datadog_secrets.auto.tfvars
│       │   │   ├── main.tf
│       │   │   └── service_secrets.auto.tfvars -> ../../secrets/acme-secrets/service_secrets.auto.tfvars
│       │   └── sandbox
│       │       ├── __global.tf -> ../../__global.tf
│       │       ├── _common.tf -> ../_common.tf
│       │       ├── _variables-legacy.tf -> ../_variables-legacy.tf
│       │       ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
│       │       ├── datadog_secrets.auto.tfvars -> ../../secrets/acme-secrets/datadog_secrets.auto.tfvars
│       │       ├── main.tf
│       │       ├── migration-20230721.tf
│       │       └── service_secrets.auto.tfvars -> ../../secrets/acme-secrets/service_secrets.auto.tfvars
│       ├── modules
│       │   └── main
│       │       ├── data.tf
│       │       ├── locals.tf
│       │       ├── main.tf
│       │       ├── outputs.tf
│       │       ├── services-mailcatch.tf
│       │       ├── services.tf
│       │       └── variables.tf
│       ├── namespaces
│       │   ├── _common.tf
│       │   ├── review
│       │   │   ├── __global.tf -> ../../__global.tf
│       │   │   ├── _common.tf -> ../_common.tf
│       │   │   ├── main.tf
│       │   │   └── security.tf
│       │   └── sandbox
│       │       ├── __global.tf -> ../../__global.tf
│       │       ├── _common.tf -> ../_common.tf
│       │       ├── main.tf
│       │       └── security.tf
│       └── secrets
├── platform
│   ├── backstage
│   │   └── TBD
│   └── sample-app
│       ├── README.md
│       ├── __global.tf
│       ├── environments
│       │   ├── _common.tf
│       │   ├── dev
│       │   │   ├── __global.tf -> ../../__global.tf
│       │   │   ├── _common.tf -> ../_common.tf
│       │   │   └── main.tf
│       │   └── review
│       │       ├── __global.tf -> ../../__global.tf
│       │       ├── _common.tf -> ../_common.tf
│       │       └── main.tf
│       ├── modules
│       │   └── main
│       │       ├── data.tf
│       │       ├── locals.tf
│       │       ├── main.tf
│       │       ├── outputs.tf
│       │       ├── providers.tf
│       │       └── variables.tf
│       └── namespaces
│           ├── _common.tf
│           ├── dev
│           │   ├── __global.tf -> ../../__global.tf
│           │   ├── _common.tf -> ../_common.tf
│           │   └── main.tf
│           └── review
│               ├── __global.tf -> ../../__global.tf
│               ├── _common.tf -> ../_common.tf
│               └── main.tf
└── sso
    ├── README.md
    ├── namespaces
    │   ├── _common.tf
    │   ├── dev
    │   │   ├── _common.tf -> ../_common.tf
    │   │   ├── main.tf
    │   │   └── security.tf
    │   ├── prod
    │   │   ├── _common.tf -> ../_common.tf
    │   │   ├── main.tf
    │   │   └── security.tf
    │   ├── sandbox
    │   │   ├── _common.tf -> ../_common.tf
    │   │   ├── main.tf
    │   │   └── security.tf
    │   └── stg
    │       ├── _common.tf -> ../_common.tf
    │       ├── main.tf
    │       └── security.tf
    └── token-handler
        ├── README.md
        ├── __global.tf
        ├── environments
        │   ├── _common.tf
        │   ├── _variables-legacy.tf
        │   ├── dev
        │   │   ├── __global.tf -> ../../__global.tf
        │   │   ├── _common.tf -> ../_common.tf
        │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
        │   │   ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
        │   │   ├── datadog.auto.tfvars -> ../../secrets/acme-secrets/datadog.auto.tfvars
        │   │   └── main.tf
        │   ├── prod
        │   │   ├── __global.tf -> ../../__global.tf
        │   │   ├── _common.tf -> ../_common.tf
        │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
        │   │   ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
        │   │   ├── datadog.auto.tfvars -> ../../secrets/acme-secrets/datadog.auto.tfvars
        │   │   └── main.tf
        │   ├── sandbox
        │   │   ├── __global.tf -> ../../__global.tf
        │   │   ├── _common.tf -> ../_common.tf
        │   │   ├── _variables-legacy.tf -> ../_variables-legacy.tf
        │   │   ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
        │   │   ├── datadog.auto.tfvars -> ../../secrets/acme-secrets/datadog.auto.tfvars
        │   │   └── main.tf
        │   └── stg
        │       ├── __global.tf -> ../../__global.tf
        │       ├── _common.tf -> ../_common.tf
        │       ├── _variables-legacy.tf -> ../_variables-legacy.tf
        │       ├── cloudflare.auto.tfvars -> ../../secrets/acme-secrets/cloudflare.auto.tfvars
        │       ├── datadog.auto.tfvars -> ../../secrets/acme-secrets/datadog.auto.tfvars
        │       └── main.tf
        ├── modules
        │   ├── api-gateway
        │   │   ├── data.tf
        │   │   ├── main.tf
        │   │   ├── outputs.tf
        │   │   └── variables.tf
        │   └── main
        │       ├── data.tf
        │       ├── discovery.tf
        │       ├── locals.tf
        │       ├── main.tf
        │       ├── outputs.tf
        │       └── variables.tf
        └── secrets
@radeksimko radeksimko added enhancement New feature or request performance Gotta go fast labels Sep 12, 2023
@radeksimko
Copy link
Member Author

@briceburg Thank you for describing the tree with symlinks. We can try to reproduce this but based on some other issues I have filed before I can imagine symlinks generally causing issues, performance-related or not.

I have transferred your comment here as the issue you commented on primarily focuses on different problem, which is processing that happens after file is opened.

The optimisation there could help in your case but it doesn't target symlinks in any way, so it would be incidental if you see improvements, rather than intentional.

Related:

@radeksimko
Copy link
Member Author

We have done some investigating in #1409 and have some ideas on how we can improve the performance when links are involved. It is however non-trivial, so while we still want to address this problem, we will have to weigh the complexity/effort against other work.


For these reasons (to aid prioritisation): @briceburg would you mind describing your motivations in more detail? As far as I understand, you're dealing with repetition between different customers, apps and different environments. What I'm specifically curious about is what problems are symlinks solving for you, which modules cannot solve, or solve in a suboptimal way?

On a separate note, I'm vaguely aware that symlinks are generally Unix concept and they may not translate well to Windows. How do you deal with this problem? Do you just assume/ensure that all users, CI systems etc. always run on Unix?

@github-actions
Copy link

Marking this issue as stale due to inactivity over the last 30 days. This helps our maintainers find and focus on the active issues. If this issue receives no comments in the next 30 days it will automatically be closed. Maintainers can also remove the stale label.

Thank you for understanding.

@github-actions github-actions bot added the stale label Oct 16, 2023
@briceburg
Copy link

briceburg commented Nov 10, 2023

@radeksimko I am terribly sorry for my delayed response.

would you mind describing your motivations in more detail?

primarily our reliance on symlinks is to maintain DRYness when defining providers, common variables/locals, and other core bits of the root configuration. while we previously used workspaces to handle this, in practice it became cumbersome and less flexible than having separate root configurations per environment per google's advice. we do use modules in this root configuration as would be expected, e.g.;

# foo-project/environments/production/main.tf

module "main" {
  source = "../../modules/main"
  ...
}

... but chose to use symlinks as a happy medium to stay DRY when switching from workspaces to using environment directories.

I'm vaguely aware that symlinks are generally Unix concept and they may not translate well to Windows. How do you deal with this problem?

thankfully git properly handles symlinks and I believe Windows 10+ support is better (os x is fine) -- although you are correct in that we ensure our CI systems are containerized and or under a linux runner.

@notorand-it
Copy link

Maybe I am wrong, but why not just let the language server open and parse symlinks when they match the *\.tf regex?
It looks like the program is intentionally checking whether an inode object IS NOT a symlink before opening it.
Terraform itself works well with symlynks, which are a useful way to centralize stuff without copying it over and over.

@DownRangeDevOps
Copy link

Workspaces are an obvious footgun. Symlinks are a more robust solution. It would be appreciated if terraform-ls parsed them correctly to eliminate false-positive not defined errors.

@notorand-it
Copy link

Indeed. Also because terraform-ls should conform to terraform itself.
This deviating behavior by the language server is simply breaking the rules set by the same developer/company.

@theherk
Copy link

theherk commented May 5, 2024

I'm in a similar situation, where I use a directory per state, but this causes some issues with the LSP.

In env/dev-eu-north-1, for example:

_main.tf -> ../../../shared/bases/main.tf
_providers.tf -> ../../../shared/bases/providers.tf
_variables.tf -> ../../../shared/bases/variables.tf
_versions.tf -> ../../../shared/bases/versions.tf
main.tf -> ../main.tf
outputs.tf -> ../outputs.tf
terraform.tfvars
variables.tf -> ../variables.tf
versions.tf

We get several shared base configurations that are linked into many states. Then, for a given domain's environment, we link to the directory just above, so that all states have the base configurations and the domain configurations. In that way, the only actual non-link files in the environment directory are the backend configuration in versions.tf and the variables in terraform.tfvars.

This works extremely well, except it make the LSP complain a bit and jumping to definitions doesn't work.

I'm not sure what the actual solution could be though, because in the files actual location, the source paths are just not correct, and I don't see a way for the LSP to intuit where it should be looking. It doesn't know where the file could be potentially linked.

However, it would be done with a comment directive like:

module "core" {
  # terraform-ls: source = "./module/domain-core"
  source = "../modules/domain-core"

  # ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request performance Gotta go fast
Projects
None yet
Development

No branches or pull requests

5 participants