1package grpcwrap
2
3import (
4	"context"
5
6	"github.com/hashicorp/terraform/internal/plugin/convert"
7	"github.com/hashicorp/terraform/internal/providers"
8	"github.com/hashicorp/terraform/internal/tfplugin5"
9	"github.com/zclconf/go-cty/cty"
10	ctyjson "github.com/zclconf/go-cty/cty/json"
11	"github.com/zclconf/go-cty/cty/msgpack"
12)
13
14// New wraps a providers.Interface to implement a grpc ProviderServer.
15// This is useful for creating a test binary out of an internal provider
16// implementation.
17func Provider(p providers.Interface) tfplugin5.ProviderServer {
18	return &provider{
19		provider: p,
20		schema:   p.GetProviderSchema(),
21	}
22}
23
24type provider struct {
25	provider providers.Interface
26	schema   providers.GetProviderSchemaResponse
27}
28
29func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema_Request) (*tfplugin5.GetProviderSchema_Response, error) {
30	resp := &tfplugin5.GetProviderSchema_Response{
31		ResourceSchemas:   make(map[string]*tfplugin5.Schema),
32		DataSourceSchemas: make(map[string]*tfplugin5.Schema),
33	}
34
35	resp.Provider = &tfplugin5.Schema{
36		Block: &tfplugin5.Schema_Block{},
37	}
38	if p.schema.Provider.Block != nil {
39		resp.Provider.Block = convert.ConfigSchemaToProto(p.schema.Provider.Block)
40	}
41
42	resp.ProviderMeta = &tfplugin5.Schema{
43		Block: &tfplugin5.Schema_Block{},
44	}
45	if p.schema.ProviderMeta.Block != nil {
46		resp.ProviderMeta.Block = convert.ConfigSchemaToProto(p.schema.ProviderMeta.Block)
47	}
48
49	for typ, res := range p.schema.ResourceTypes {
50		resp.ResourceSchemas[typ] = &tfplugin5.Schema{
51			Version: res.Version,
52			Block:   convert.ConfigSchemaToProto(res.Block),
53		}
54	}
55	for typ, dat := range p.schema.DataSources {
56		resp.DataSourceSchemas[typ] = &tfplugin5.Schema{
57			Version: dat.Version,
58			Block:   convert.ConfigSchemaToProto(dat.Block),
59		}
60	}
61
62	// include any diagnostics from the original GetSchema call
63	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.schema.Diagnostics)
64
65	return resp, nil
66}
67
68func (p *provider) PrepareProviderConfig(_ context.Context, req *tfplugin5.PrepareProviderConfig_Request) (*tfplugin5.PrepareProviderConfig_Response, error) {
69	resp := &tfplugin5.PrepareProviderConfig_Response{}
70	ty := p.schema.Provider.Block.ImpliedType()
71
72	configVal, err := decodeDynamicValue(req.Config, ty)
73	if err != nil {
74		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
75		return resp, nil
76	}
77
78	prepareResp := p.provider.ValidateProviderConfig(providers.ValidateProviderConfigRequest{
79		Config: configVal,
80	})
81
82	// the PreparedConfig value is no longer used
83	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, prepareResp.Diagnostics)
84	return resp, nil
85}
86
87func (p *provider) ValidateResourceTypeConfig(_ context.Context, req *tfplugin5.ValidateResourceTypeConfig_Request) (*tfplugin5.ValidateResourceTypeConfig_Response, error) {
88	resp := &tfplugin5.ValidateResourceTypeConfig_Response{}
89	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
90
91	configVal, err := decodeDynamicValue(req.Config, ty)
92	if err != nil {
93		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
94		return resp, nil
95	}
96
97	validateResp := p.provider.ValidateResourceConfig(providers.ValidateResourceConfigRequest{
98		TypeName: req.TypeName,
99		Config:   configVal,
100	})
101
102	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
103	return resp, nil
104}
105
106func (p *provider) ValidateDataSourceConfig(_ context.Context, req *tfplugin5.ValidateDataSourceConfig_Request) (*tfplugin5.ValidateDataSourceConfig_Response, error) {
107	resp := &tfplugin5.ValidateDataSourceConfig_Response{}
108	ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
109
110	configVal, err := decodeDynamicValue(req.Config, ty)
111	if err != nil {
112		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
113		return resp, nil
114	}
115
116	validateResp := p.provider.ValidateDataResourceConfig(providers.ValidateDataResourceConfigRequest{
117		TypeName: req.TypeName,
118		Config:   configVal,
119	})
120
121	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
122	return resp, nil
123}
124
125func (p *provider) UpgradeResourceState(_ context.Context, req *tfplugin5.UpgradeResourceState_Request) (*tfplugin5.UpgradeResourceState_Response, error) {
126	resp := &tfplugin5.UpgradeResourceState_Response{}
127	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
128
129	upgradeResp := p.provider.UpgradeResourceState(providers.UpgradeResourceStateRequest{
130		TypeName:     req.TypeName,
131		Version:      req.Version,
132		RawStateJSON: req.RawState.Json,
133	})
134
135	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics)
136	if upgradeResp.Diagnostics.HasErrors() {
137		return resp, nil
138	}
139
140	dv, err := encodeDynamicValue(upgradeResp.UpgradedState, ty)
141	if err != nil {
142		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
143		return resp, nil
144	}
145
146	resp.UpgradedState = dv
147
148	return resp, nil
149}
150
151func (p *provider) Configure(_ context.Context, req *tfplugin5.Configure_Request) (*tfplugin5.Configure_Response, error) {
152	resp := &tfplugin5.Configure_Response{}
153	ty := p.schema.Provider.Block.ImpliedType()
154
155	configVal, err := decodeDynamicValue(req.Config, ty)
156	if err != nil {
157		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
158		return resp, nil
159	}
160
161	configureResp := p.provider.ConfigureProvider(providers.ConfigureProviderRequest{
162		TerraformVersion: req.TerraformVersion,
163		Config:           configVal,
164	})
165
166	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, configureResp.Diagnostics)
167	return resp, nil
168}
169
170func (p *provider) ReadResource(_ context.Context, req *tfplugin5.ReadResource_Request) (*tfplugin5.ReadResource_Response, error) {
171	resp := &tfplugin5.ReadResource_Response{}
172	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
173
174	stateVal, err := decodeDynamicValue(req.CurrentState, ty)
175	if err != nil {
176		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
177		return resp, nil
178	}
179
180	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
181	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
182	if err != nil {
183		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
184		return resp, nil
185	}
186
187	readResp := p.provider.ReadResource(providers.ReadResourceRequest{
188		TypeName:     req.TypeName,
189		PriorState:   stateVal,
190		Private:      req.Private,
191		ProviderMeta: metaVal,
192	})
193	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
194	if readResp.Diagnostics.HasErrors() {
195		return resp, nil
196	}
197	resp.Private = readResp.Private
198
199	dv, err := encodeDynamicValue(readResp.NewState, ty)
200	if err != nil {
201		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
202		return resp, nil
203	}
204	resp.NewState = dv
205
206	return resp, nil
207}
208
209func (p *provider) PlanResourceChange(_ context.Context, req *tfplugin5.PlanResourceChange_Request) (*tfplugin5.PlanResourceChange_Response, error) {
210	resp := &tfplugin5.PlanResourceChange_Response{}
211	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
212
213	priorStateVal, err := decodeDynamicValue(req.PriorState, ty)
214	if err != nil {
215		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
216		return resp, nil
217	}
218
219	proposedStateVal, err := decodeDynamicValue(req.ProposedNewState, ty)
220	if err != nil {
221		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
222		return resp, nil
223	}
224
225	configVal, err := decodeDynamicValue(req.Config, ty)
226	if err != nil {
227		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
228		return resp, nil
229	}
230
231	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
232	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
233	if err != nil {
234		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
235		return resp, nil
236	}
237
238	planResp := p.provider.PlanResourceChange(providers.PlanResourceChangeRequest{
239		TypeName:         req.TypeName,
240		PriorState:       priorStateVal,
241		ProposedNewState: proposedStateVal,
242		Config:           configVal,
243		PriorPrivate:     req.PriorPrivate,
244		ProviderMeta:     metaVal,
245	})
246	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, planResp.Diagnostics)
247	if planResp.Diagnostics.HasErrors() {
248		return resp, nil
249	}
250
251	resp.PlannedPrivate = planResp.PlannedPrivate
252
253	resp.PlannedState, err = encodeDynamicValue(planResp.PlannedState, ty)
254	if err != nil {
255		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
256		return resp, nil
257	}
258
259	for _, path := range planResp.RequiresReplace {
260		resp.RequiresReplace = append(resp.RequiresReplace, convert.PathToAttributePath(path))
261	}
262
263	return resp, nil
264}
265
266func (p *provider) ApplyResourceChange(_ context.Context, req *tfplugin5.ApplyResourceChange_Request) (*tfplugin5.ApplyResourceChange_Response, error) {
267	resp := &tfplugin5.ApplyResourceChange_Response{}
268	ty := p.schema.ResourceTypes[req.TypeName].Block.ImpliedType()
269
270	priorStateVal, err := decodeDynamicValue(req.PriorState, ty)
271	if err != nil {
272		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
273		return resp, nil
274	}
275
276	plannedStateVal, err := decodeDynamicValue(req.PlannedState, ty)
277	if err != nil {
278		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
279		return resp, nil
280	}
281
282	configVal, err := decodeDynamicValue(req.Config, ty)
283	if err != nil {
284		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
285		return resp, nil
286	}
287
288	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
289	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
290	if err != nil {
291		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
292		return resp, nil
293	}
294
295	applyResp := p.provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
296		TypeName:       req.TypeName,
297		PriorState:     priorStateVal,
298		PlannedState:   plannedStateVal,
299		Config:         configVal,
300		PlannedPrivate: req.PlannedPrivate,
301		ProviderMeta:   metaVal,
302	})
303
304	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, applyResp.Diagnostics)
305	if applyResp.Diagnostics.HasErrors() {
306		return resp, nil
307	}
308	resp.Private = applyResp.Private
309
310	resp.NewState, err = encodeDynamicValue(applyResp.NewState, ty)
311	if err != nil {
312		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
313		return resp, nil
314	}
315
316	return resp, nil
317}
318
319func (p *provider) ImportResourceState(_ context.Context, req *tfplugin5.ImportResourceState_Request) (*tfplugin5.ImportResourceState_Response, error) {
320	resp := &tfplugin5.ImportResourceState_Response{}
321
322	importResp := p.provider.ImportResourceState(providers.ImportResourceStateRequest{
323		TypeName: req.TypeName,
324		ID:       req.Id,
325	})
326	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, importResp.Diagnostics)
327
328	for _, res := range importResp.ImportedResources {
329		ty := p.schema.ResourceTypes[res.TypeName].Block.ImpliedType()
330		state, err := encodeDynamicValue(res.State, ty)
331		if err != nil {
332			resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
333			continue
334		}
335
336		resp.ImportedResources = append(resp.ImportedResources, &tfplugin5.ImportResourceState_ImportedResource{
337			TypeName: res.TypeName,
338			State:    state,
339			Private:  res.Private,
340		})
341	}
342
343	return resp, nil
344}
345
346func (p *provider) ReadDataSource(_ context.Context, req *tfplugin5.ReadDataSource_Request) (*tfplugin5.ReadDataSource_Response, error) {
347	resp := &tfplugin5.ReadDataSource_Response{}
348	ty := p.schema.DataSources[req.TypeName].Block.ImpliedType()
349
350	configVal, err := decodeDynamicValue(req.Config, ty)
351	if err != nil {
352		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
353		return resp, nil
354	}
355
356	metaTy := p.schema.ProviderMeta.Block.ImpliedType()
357	metaVal, err := decodeDynamicValue(req.ProviderMeta, metaTy)
358	if err != nil {
359		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
360		return resp, nil
361	}
362
363	readResp := p.provider.ReadDataSource(providers.ReadDataSourceRequest{
364		TypeName:     req.TypeName,
365		Config:       configVal,
366		ProviderMeta: metaVal,
367	})
368	resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, readResp.Diagnostics)
369	if readResp.Diagnostics.HasErrors() {
370		return resp, nil
371	}
372
373	resp.State, err = encodeDynamicValue(readResp.State, ty)
374	if err != nil {
375		resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
376		return resp, nil
377	}
378
379	return resp, nil
380}
381
382func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
383	resp := &tfplugin5.Stop_Response{}
384	err := p.provider.Stop()
385	if err != nil {
386		resp.Error = err.Error()
387	}
388	return resp, nil
389}
390
391// decode a DynamicValue from either the JSON or MsgPack encoding.
392func decodeDynamicValue(v *tfplugin5.DynamicValue, ty cty.Type) (cty.Value, error) {
393	// always return a valid value
394	var err error
395	res := cty.NullVal(ty)
396	if v == nil {
397		return res, nil
398	}
399
400	switch {
401	case len(v.Msgpack) > 0:
402		res, err = msgpack.Unmarshal(v.Msgpack, ty)
403	case len(v.Json) > 0:
404		res, err = ctyjson.Unmarshal(v.Json, ty)
405	}
406	return res, err
407}
408
409// encode a cty.Value into a DynamicValue msgpack payload.
410func encodeDynamicValue(v cty.Value, ty cty.Type) (*tfplugin5.DynamicValue, error) {
411	mp, err := msgpack.Marshal(v, ty)
412	return &tfplugin5.DynamicValue{
413		Msgpack: mp,
414	}, err
415}
416