cloudposse / atmos

👽 Terraform Orchestration Tool for DevOps. Keep environment configuration DRY with hierarchical imports of configurations, inheritance, and WAY more. Native support for Terraform and Helmfile.
https://atmos.tools
Apache License 2.0
773 stars 95 forks source link

Remote sources for components #598

Open Gowiem opened 6 months ago

Gowiem commented 6 months ago

Describe the Feature

This is a similar idea to what Terragrunt does with their "Remote Terraform Configurations" feature: https://terragrunt.gruntwork.io/docs/features/keep-your-terraform-code-dry/#remote-terraform-configurations

The idea would be that you could provide a URL to a given root module and use that to create a component instance instead of having that component available locally in the atmos project repo.

The benefit here is that you don't need to vendor in the code for that root module. Vendoring is great when you're going to make changes to a configuration, BUT if you're not making any changes then it just creates large PRs that are hard to review and doesn't provide much value.

Another benefit: I have team members that strongly dislike creating root modules that are simply slim wrappers of a single child module because then we're in the game of maintaining a very slim wrapper. @kevcube can speak to that if there is interest to understand more there.

Expected Behavior

Today all non-custom root module usage is done through vendoring in Atmos, so no similar expected behavior AFAIK.

Use Case

Help avoid vendoring in code that you're not changing and therefore not polluting the atmos project with additional code that is unchanged.

Describe Ideal Solution

I'm envisioning this would work like the following with the $COMPONENT_NAME.metadata.url being the only change to the schema. Maybe we also need a version attribute as well, but TBD.

components:
  terraform:
    s3-bucket:
      metadata:
        url: https://github.com/cloudposse/terraform-aws-components/tree/1.431.0/modules/s3-bucket
      vars:
        ...

Running atmos against this configuration would result in atmos cloning that root module down to the local in a temporary cache and then using that cloned root module as the source to run terraform or tofu against.

Alternatives Considered

None.

Additional Context

None.

Gowiem commented 6 months ago

@osterman @aknysh @nitrocode -- Would be interested in your folks thoughts on this one!

osterman commented 6 months ago

This has come up from a handful of people lately in the community. Clearly it's a popular feature for users migrating from a Terragrunt ecosystem.

This is supported today by combining 2 concepts.

How it's different from Terragrunt

  1. You have to run atmos vendor pull before running commands. In other words, it's not Just in Time (JIT). We like not relying on temporary cache folders like Terragrunt.
  2. You'll need to ensure you vendor to unique folders, if you want to support concurrent versions of remote components.
  3. We don't support HCL-style "glue" code to dynamically construct a component from multiple remote "root" modules, but you can use terraform overrides to monkey patch behavior or add functionality. https://developer.hashicorp.com/terraform/language/files/override

How we could improve it?

Why Not Define Vendoring in Stack Configurations?

We deliberately did not add the vendor.yaml configurations to the Stack configurations for a few reasons

osterman commented 6 months ago

Another benefit: I have team members that strongly dislike creating root modules that are simply slim wrappers of a single child module because then we're in the game of maintaining a very slim wrapper. @kevcube can speak to that if there is interest to understand more there.

See: https://atmos.tools/core-concepts/components/vendoring#vendoring-modules-as-components

kevcube commented 6 months ago

See: https://atmos.tools/core-concepts/components/vendoring#vendoring-modules-as-components

@osterman My problem with this is adding potentially hundreds of lines of code to our repo that we have no immediate need to modify. Makes for a large, difficult to actually verify PR.

osterman commented 6 months ago

@kevcube committing the files is not required if using component.yaml, and mitigated from a code review perspective using .gitattributes.

See https://sweetops.slack.com/archives/C031919U8A0/p1714589350835659

Option 1: Vendor and Commit

By default, Cloud Posse (in our engagements and refarch), vendor the everything in to the repositories.

Pros

Cons

Option 2: Vendoring Just In Time

Vendoring components (or anything for that fact, which is supported by atmos) can be done "Just in time", more or less like terraform init for provider and modules.

Pros:

Cons

Option 3: Hybrid

osterman commented 5 months ago

Should components be able to have their own vendor.yaml that can be imported?

apiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
  name: example-vendor-config
  description: Atmos vendoring manifest
spec:
  # `imports` or `sources` (or both) must be defined in a vendoring manifest
  imports:
    - "vendor/vpc"
    - "components/terraform/**/vendor.yaml"
osterman commented 5 months ago

Imagine a vendor.yaml with the following.

apiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
  name: example-vendor-config
  description: Atmos vendoring manifest
spec:
  # `imports` or `sources` (or both) must be defined in a vendoring manifest
  imports:
    - "vendor/vendor2"
    - "vendor/vendor3.yaml"

  sources:
    - component: "vpc"
      source: "oci://public.ecr.aws/cloudposse/components/terraform/stable/aws/vpc:{{.Version}}"
      targets:
        - "components/terraform/vpc/{{ .Version }}"
      included_paths:
        - "**/*.tf"
        - "**/*.tfvars"
        - "**/*.md"

    - component: "vpc-flow-logs-bucket"
      source: "github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}}"
      targets:
        - "components/terraform/infra/vpc-flow-logs-bucket/{{.Version}}"
      excluded_paths:
        - "**/*.yaml"
        - "**/*.yml"

Then in a stack configuration for dev

components:
  terraform:
    vpc:
      settings:
        vendor: 
           version: 1.2.3

    vpc-flow-logs-bucket:
      settings:
        vendor:
          version: 1.2.4
          auto: true

Then in a stack configuration for prod

components:
  terraform:
    vpc:
      settings:
        vendor: 
           version: 1.2.1

    vpc-flow-logs-bucket:
      settings:
        vendor:
          version: 1.2.1
          auto: true

And then running atmos terraform plan vpc --stack use1-prod-vpc --vendor

Or using release channels,

components:
  terraform:
    vpc:
      settings:
        vendor: 
           version: alpha

    vpc-flow-logs-bucket:
      settings:
        vendor:
          version: beta
          auto: true
osterman commented 5 months ago

How would overrides pattern work, if you want to use vendoring as proposed?

[!NOTE] Monkey patching is an anti-pattern, but is supported

Option 1

Without any signficant changes to atmos, in your vendoring config, ensure you have

    - component: "vpc-flow-logs-bucket"
      # ...
      excluded_paths:
        - "**/*_override.tf"

Place your {xxx}_override.tf in the vendored folder and commit only the the _override.tf files.

Option 2

Without any signficant changes to atmos, in your vendoring config, ensure you have:

    - component: "vpc-flow-logs-bucket"
      source: "github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}}"
      targets:
        - "components/terraform/infra/vpc-flow-logs-bucket/{{.Version}}"
      mixins:
        - "components/teraform/overrides/vpc-flow-logs-bucket/*_override.tf"
        - "github.com:cloudposse/my-private-repo/providers.tf?ref=v1.2.3"

In this example, local files stored in components/teraform/overrides/vpc-flow-logs-bucket are copied into the target. As well as a remote providers file is copied in at version 1.2.3.

You don't have to use *_override.tf, it could just be *, but it's "safer" to focus on overrides.