Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
100dc06
feat: Add Terraform streaming UI with real-time plan/apply visualization
osterman Dec 22, 2025
72a2d01
feat: Add minimum 5s display duration for streaming UI progress
osterman Dec 22, 2025
eef9aa1
refactor: move progress bar inline with activity on same line
osterman Dec 22, 2025
850f8c1
refactor: remove artificial minimum display duration
osterman Dec 22, 2025
3cc4cc3
chore: add .atmos-*.tfplan to .gitignore
osterman Dec 22, 2025
23efada
docs: update PRD and blog post with current UI design
osterman Dec 22, 2025
77e6faa
fix: address PR review feedback for streaming UI
osterman Dec 22, 2025
4b844d4
fix: remove explicit capacity allocation to prevent overflow
osterman Dec 22, 2025
b7d8b06
chore: regenerate golden snapshots for streaming UI feature
osterman Dec 22, 2025
c1a4a6d
fix: use orange refresh icon for replace actions in streaming UI
osterman Dec 22, 2025
0d8768b
fix: show computed attributes as yellow (update) not red (delete)
osterman Dec 22, 2025
e398cd5
feat: implement line-by-line diff for multiline attribute changes
osterman Dec 22, 2025
d5e3b6e
fix: group consecutive changed lines in diff output
osterman Dec 22, 2025
f531b55
test: add comprehensive tests for diff behavior and color coding
osterman Dec 22, 2025
1510d7c
feat: show '# forces replacement' annotation for replace-triggering a…
osterman Dec 22, 2025
b82dbd0
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 22, 2025
f116313
refactor: split tree.go into smaller focused modules
osterman Dec 22, 2025
5a7354d
test: regenerate golden snapshots for streaming UI feature
osterman Dec 22, 2025
b6270d6
test: remove tautological and stub tests from streaming UI
osterman Dec 23, 2025
e05f606
test: improve streaming UI test coverage to ~70%
osterman Dec 23, 2025
39be2ce
chore: remove accidentally committed tfplan file
osterman Dec 23, 2025
0e9f9ec
docs: mark streaming Terraform UI as shipped in roadmap
osterman Dec 27, 2025
169601a
fix: address CodeRabbit review feedback for streaming UI
osterman Jan 2, 2026
06df840
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 4, 2026
8ab9852
fix: wrap parser errors with static sentinel per CodeRabbit feedback
osterman Jan 4, 2026
1458235
fix: address CodeRabbit review feedback (round 2)
osterman Jan 4, 2026
f7c071b
feat: route terraform diagnostics through structured logger
osterman Jan 9, 2026
e895294
fix: improve streaming UI diagnostic display and badge summary
osterman Jan 9, 2026
272503b
fix: remove duplicate error display in streaming UI
osterman Jan 9, 2026
831c088
fix: restore failed resources display in streaming UI
osterman Jan 9, 2026
8272096
fix: address CodeRabbit review feedback (round 3)
osterman Jan 9, 2026
73c1275
test: regenerate golden snapshots
osterman Jan 10, 2026
3097a3d
fix: address CodeRabbit review feedback (round 4)
osterman Jan 10, 2026
236c09f
test: regenerate golden snapshots and fix TTY exit code handling
osterman Jan 10, 2026
2dcd7d0
refactor: use theme constants and add stdin TTY validation
osterman Jan 10, 2026
2a1bdd7
test: remove non-TTY diagnostic tests
osterman Jan 11, 2026
a2f91e1
fix: add perf.Track to ConfirmApply and ConfirmDestroy
osterman Jan 11, 2026
446fd92
fix: align tree continuation with tree connectors
osterman Jan 12, 2026
e596ec8
feat: add robust multi-line tree rendering with JSON support
osterman Jan 13, 2026
4ea5446
fix: guard truncation against small terminal widths
osterman Jan 13, 2026
de8df55
Merge main into osterman/terraform-streaming-ui
osterman Jan 16, 2026
4536d2e
test: regenerate golden snapshots after merge from main
osterman Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**/*.tf.json
**/planfile
**/*.planfile
**/.atmos-*.tfplan
**/*.kubeconfig
**/terraform.tfvars.json
**/helmfile.vars.yaml
Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ For complete Terraform/OpenTofu documentation, see:
return err
}

// Parse base terraform options.
opts := ParseTerraformRunOptions(v)
// Parse base terraform options with command context for UI flag detection.
opts := ParseTerraformRunOptions(v, cmd)

// Apply-specific flags (from-plan, planfile) flow through the
// legacy ProcessCommandLineArgs which sets info.UseTerraformPlan, info.PlanFile.
Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ This ensures that the changes defined in your Terraform configuration are applie
return err
}

// Parse base terraform options.
opts := ParseTerraformRunOptions(v)
// Parse base terraform options with command context for UI flag detection.
opts := ParseTerraformRunOptions(v, cmd)

// Deploy-specific flags (deploy-run-init, from-plan, planfile) flow through
// the legacy ProcessCommandLineArgs which sets info.DeployRunInit, etc.
Expand Down
7 changes: 7 additions & 0 deletions cmd/terraform/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ func registerExecutionFlags(registry *flags.FlagRegistry) {
Description: "Customize User-Agent string in Terraform provider requests (sets TF_APPEND_USER_AGENT)",
EnvVars: []string{"ATMOS_APPEND_USER_AGENT"},
})
registry.Register(&flags.BoolFlag{
Name: "ui",
Shorthand: "",
Default: false,
Description: "Enable streaming UI mode for real-time resource status display",
EnvVars: []string{"ATMOS_TERRAFORM_UI"},
})
}

// BackendExecutionFlags returns flags for commands that generate backend files or run init.
Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ For complete Terraform/OpenTofu documentation, see:
return err
}

// Parse base terraform options.
opts := ParseTerraformRunOptions(v)
// Parse base terraform options with command context for UI flag detection.
opts := ParseTerraformRunOptions(v, cmd)

return terraformRunWithOptions(terraformCmd, cmd, args, opts)
},
Expand Down
19 changes: 17 additions & 2 deletions cmd/terraform/options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package terraform

import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cloudposse/atmos/pkg/perf"
Expand Down Expand Up @@ -33,13 +34,19 @@ type TerraformRunOptions struct {
Components []string
All bool
Affected bool

// UI flags.
UI bool // Enable streaming UI mode.
UIFlagSet bool // Whether --ui flag was explicitly set.
}

// ParseTerraformRunOptions parses shared terraform flags from Viper.
func ParseTerraformRunOptions(v *viper.Viper) *TerraformRunOptions {
// Pass cmd to detect if --ui flag was explicitly set (for tri-state logic).
// If cmd is nil, UIFlagSet will be false.
func ParseTerraformRunOptions(v *viper.Viper, cmd *cobra.Command) *TerraformRunOptions {
defer perf.Track(nil, "terraform.ParseTerraformRunOptions")()

return &TerraformRunOptions{
opts := &TerraformRunOptions{
ProcessTemplates: v.GetBool("process-templates"),
ProcessFunctions: v.GetBool("process-functions"),
Skip: v.GetStringSlice("skip"),
Expand All @@ -55,5 +62,13 @@ func ParseTerraformRunOptions(v *viper.Viper) *TerraformRunOptions {
Components: v.GetStringSlice("components"),
All: v.GetBool("all"),
Affected: v.GetBool("affected"),
UI: v.GetBool("ui"),
}

// Check if --ui flag was explicitly set (for tri-state: unset vs true vs false).
if cmd != nil && cmd.Flags().Changed("ui") {
opts.UIFlagSet = true
}

return opts
}
2 changes: 1 addition & 1 deletion cmd/terraform/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func TestParseTerraformRunOptions(t *testing.T) {
v := viper.New()
tt.setup(v)

result := ParseTerraformRunOptions(v)
result := ParseTerraformRunOptions(v, nil)

assert.Equal(t, tt.expected.ProcessTemplates, result.ProcessTemplates, "ProcessTemplates should match")
assert.Equal(t, tt.expected.ProcessFunctions, result.ProcessFunctions, "ProcessFunctions should match")
Expand Down
4 changes: 2 additions & 2 deletions cmd/terraform/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ For complete Terraform/OpenTofu documentation, see:
return err
}

// Parse base terraform options.
opts := ParseTerraformRunOptions(v)
// Parse base terraform options with command context for UI flag detection.
opts := ParseTerraformRunOptions(v, cmd)

// Plan-specific flags (upload-status, skip-planfile) flow through the
// legacy ProcessCommandLineArgs which sets info.PlanSkipPlanfile.
Expand Down
33 changes: 32 additions & 1 deletion cmd/terraform/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package terraform

import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cloudposse/atmos/cmd/internal"
"github.com/cloudposse/atmos/pkg/flags"
)

// refreshParser handles flag parsing for refresh command.
var refreshParser *flags.StandardParser

// refreshCmd represents the terraform refresh command.
var refreshCmd = &cobra.Command{
Use: "refresh",
Expand All @@ -16,11 +21,37 @@ For complete Terraform/OpenTofu documentation, see:
https://developer.hashicorp.com/terraform/cli/commands/refresh
https://opentofu.org/docs/cli/commands/refresh`,
RunE: func(cmd *cobra.Command, args []string) error {
return terraformRun(terraformCmd, cmd, args)
v := viper.GetViper()

// Bind both parent and subcommand parsers.
if err := terraformParser.BindFlagsToViper(cmd, v); err != nil {
return err
}
if err := refreshParser.BindFlagsToViper(cmd, v); err != nil {
return err
}

// Parse base terraform options with command context for UI flag detection.
opts := ParseTerraformRunOptions(v, cmd)

return terraformRunWithOptions(terraformCmd, cmd, args, opts)
},
}

func init() {
// Create parser with refresh-specific flags (backend execution for init).
refreshParser = flags.NewStandardParser(
WithBackendExecutionFlags(),
)

// Register refresh-specific flags with Cobra.
refreshParser.RegisterFlags(refreshCmd)

// Bind flags to Viper for environment variable support.
if err := refreshParser.BindToViper(viper.GetViper()); err != nil {
panic(err)
}

// Register completions for refreshCmd.
RegisterTerraformCompletions(refreshCmd)

Expand Down
6 changes: 3 additions & 3 deletions cmd/terraform/subcommands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestSubcommandParseOptions(t *testing.T) {
v.Set("skip-init", tc.skipInitVal)
v.Set("dry-run", true)

opts := ParseTerraformRunOptions(v)
opts := ParseTerraformRunOptions(v, nil)

assert.Equal(t, tc.backendFileVal, opts.AutoGenerateBackendFile)
assert.Equal(t, tc.reconfigureVal, opts.InitRunReconfigure)
Expand Down Expand Up @@ -223,7 +223,7 @@ func TestSubcommandRunEFlagBinding(t *testing.T) {
require.NoError(t, err, "%sParser.BindFlagsToViper should succeed", tc.name)

// Parse options (simulating what RunE does).
opts := ParseTerraformRunOptions(v)
opts := ParseTerraformRunOptions(v, testCmd)

// Verify the options were parsed correctly.
assert.Equal(t, tc.backendFileVal, opts.AutoGenerateBackendFile)
Expand Down Expand Up @@ -255,7 +255,7 @@ func TestSubcommandRunEWithDryRun(t *testing.T) {
require.NoError(t, err)

// Parse options.
opts := ParseTerraformRunOptions(v)
opts := ParseTerraformRunOptions(v, testCmd)

// Verify the flag binding and parsing worked correctly.
assert.True(t, opts.DryRun)
Expand Down
6 changes: 5 additions & 1 deletion cmd/terraform/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func terraformRun(parentCmd *cobra.Command, actualCmd *cobra.Command, args []str
return err
}

opts := ParseTerraformRunOptions(v)
opts := ParseTerraformRunOptions(v, actualCmd)
return terraformRunWithOptions(parentCmd, actualCmd, args, opts)
}

Expand Down Expand Up @@ -203,6 +203,10 @@ func applyOptionsToInfo(info *schema.ConfigAndStacksInfo, opts *TerraformRunOpti
if opts.DeployRunInit {
info.DeployRunInit = "true"
}

// UI flags.
info.UIEnabled = opts.UI
info.UIFlagExplicitlySet = opts.UIFlagSet
}

// terraformRunWithOptions is the shared execution logic for terraform subcommands.
Expand Down
2 changes: 1 addition & 1 deletion cmd/terraform/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ For complete Terraform/OpenTofu documentation, see:
}

// Parse base terraform options.
opts := ParseTerraformRunOptions(v)
opts := ParseTerraformRunOptions(v, cmd)

return terraformRunWithOptions(terraformCmd, cmd, args, opts)
},
Expand Down
Loading