Skip to content

Commit 102a561

Browse files
add actions to the planfile config
1 parent 02fd462 commit 102a561

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed

internal/command/jsonconfig/config.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type module struct {
4545
Resources []resource `json:"resources,omitempty"`
4646
ModuleCalls map[string]moduleCall `json:"module_calls,omitempty"`
4747
Variables variables `json:"variables,omitempty"`
48+
Actions []action `json:"actions,omitempty"`
4849
}
4950

5051
type moduleCall struct {
@@ -107,6 +108,29 @@ type resource struct {
107108
DependsOn []string `json:"depends_on,omitempty"`
108109
}
109110

111+
// Action is the representation of an action in the config
112+
type action struct {
113+
// Address is the absolute resource address
114+
Address string `json:"address,omitempty"`
115+
116+
Type string `json:"type,omitempty"`
117+
Name string `json:"name,omitempty"`
118+
119+
// ProviderConfigKey is the key into "provider_configs" (shown above) for
120+
// the provider configuration that this resource is associated with.
121+
//
122+
// NOTE: If a given resource is in a ModuleCall, and the provider was
123+
// configured outside of the module (in a higher level configuration file),
124+
// the ProviderConfigKey will not match a key in the ProviderConfigs map.
125+
ProviderConfigKey string `json:"provider_config_key,omitempty"`
126+
127+
// CountExpression and ForEachExpression describe the expressions given for
128+
// the corresponding meta-arguments in the resource configuration block.
129+
// These are omitted if the corresponding argument isn't set.
130+
CountExpression *expression `json:"count_expression,omitempty"`
131+
ForEachExpression *expression `json:"for_each_expression,omitempty"`
132+
}
133+
110134
type output struct {
111135
Sensitive bool `json:"sensitive,omitempty"`
112136
Expression expression `json:"expression,omitempty"`
@@ -369,6 +393,7 @@ func marshalModule(c *configs.Config, schemas *terraform.Schemas, addr string) (
369393
}
370394
module.Variables = vars
371395
}
396+
module.Actions = marshalActions(c.Module.Actions, addr)
372397

373398
return module, nil
374399
}
@@ -520,6 +545,37 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform
520545
return rs, nil
521546
}
522547

548+
func marshalActions(actions map[string]*configs.Action, moduleAddr string) []action {
549+
var as []action
550+
551+
for _, v := range actions {
552+
providerConfigKey := opaqueProviderKey(v.ProviderConfigAddr().StringCompact(), moduleAddr)
553+
a := action{
554+
Address: v.Addr().String(),
555+
Type: v.Type,
556+
Name: v.Name,
557+
ProviderConfigKey: providerConfigKey,
558+
}
559+
560+
cExp := marshalExpression(v.Count)
561+
if !cExp.Empty() {
562+
a.CountExpression = &cExp
563+
} else {
564+
fExp := marshalExpression(v.ForEach)
565+
if !fExp.Empty() {
566+
a.ForEachExpression = &fExp
567+
}
568+
}
569+
570+
as = append(as, a)
571+
}
572+
573+
sort.Slice(as, func(i, j int) bool {
574+
return as[i].Address < as[j].Address
575+
})
576+
return as
577+
}
578+
523579
// Flatten all resource provider keys in a module and its descendants, such
524580
// that any resources from providers using a configuration passed through the
525581
// module call have a direct reference to that provider configuration.

internal/command/show_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,101 @@ func TestShow_json_output_sensitive(t *testing.T) {
749749
}
750750
}
751751

752+
func TestShow_json_output_actions(t *testing.T) {
753+
td := t.TempDir()
754+
inputDir := "testdata/show-json-actions"
755+
testCopyDir(t, inputDir, td)
756+
t.Chdir(td)
757+
758+
providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}})
759+
defer close()
760+
761+
p := showFixtureProvider()
762+
763+
// init
764+
ui := new(cli.MockUi)
765+
view, _ := testView(t)
766+
ic := &InitCommand{
767+
Meta: Meta{
768+
testingOverrides: metaOverridesForProvider(p),
769+
Ui: ui,
770+
View: view,
771+
ProviderSource: providerSource,
772+
AllowExperimentalFeatures: true,
773+
},
774+
}
775+
if code := ic.Run([]string{}); code != 0 {
776+
t.Fatalf("init failed\n%s", ui.ErrorWriter)
777+
}
778+
779+
// plan
780+
planView, planDone := testView(t)
781+
pc := &PlanCommand{
782+
Meta: Meta{
783+
testingOverrides: metaOverridesForProvider(p),
784+
View: planView,
785+
ProviderSource: providerSource,
786+
},
787+
}
788+
789+
args := []string{
790+
"-out=terraform.plan",
791+
}
792+
code := pc.Run(args)
793+
planOutput := planDone(t)
794+
795+
if code != 0 {
796+
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr())
797+
}
798+
799+
// show
800+
showView, showDone := testView(t)
801+
sc := &ShowCommand{
802+
Meta: Meta{
803+
testingOverrides: metaOverridesForProvider(p),
804+
View: showView,
805+
ProviderSource: providerSource,
806+
AllowExperimentalFeatures: true,
807+
},
808+
}
809+
810+
args = []string{
811+
"-json",
812+
"terraform.plan",
813+
}
814+
defer os.Remove("terraform.plan")
815+
code = sc.Run(args)
816+
showOutput := showDone(t)
817+
818+
if code != 0 {
819+
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr())
820+
}
821+
822+
// compare ui output to wanted output
823+
var got, want plan
824+
825+
gotString := showOutput.Stdout()
826+
json.Unmarshal([]byte(gotString), &got)
827+
828+
wantFile, err := os.Open("output.json")
829+
if err != nil {
830+
t.Fatalf("unexpected err: %s", err)
831+
}
832+
defer wantFile.Close()
833+
byteValue, err := ioutil.ReadAll(wantFile)
834+
if err != nil {
835+
t.Fatalf("unexpected err: %s", err)
836+
}
837+
json.Unmarshal([]byte(byteValue), &want)
838+
839+
// Disregard format version to reduce needless test fixture churn
840+
want.FormatVersion = got.FormatVersion
841+
842+
if !cmp.Equal(got, want) {
843+
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
844+
}
845+
}
846+
752847
// Failing conditions are only present in JSON output for refresh-only plans,
753848
// so we test that separately here.
754849
func TestShow_json_output_conditions_refresh_only(t *testing.T) {
@@ -1032,6 +1127,16 @@ func showFixtureSchema() *providers.GetProviderSchemaResponse {
10321127
},
10331128
},
10341129
},
1130+
Actions: map[string]providers.ActionSchema{
1131+
"test_unlinked": {
1132+
ConfigSchema: &configschema.Block{
1133+
Attributes: map[string]*configschema.Attribute{
1134+
"attr": {Type: cty.String, Optional: true},
1135+
},
1136+
},
1137+
Unlinked: &providers.UnlinkedAction{},
1138+
},
1139+
},
10351140
}
10361141
}
10371142

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
provider "test" {
2+
region = "somewhere"
3+
}
4+
5+
variable "test_var" {
6+
default = "bar"
7+
}
8+
9+
action "test_action" "hello" {
10+
count = 3
11+
config {
12+
attr = "Hello, World #${count.index}!"
13+
}
14+
}
15+
16+
resource "test_instance" "test" {
17+
ami = var.test_var
18+
19+
lifecycle {
20+
action_trigger {
21+
events = [before_create]
22+
actions = [action.test_action.hello]
23+
}
24+
}
25+
}
26+
27+
output "test" {
28+
value = var.test_var
29+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"format_version": "1.0",
3+
"applyable": true,
4+
"complete": true,
5+
"variables": {
6+
"test_var": {
7+
"value": "bar"
8+
}
9+
},
10+
"planned_values": {
11+
"outputs": {
12+
"test": {
13+
"sensitive": false,
14+
"type": "string",
15+
"value": "bar"
16+
}
17+
},
18+
"root_module": {
19+
"resources": [
20+
{
21+
"address": "test_instance.test",
22+
"mode": "managed",
23+
"type": "test_instance",
24+
"name": "test",
25+
"provider_name": "registry.terraform.io/hashicorp/test",
26+
"schema_version": 0,
27+
"sensitive_values": {},
28+
"values": {
29+
"ami": "bar"
30+
}
31+
}
32+
]
33+
}
34+
},
35+
"prior_state": {
36+
"format_version": "1.0",
37+
"values": {
38+
"outputs": {
39+
"test": {
40+
"sensitive": false,
41+
"type": "string",
42+
"value": "bar"
43+
}
44+
},
45+
"root_module": {}
46+
}
47+
},
48+
"resource_changes": [
49+
{
50+
"address": "test_instance.test",
51+
"mode": "managed",
52+
"type": "test_instance",
53+
"provider_name": "registry.terraform.io/hashicorp/test",
54+
"name": "test",
55+
"change": {
56+
"actions": ["create"],
57+
"before": null,
58+
"after_unknown": {
59+
"id": true
60+
},
61+
"after": {
62+
"ami": "bar"
63+
},
64+
"after_sensitive": {},
65+
"before_sensitive": false
66+
}
67+
}
68+
],
69+
"output_changes": {
70+
"test": {
71+
"actions": ["create"],
72+
"before": null,
73+
"after": "bar",
74+
"after_unknown": false,
75+
"before_sensitive": false,
76+
"after_sensitive": false
77+
}
78+
},
79+
"configuration": {
80+
"provider_config": {
81+
"test": {
82+
"name": "test",
83+
"full_name": "registry.terraform.io/hashicorp/test",
84+
"expressions": {
85+
"region": {
86+
"constant_value": "somewhere"
87+
}
88+
}
89+
}
90+
},
91+
"root_module": {
92+
"outputs": {
93+
"test": {
94+
"expression": {
95+
"references": ["var.test_var"]
96+
}
97+
}
98+
},
99+
"resources": [
100+
{
101+
"address": "test_instance.test",
102+
"mode": "managed",
103+
"type": "test_instance",
104+
"name": "test",
105+
"provider_config_key": "test",
106+
"schema_version": 0,
107+
"expressions": {
108+
"ami": {
109+
"references": ["var.test_var"]
110+
}
111+
}
112+
}
113+
],
114+
"variables": {
115+
"test_var": {
116+
"default": "bar"
117+
}
118+
},
119+
"actions": [
120+
{
121+
"address": "action.test_action.hello",
122+
"type": "test_action",
123+
"name": "hello",
124+
"provider_config_key": "test",
125+
"count_expression": {
126+
"constant_value": 3
127+
}
128+
}
129+
]
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)