Skip to content

JIT vendoring (source pull) writes to a different workdir than terraform plan/init when metadata.component differs from the component instance name #2134

@zack-is-cool

Description

@zack-is-cool

Describe the Bug

When a component's instance name differs from its metadata.component value, Atmos JIT vendoring (atmos terraform source pull) and atmos terraform $command resolve to two different .workdir paths for the same component. This means:

  • source pull --force hydrates one directory.
  • plan/init looks in a different directory and re-downloads (or fails to find) the source.

This was discussed in the Atmos Slack and appeared related to PR #2093, but the issue persists.

Expected Behavior

Both atmos terraform source pull and atmos terraform init/plan should resolve to the same workdir path for a given component instance.

When metadata.component is set, it represents the upstream module identity — the JIT workdir path should be derived consistently from that same identity regardless of which subcommand triggers the resolution.

Expected .workdir layout (one directory):

.workdir/
└── terraform/
    └── demo-dev-my-module/          # keyed by metadata.component
        └── <module files>

Actual behavior

Two separate directories are created:

.workdir/
└── terraform/
    ├── demo-dev-my-module-iac/      # created by `source pull` (uses instance name)
    └── demo-dev-my-module/          # created by `init`/`plan` (uses metadata.component)

The practical consequence is that source pull --force pre-seeds the wrong directory, and every subsequent plan/init re-downloads the source from scratch (or vice versa, depending on which path the subcommand resolves first).

Steps to Reproduce

#!/usr/bin/env bash
# ============================================================
# ATMOS REPRO: JIT workdir mismatch when metadata.component
#              differs from the component instance name
# ============================================================
set -euo pipefail

WORKDIR="$(mktemp -d -t atmos-repro-XXXXXX)"
echo "Working in: ${WORKDIR}"
cd "${WORKDIR}"

# --- 1) atmos.yaml ---
cat <<'EOF' > atmos.yaml
base_path: "."

components:
  terraform:
    base_path: "components/terraform"
    command: "tofu"
    apply_auto_approve: false
    deploy_run_init: true
    init_run_reconfigure: true
    auto_generate_backend_file: true

stacks:
  name_template: "{{ .vars.tenant }}-{{ .vars.environment }}"
  base_path: "stacks"
  included_paths:
    - "**/*"
EOF

# --- 2) Stack YAML ---
# Key detail: the component *instance* name is "my-module-iac"
#             but metadata.component is "my-module"
#             (mirrors a real pattern where the same upstream module
#              is instantiated multiple times under different names)
mkdir -p stacks
cat <<'EOF' > stacks/demo.yaml
vars:
  tenant: "demo"
  environment: "dev"

terraform:
  backend_type: local

components:
  terraform:
    my-module-iac:                          # <-- instance name
      source:
        uri: "git::https://github.com/cloudposse/terraform-null-label.git"
        version: "0.25.0"
      provision:
        workdir:
          enabled: true
      metadata:
        component: "my-module"             # <-- DIFFERENT from instance name
EOF

# --- 3) Show structure ---
echo
echo "=== workspace layout ==="
find . -maxdepth 3 -type f | sed 's|^\./||'

echo
echo "=== atmos describe component (pre-pull) ==="
atmos describe component my-module-iac -s demo-dev

# --- 4) Explicit source pull (simulates pre-seeding the cache) ---
echo
echo "=== atmos terraform source pull ==="
atmos terraform source pull --force my-module-iac -s demo-dev

echo
echo "=== .workdir contents AFTER source pull ==="
find .workdir -maxdepth 4 -type d 2>/dev/null || echo "(no .workdir directory found)"

# --- 5) Init / plan (these use metadata.component for the workdir path) ---
echo
echo "=== atmos terraform init ==="
atmos terraform init my-module-iac -s demo-dev || true

echo
echo "=== .workdir contents AFTER init ==="
find .workdir -maxdepth 4 -type d 2>/dev/null || echo "(no .workdir directory found)"

echo
echo "=== RESULT ==="
echo "Expected: exactly ONE directory under .workdir/terraform/"
echo "Actual:   two directories — one keyed by instance name, one by metadata.component"
echo
echo "Done. Workspace preserved at: ${WORKDIR}"

Results - you can see two different .workdirs

✓ Vendored my-module-iac@0.25.0 to .workdir/terraform/demo-dev-my-module-iac

=== .workdir contents AFTER source pull ===
.workdir
.workdir/terraform
.workdir/terraform/demo-dev-my-module-iac
.workdir/terraform/demo-dev-my-module-iac/test
.workdir/terraform/demo-dev-my-module-iac/test/src
.workdir/terraform/demo-dev-my-module-iac/docs
.workdir/terraform/demo-dev-my-module-iac/exports
.workdir/terraform/demo-dev-my-module-iac/examples
.workdir/terraform/demo-dev-my-module-iac/examples/complete
.workdir/terraform/demo-dev-my-module-iac/examples/autoscalinggroup
.workdir/terraform/demo-dev-my-module-iac/.github
.workdir/terraform/demo-dev-my-module-iac/.github/workflows
.workdir/terraform/demo-dev-my-module-iac/.github/ISSUE_TEMPLATE

=== atmos terraform init ===
✓ Auto-provisioned source to .workdir/terraform/demo-dev-my-module

Initializing the backend...

Successfully configured the backend "local"! OpenTofu will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

OpenTofu has been successfully initialized!

=== .workdir contents AFTER init ===
.workdir
.workdir/terraform
.workdir/terraform/demo-dev-my-module-iac
.workdir/terraform/demo-dev-my-module-iac/test
.workdir/terraform/demo-dev-my-module-iac/test/src
.workdir/terraform/demo-dev-my-module-iac/docs
.workdir/terraform/demo-dev-my-module-iac/exports
.workdir/terraform/demo-dev-my-module-iac/examples
.workdir/terraform/demo-dev-my-module-iac/examples/complete
.workdir/terraform/demo-dev-my-module-iac/examples/autoscalinggroup
.workdir/terraform/demo-dev-my-module-iac/.github
.workdir/terraform/demo-dev-my-module-iac/.github/workflows
.workdir/terraform/demo-dev-my-module-iac/.github/ISSUE_TEMPLATE
.workdir/terraform/demo-dev-my-module
.workdir/terraform/demo-dev-my-module/test
.workdir/terraform/demo-dev-my-module/test/src
.workdir/terraform/demo-dev-my-module/.terraform
.workdir/terraform/demo-dev-my-module/docs
.workdir/terraform/demo-dev-my-module/exports
.workdir/terraform/demo-dev-my-module/examples
.workdir/terraform/demo-dev-my-module/examples/complete
.workdir/terraform/demo-dev-my-module/examples/autoscalinggroup
.workdir/terraform/demo-dev-my-module/.github
.workdir/terraform/demo-dev-my-module/.github/workflows
.workdir/terraform/demo-dev-my-module/.github/ISSUE_TEMPLATE
.workdir/terraform/demo-dev-my-module/.atmos

=== RESULT ===
Expected: exactly ONE directory under .workdir/terraform/
Actual:   two directories — one keyed by instance name, one by metadata.component

Done. Workspace preserved at: /var/folders/rs/nw_8jpb53l30mnds_y98zq6m0000gp/T/atmos-repro-XXXXXX.39t8f1SFIi

Screenshots

No response

Environment

Atmos version 1.207.0, also tested on #2093

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug🐛 An issue with the system

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions