Skip to content

Commit 1479d62

Browse files
committed
Introduce (*ResourceData).GetRawWriteOnly() and (*ResourceData).GetWriteOnly() for retrieving write-only values during apply
1 parent 2aec830 commit 1479d62

File tree

7 files changed

+938
-0
lines changed

7 files changed

+938
-0
lines changed

helper/schema/grpc_provider.go

+6
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,12 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro
11911191
priorState.ProviderMeta = providerSchemaVal
11921192
}
11931193

1194+
// This is a hack to pass write-only values to instanceDiff,
1195+
// for (*ResourceData).GetRawWriteOnly() and (*ResourceData).GetWriteOnly().
1196+
// Ideally, this should be set in DiffFromValues() but since it is a public
1197+
// function, we cannot change its signature.
1198+
diff.RawWriteOnly = createRawWriteOnly(configVal, schemaBlock)
1199+
11941200
newInstanceState, diags := res.Apply(ctx, priorState, diff, s.provider.Meta())
11951201
resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, diags)
11961202

helper/schema/grpc_provider_test.go

+315
Original file line numberDiff line numberDiff line change
@@ -5094,6 +5094,88 @@ func TestPlanResourceChange(t *testing.T) {
50945094
UnsafeToUseLegacyTypeSystem: true,
50955095
},
50965096
},
5097+
"create-writeonly-plan-modification": {
5098+
server: NewGRPCProviderServer(&Provider{
5099+
ResourcesMap: map[string]*Resource{
5100+
"test": {
5101+
SchemaVersion: 4,
5102+
CustomizeDiff: func(ctx context.Context, d *ResourceDiff, i interface{}) error {
5103+
val := d.Get("foo")
5104+
if val != "bar" {
5105+
t.Fatalf("Incorrect write-only value")
5106+
}
5107+
5108+
return nil
5109+
},
5110+
Schema: map[string]*Schema{
5111+
"foo": {
5112+
Type: TypeString,
5113+
Optional: true,
5114+
WriteOnly: true,
5115+
},
5116+
},
5117+
},
5118+
},
5119+
}),
5120+
req: &tfprotov5.PlanResourceChangeRequest{
5121+
TypeName: "test",
5122+
PriorState: &tfprotov5.DynamicValue{
5123+
MsgPack: mustMsgpackMarshal(
5124+
cty.Object(map[string]cty.Type{
5125+
"foo": cty.String,
5126+
}),
5127+
cty.NullVal(
5128+
cty.Object(map[string]cty.Type{
5129+
"foo": cty.String,
5130+
}),
5131+
),
5132+
),
5133+
},
5134+
ProposedNewState: &tfprotov5.DynamicValue{
5135+
MsgPack: mustMsgpackMarshal(
5136+
cty.Object(map[string]cty.Type{
5137+
"id": cty.String,
5138+
"foo": cty.String,
5139+
}),
5140+
cty.ObjectVal(map[string]cty.Value{
5141+
"id": cty.UnknownVal(cty.String),
5142+
"foo": cty.StringVal("bar"),
5143+
}),
5144+
),
5145+
},
5146+
Config: &tfprotov5.DynamicValue{
5147+
MsgPack: mustMsgpackMarshal(
5148+
cty.Object(map[string]cty.Type{
5149+
"id": cty.String,
5150+
"foo": cty.String,
5151+
}),
5152+
cty.ObjectVal(map[string]cty.Value{
5153+
"id": cty.NullVal(cty.String),
5154+
"foo": cty.StringVal("bar"),
5155+
}),
5156+
),
5157+
},
5158+
},
5159+
expected: &tfprotov5.PlanResourceChangeResponse{
5160+
PlannedState: &tfprotov5.DynamicValue{
5161+
MsgPack: mustMsgpackMarshal(
5162+
cty.Object(map[string]cty.Type{
5163+
"id": cty.String,
5164+
"foo": cty.String,
5165+
}),
5166+
cty.ObjectVal(map[string]cty.Value{
5167+
"id": cty.UnknownVal(cty.String),
5168+
"foo": cty.NullVal(cty.String),
5169+
}),
5170+
),
5171+
},
5172+
PlannedPrivate: []byte(`{"_new_extra_shim":{}}`),
5173+
RequiresReplace: []*tftypes.AttributePath{
5174+
tftypes.NewAttributePath().WithAttributeName("id"),
5175+
},
5176+
UnsafeToUseLegacyTypeSystem: true,
5177+
},
5178+
},
50975179
}
50985180

50995181
for name, testCase := range testCases {
@@ -5528,6 +5610,239 @@ func TestApplyResourceChange_bigint(t *testing.T) {
55285610
}
55295611
}
55305612

5613+
func TestApplyResourceChange_writeOnly(t *testing.T) {
5614+
t.Parallel()
5615+
5616+
testCases := map[string]struct {
5617+
TestResource *Resource
5618+
ExpectedUnsafeLegacyTypeSystem bool
5619+
}{
5620+
"Create": {
5621+
TestResource: &Resource{
5622+
SchemaVersion: 4,
5623+
Schema: map[string]*Schema{
5624+
"foo": {
5625+
Type: TypeInt,
5626+
Optional: true,
5627+
},
5628+
"write_only_bar": {
5629+
Type: TypeString,
5630+
Optional: true,
5631+
WriteOnly: true,
5632+
},
5633+
},
5634+
Create: func(rd *ResourceData, _ interface{}) error {
5635+
rd.SetId("baz")
5636+
writeOnlyVal, err := rd.GetWriteOnly(cty.GetAttrPath("write_only_bar"))
5637+
if err != nil {
5638+
t.Errorf("Unable to retrieve write only attribute, err: %s", err)
5639+
}
5640+
if writeOnlyVal.AsString() != "bar" {
5641+
t.Errorf("Incorrect write-only value: expected bar but got %s", writeOnlyVal)
5642+
}
5643+
return nil
5644+
},
5645+
},
5646+
ExpectedUnsafeLegacyTypeSystem: true,
5647+
},
5648+
"CreateContext": {
5649+
TestResource: &Resource{
5650+
SchemaVersion: 4,
5651+
Schema: map[string]*Schema{
5652+
"foo": {
5653+
Type: TypeInt,
5654+
Optional: true,
5655+
},
5656+
"write_only_bar": {
5657+
Type: TypeString,
5658+
Optional: true,
5659+
WriteOnly: true,
5660+
},
5661+
},
5662+
CreateContext: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics {
5663+
rd.SetId("baz")
5664+
writeOnlyVal, err := rd.GetWriteOnly(cty.GetAttrPath("write_only_bar"))
5665+
if err != nil {
5666+
t.Errorf("Unable to retrieve write only attribute, err: %s", err)
5667+
}
5668+
if writeOnlyVal.AsString() != "bar" {
5669+
t.Errorf("Incorrect write-only value: expected bar but got %s", writeOnlyVal)
5670+
}
5671+
return nil
5672+
},
5673+
},
5674+
ExpectedUnsafeLegacyTypeSystem: true,
5675+
},
5676+
"CreateWithoutTimeout": {
5677+
TestResource: &Resource{
5678+
SchemaVersion: 4,
5679+
Schema: map[string]*Schema{
5680+
"foo": {
5681+
Type: TypeInt,
5682+
Optional: true,
5683+
},
5684+
"write_only_bar": {
5685+
Type: TypeString,
5686+
Optional: true,
5687+
WriteOnly: true,
5688+
},
5689+
},
5690+
CreateWithoutTimeout: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics {
5691+
rd.SetId("baz")
5692+
writeOnlyVal, err := rd.GetWriteOnly(cty.GetAttrPath("write_only_bar"))
5693+
if err != nil {
5694+
t.Errorf("Unable to retrieve write only attribute, err: %s", err)
5695+
}
5696+
if writeOnlyVal.AsString() != "bar" {
5697+
t.Errorf("Incorrect write-only value: expected bar but got %s", writeOnlyVal)
5698+
}
5699+
return nil
5700+
},
5701+
},
5702+
ExpectedUnsafeLegacyTypeSystem: true,
5703+
},
5704+
"Create_cty": {
5705+
TestResource: &Resource{
5706+
SchemaVersion: 4,
5707+
Schema: map[string]*Schema{
5708+
"foo": {
5709+
Type: TypeInt,
5710+
Optional: true,
5711+
},
5712+
"write_only_bar": {
5713+
Type: TypeString,
5714+
Optional: true,
5715+
WriteOnly: true,
5716+
},
5717+
},
5718+
CreateWithoutTimeout: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics {
5719+
rd.SetId("baz")
5720+
if rd.GetRawWriteOnly().IsNull() {
5721+
return diag.FromErr(errors.New("null raw writeOnly val"))
5722+
}
5723+
if rd.GetRawWriteOnly().GetAttr("write_only_bar").Type() != cty.String {
5724+
return diag.FromErr(errors.New("write_only_bar is not of the expected type string"))
5725+
}
5726+
writeOnlyVal := rd.GetRawWriteOnly().GetAttr("write_only_bar").AsString()
5727+
if writeOnlyVal != "bar" {
5728+
t.Errorf("Incorrect write-only value: expected bar but got %s", writeOnlyVal)
5729+
}
5730+
return nil
5731+
},
5732+
},
5733+
ExpectedUnsafeLegacyTypeSystem: true,
5734+
},
5735+
"CreateContext_SchemaFunc": {
5736+
TestResource: &Resource{
5737+
SchemaFunc: func() map[string]*Schema {
5738+
return map[string]*Schema{
5739+
"id": {
5740+
Type: TypeString,
5741+
Computed: true,
5742+
},
5743+
"write_only_bar": {
5744+
Type: TypeString,
5745+
Optional: true,
5746+
WriteOnly: true,
5747+
},
5748+
}
5749+
},
5750+
CreateContext: func(_ context.Context, rd *ResourceData, _ interface{}) diag.Diagnostics {
5751+
rd.SetId("baz")
5752+
writeOnlyVal, err := rd.GetWriteOnly(cty.GetAttrPath("write_only_bar"))
5753+
if err != nil {
5754+
t.Errorf("Unable to retrieve write only attribute, err: %s", err)
5755+
}
5756+
if writeOnlyVal.AsString() != "bar" {
5757+
t.Errorf("Incorrect write-only value: expected bar but got %s", writeOnlyVal)
5758+
}
5759+
return nil
5760+
},
5761+
},
5762+
ExpectedUnsafeLegacyTypeSystem: true,
5763+
},
5764+
}
5765+
5766+
for name, testCase := range testCases {
5767+
name, testCase := name, testCase
5768+
5769+
t.Run(name, func(t *testing.T) {
5770+
t.Parallel()
5771+
5772+
server := NewGRPCProviderServer(&Provider{
5773+
ResourcesMap: map[string]*Resource{
5774+
"test": testCase.TestResource,
5775+
},
5776+
})
5777+
5778+
schema := testCase.TestResource.CoreConfigSchema()
5779+
priorState, err := msgpack.Marshal(cty.NullVal(schema.ImpliedType()), schema.ImpliedType())
5780+
if err != nil {
5781+
t.Fatal(err)
5782+
}
5783+
5784+
// A proposed state with only the ID unknown will produce a nil diff, and
5785+
// should return the proposed state value.
5786+
plannedVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
5787+
"id": cty.UnknownVal(cty.String),
5788+
}))
5789+
if err != nil {
5790+
t.Fatal(err)
5791+
}
5792+
plannedState, err := msgpack.Marshal(plannedVal, schema.ImpliedType())
5793+
if err != nil {
5794+
t.Fatal(err)
5795+
}
5796+
5797+
config, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
5798+
"id": cty.NullVal(cty.String),
5799+
"write_only_bar": cty.StringVal("bar"),
5800+
}))
5801+
if err != nil {
5802+
t.Fatal(err)
5803+
}
5804+
configBytes, err := msgpack.Marshal(config, schema.ImpliedType())
5805+
if err != nil {
5806+
t.Fatal(err)
5807+
}
5808+
5809+
testReq := &tfprotov5.ApplyResourceChangeRequest{
5810+
TypeName: "test",
5811+
PriorState: &tfprotov5.DynamicValue{
5812+
MsgPack: priorState,
5813+
},
5814+
PlannedState: &tfprotov5.DynamicValue{
5815+
MsgPack: plannedState,
5816+
},
5817+
Config: &tfprotov5.DynamicValue{
5818+
MsgPack: configBytes,
5819+
},
5820+
}
5821+
5822+
resp, err := server.ApplyResourceChange(context.Background(), testReq)
5823+
if err != nil {
5824+
t.Fatal(err)
5825+
}
5826+
5827+
newStateVal, err := msgpack.Unmarshal(resp.NewState.MsgPack, schema.ImpliedType())
5828+
if err != nil {
5829+
t.Fatal(err)
5830+
}
5831+
5832+
id := newStateVal.GetAttr("id").AsString()
5833+
if id != "baz" {
5834+
t.Fatalf("incorrect final state: %#v\n", newStateVal)
5835+
}
5836+
5837+
//nolint:staticcheck // explicitly for this SDK
5838+
if testCase.ExpectedUnsafeLegacyTypeSystem != resp.UnsafeToUseLegacyTypeSystem {
5839+
//nolint:staticcheck // explicitly for this SDK
5840+
t.Fatalf("expected UnsafeLegacyTypeSystem %t, got: %t", testCase.ExpectedUnsafeLegacyTypeSystem, resp.UnsafeToUseLegacyTypeSystem)
5841+
}
5842+
})
5843+
}
5844+
}
5845+
55315846
func TestImportResourceState(t *testing.T) {
55325847
t.Parallel()
55335848

0 commit comments

Comments
 (0)