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