Skip to content

Commit

Permalink
WIP: ephemeral output rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Sep 29, 2024
1 parent cc88992 commit ccd2b2a
Show file tree
Hide file tree
Showing 37 changed files with 471 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
output "foo" {
value = "bar"
}

variable "foo" {
ephemeral = true
type = string
default = "eph-val"
}

output "existing_eph" {
ephemeral = true
value = var.foo
}

output "new_ephemeral" {
ephemeral = true
value = var.foo
}
49 changes: 49 additions & 0 deletions internal/command/jsonformat/computed/renderers/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package renderers

import (
"fmt"

"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/plans"
)

var _ computed.DiffRenderer = (*ephemeralRenderer)(nil)

func Ephemeral(action plans.Action, beforeEphemeral, afterEphemeral bool) computed.DiffRenderer {
return &ephemeralRenderer{
planAction: action,
beforeEphemeral: beforeEphemeral,
afterEphemeral: afterEphemeral,
}
}

type ephemeralRenderer struct {
planAction plans.Action
beforeEphemeral bool
afterEphemeral bool
}

func (renderer ephemeralRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string {
return fmt.Sprintf("(ephemeral value)%s%s", nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts))
}

func (renderer ephemeralRenderer) WarningsHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) []string {
if (renderer.beforeEphemeral == renderer.afterEphemeral) || renderer.planAction == plans.Create || renderer.planAction == plans.Delete {
// Only display warnings for ephemeral values if they are changing from
// being ephemeral or to being ephemeral and if they are not being
// destroyed or created.
return []string{}
}

var warning string
if renderer.beforeEphemeral {
warning = opts.Colorize.Color(fmt.Sprintf(" # [yellow]Warning[reset]: this attribute value will no longer be marked as ephemeral\n%s # after applying this change.", formatIndent(indent)))
} else {
warning = opts.Colorize.Color(fmt.Sprintf(" # [yellow]Warning[reset]: this attribute value will be marked as ephemeral and will not\n%s # display in UI output after applying this change.", formatIndent(indent)))
}

if renderer.planAction == plans.NoOp {
return []string{fmt.Sprintf("%s The value is unchanged.", warning)}
}
return []string{warning}
}
1 change: 1 addition & 0 deletions internal/command/jsonformat/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func precomputeDiffs(plan Plan, mode plans.Mode) diffs {
}

for key, output := range plan.OutputChanges {
// TODO: Change all this
change := structured.FromJsonChange(output, attribute_path.AlwaysMatcher())
diffs.outputs[key] = differ.ComputeDiffForOutput(change)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/command/jsonformat/differ/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func ComputeDiffForType(change structured.Change, ctype cty.Type) computed.Diff
}
}

// TODO: check for ephemerality

if sensitive, ok := checkForSensitiveType(change, ctype); ok {
return sensitive
}
Expand Down
17 changes: 17 additions & 0 deletions internal/command/jsonformat/differ/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package differ

import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed/renderers"
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
)

type CreateEphemeralRenderer func(computed.Diff, bool, bool) computed.DiffRenderer

func checkForEphemeralType(change structured.Change) (computed.Diff, bool) {
if !change.IsBeforeEphemeral() && !change.IsAfterEphemeral() {
return computed.Diff{}, false
}

return computed.NewDiff(renderers.Ephemeral(change.PlanAction, change.IsBeforeEphemeral(), change.IsAfterEphemeral()), change.CalculateAction(), change.ReplacePaths.Matches()), true
}
4 changes: 4 additions & 0 deletions internal/command/jsonformat/differ/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import (
)

func ComputeDiffForOutput(change structured.Change) computed.Diff {
if ephemeral, ok := checkForEphemeralType(change); ok {
return ephemeral
}

if sensitive, ok := checkForSensitiveType(change, cty.DynamicPseudoType); ok {
return sensitive
}
Expand Down
2 changes: 1 addition & 1 deletion internal/command/jsonformat/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (renderer Renderer) RenderHumanPlan(plan Plan, mode plans.Mode, opts ...pla
renderer.Colorize.Color("\n[bold][red]Warning:[reset][bold] This plan was generated using a different version of Terraform, the diff presented here may be missing representations of recent features."),
renderer.Streams.Stdout.Columns()))
}

// TODO: reflect ephemeral outputs
plan.renderHuman(renderer, mode, opts...)
}

Expand Down
3 changes: 3 additions & 0 deletions internal/command/jsonformat/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ func (state State) renderHumanStateOutputs(renderer Renderer, opts computed.Rend

for _, key := range keys {
output := state.RootModuleOutputs[key]

// TODO: There is no reason to be rendering state as diff?!

change := structured.FromJsonOutput(output)
ctype, err := ctyjson.UnmarshalType(output.Type)
if err != nil {
Expand Down
23 changes: 22 additions & 1 deletion internal/command/jsonformat/structured/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
// the type would need to change between the before and after value. It is in
// fact just easier to iterate through the values as generic JSON interfaces.
type Change struct {

// BeforeExplicit matches AfterExplicit except references the Before value.
BeforeExplicit bool

Expand Down Expand Up @@ -83,6 +82,15 @@ type Change struct {
// sensitive.
AfterSensitive interface{}

// BeforeEphemeral TODO
BeforeEphemeral interface{}

// AfterEphemeral TODO
AfterEphemeral interface{}

// PlanAction TODO
PlanAction plans.Action

// ReplacePaths contains a set of paths that point to attributes/elements
// that are causing the overall resource to be replaced rather than simply
// updated.
Expand Down Expand Up @@ -111,6 +119,9 @@ func FromJsonChange(change jsonplan.Change, relevantAttributes attribute_path.Ma
Unknown: unmarshalGeneric(change.AfterUnknown),
BeforeSensitive: unmarshalGeneric(change.BeforeSensitive),
AfterSensitive: unmarshalGeneric(change.AfterSensitive),
BeforeEphemeral: unmarshalGeneric(change.BeforeEphemeral),
AfterEphemeral: unmarshalGeneric(change.AfterEphemeral),
PlanAction: jsonplan.UnmarshalActions(change.Actions),
ReplacePaths: attribute_path.Parse(change.ReplacePaths, false),
RelevantAttributes: relevantAttributes,
}
Expand Down Expand Up @@ -158,6 +169,9 @@ func FromJsonOutput(output jsonstate.Output) Change {
BeforeSensitive: output.Sensitive,
AfterSensitive: output.Sensitive,

BeforeEphemeral: output.Ephemeral,
AfterEphemeral: output.Ephemeral,

// We don't display replacement data for resources, and all attributes
// are relevant.
ReplacePaths: attribute_path.Empty(false),
Expand All @@ -178,6 +192,9 @@ func FromJsonViewsOutput(output viewsjson.Output) Change {
BeforeSensitive: output.Sensitive,
AfterSensitive: output.Sensitive,

// TODO: BeforeEphemeral:
// TODO: AfterEphemeral:

// We don't display replacement data for resources, and all attributes
// are relevant.
ReplacePaths: attribute_path.Empty(false),
Expand All @@ -190,6 +207,10 @@ func FromJsonViewsOutput(output viewsjson.Output) Change {
// or sets it is likely more efficient to work out the action directly instead
// of relying on this function.
func (change Change) CalculateAction() plans.Action {
if change.IsBeforeEphemeral() || change.IsAfterEphemeral() {
return change.PlanAction
}

if (change.Before == nil && !change.BeforeExplicit) && (change.After != nil || change.AfterExplicit) {
return plans.Create
}
Expand Down
34 changes: 34 additions & 0 deletions internal/command/jsonformat/structured/ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package structured

import (
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/plans"
)

type ProcessEphemeralInner func(change Change) computed.Diff
type CreateEphemeralDiff func(inner computed.Diff, beforeEphemeral, afterEphemeral bool, action plans.Action) computed.Diff

func (change Change) CheckForEphemeral(processInner ProcessEphemeralInner, createDiff CreateEphemeralDiff) (computed.Diff, bool) {
if !change.IsBeforeEphemeral() && !change.IsAfterEphemeral() {
return computed.Diff{}, false
}

value := Change{
BeforeExplicit: change.BeforeExplicit,
AfterExplicit: change.AfterExplicit,
Before: change.Before,
After: change.After,
Unknown: change.Unknown,
BeforeSensitive: change.BeforeSensitive,
AfterSensitive: change.AfterSensitive,
BeforeEphemeral: change.BeforeEphemeral,
AfterEphemeral: change.AfterEphemeral,
ReplacePaths: change.ReplacePaths,
RelevantAttributes: change.RelevantAttributes,
}

// TODO: this turns create into update
inner := processInner(value)

return createDiff(inner, change.IsBeforeEphemeral(), change.IsAfterEphemeral(), inner.Action), true
}
14 changes: 14 additions & 0 deletions internal/command/jsonformat/structured/sensitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ func (change Change) IsAfterSensitive() bool {
return false
}

func (change Change) IsBeforeEphemeral() bool {
if ephemeral, ok := change.BeforeEphemeral.(bool); ok {
return ephemeral
}
return false
}

func (change Change) IsAfterEphemeral() bool {
if ephemeral, ok := change.AfterEphemeral.(bool); ok {
return ephemeral
}
return false
}

// CheckForSensitive is a helper function that handles all common functionality
// for processing a sensitive value.
//
Expand Down
17 changes: 17 additions & 0 deletions internal/command/jsonplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ type Change struct {
BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"`
AfterSensitive json.RawMessage `json:"after_sensitive,omitempty"`

// BeforeEphemeral TODO
BeforeEphemeral json.RawMessage `json:"before_ephemeral,omitempty"`
// AfterEphemeral TODO
AfterEphemeral json.RawMessage `json:"after_ephemeral,omitempty"`

// ReplacePaths is an array of arrays representing a set of paths into the
// object value which resulted in the action being "replace". This will be
// omitted if the action is not replace, or if no paths caused the
Expand Down Expand Up @@ -172,6 +177,7 @@ type Importing struct {

type output struct {
Sensitive bool `json:"sensitive"`
Ephemeral bool `json:"ephemeral"`
Type json.RawMessage `json:"type,omitempty"`
Value json.RawMessage `json:"value,omitempty"`
}
Expand Down Expand Up @@ -709,6 +715,15 @@ func MarshalOutputChanges(changes *plans.ChangesSrc) (map[string]Change, error)
return nil, err
}

outputEphemeral := cty.False
if oc.Ephemeral {
outputEphemeral = cty.True
}
ephemeral, err := ctyjson.Marshal(outputEphemeral, outputEphemeral.Type())
if err != nil {
return nil, err
}

a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())

c := Change{
Expand All @@ -718,6 +733,8 @@ func MarshalOutputChanges(changes *plans.ChangesSrc) (map[string]Change, error)
AfterUnknown: a,
BeforeSensitive: json.RawMessage(sensitive),
AfterSensitive: json.RawMessage(sensitive),
BeforeEphemeral: json.RawMessage(ephemeral),
AfterEphemeral: json.RawMessage(ephemeral),

// Just to be explicit, outputs cannot be imported so this is always
// nil.
Expand Down
6 changes: 6 additions & 0 deletions internal/command/jsonplan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ func TestOutputs(t *testing.T) {
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
BeforeEphemeral: json.RawMessage("false"),
AfterEphemeral: json.RawMessage("false"),
},
"second": {
Actions: []string{"create"},
Expand All @@ -369,6 +371,8 @@ func TestOutputs(t *testing.T) {
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
BeforeEphemeral: json.RawMessage("false"),
AfterEphemeral: json.RawMessage("false"),
},
},
},
Expand Down Expand Up @@ -397,6 +401,8 @@ func TestOutputs(t *testing.T) {
AfterUnknown: json.RawMessage("false"),
BeforeSensitive: json.RawMessage("false"),
AfterSensitive: json.RawMessage("false"),
BeforeEphemeral: json.RawMessage("false"),
AfterEphemeral: json.RawMessage("false"),
},
},
},
Expand Down
1 change: 1 addition & 0 deletions internal/command/jsonplan/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func marshalPlannedOutputs(changes *plans.ChangesSrc) (map[string]output, error)
Value: json.RawMessage(after),
Type: json.RawMessage(afterType),
Sensitive: oc.Sensitive,
Ephemeral: oc.Ephemeral,
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/command/jsonstate/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type stateValues struct {

type Output struct {
Sensitive bool `json:"sensitive"`
Ephemeral bool `json:"ephemeral"`
Value json.RawMessage `json:"value,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
}
Expand Down Expand Up @@ -158,6 +159,8 @@ func MarshalForRenderer(sf *statefile.File, schemas *terraform.Schemas) (Module,
return Module{}, nil, err
}

// TODO: MarshallEphemeralOutputs

root, err := marshalRootModule(sf.State, schemas)
if err != nil {
return Module{}, nil, err
Expand Down
16 changes: 8 additions & 8 deletions internal/command/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,8 @@ func TestShow_json_output(t *testing.T) {
// Disregard format version to reduce needless test fixture churn
want.FormatVersion = got.FormatVersion

if !cmp.Equal(got, want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
if !cmp.Equal(want, got) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(want, got))
}
})
}
Expand Down Expand Up @@ -743,8 +743,8 @@ func TestShow_json_output_sensitive(t *testing.T) {
// Disregard format version to reduce needless test fixture churn
want.FormatVersion = got.FormatVersion

if !cmp.Equal(got, want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
if !cmp.Equal(want, got) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(want, got))
}
}

Expand Down Expand Up @@ -841,8 +841,8 @@ func TestShow_json_output_conditions_refresh_only(t *testing.T) {
// Disregard format version to reduce needless test fixture churn
want.FormatVersion = got.FormatVersion

if !cmp.Equal(got, want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
if !cmp.Equal(want, got) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(want, got))
}
}

Expand Down Expand Up @@ -927,8 +927,8 @@ func TestShow_json_output_state(t *testing.T) {
}
json.Unmarshal([]byte(byteValue), &want)

if !cmp.Equal(got, want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
if !cmp.Equal(want, got) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(want, got))
}
})
}
Expand Down
Loading

0 comments on commit ccd2b2a

Please sign in to comment.