github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/plugin/grpc_provisioner.go (about) 1 package plugin 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "sync" 8 9 plugin "github.com/hashicorp/go-plugin" 10 "github.com/cycloidio/terraform/configs/configschema" 11 "github.com/cycloidio/terraform/plugin/convert" 12 "github.com/cycloidio/terraform/provisioners" 13 proto "github.com/cycloidio/terraform/tfplugin5" 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/msgpack" 16 "google.golang.org/grpc" 17 ) 18 19 // GRPCProvisionerPlugin is the plugin.GRPCPlugin implementation. 20 type GRPCProvisionerPlugin struct { 21 plugin.Plugin 22 GRPCProvisioner func() proto.ProvisionerServer 23 } 24 25 func (p *GRPCProvisionerPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { 26 return &GRPCProvisioner{ 27 client: proto.NewProvisionerClient(c), 28 ctx: ctx, 29 }, nil 30 } 31 32 func (p *GRPCProvisionerPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { 33 proto.RegisterProvisionerServer(s, p.GRPCProvisioner()) 34 return nil 35 } 36 37 // provisioners.Interface grpc implementation 38 type GRPCProvisioner struct { 39 // PluginClient provides a reference to the plugin.Client which controls the plugin process. 40 // This allows the GRPCProvider a way to shutdown the plugin process. 41 PluginClient *plugin.Client 42 43 client proto.ProvisionerClient 44 ctx context.Context 45 46 // Cache the schema since we need it for serialization in each method call. 47 mu sync.Mutex 48 schema *configschema.Block 49 } 50 51 func (p *GRPCProvisioner) GetSchema() (resp provisioners.GetSchemaResponse) { 52 p.mu.Lock() 53 defer p.mu.Unlock() 54 55 if p.schema != nil { 56 return provisioners.GetSchemaResponse{ 57 Provisioner: p.schema, 58 } 59 } 60 61 protoResp, err := p.client.GetSchema(p.ctx, new(proto.GetProvisionerSchema_Request)) 62 if err != nil { 63 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 64 return resp 65 } 66 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 67 68 if protoResp.Provisioner == nil { 69 resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing provisioner schema")) 70 return resp 71 } 72 73 resp.Provisioner = convert.ProtoToConfigSchema(protoResp.Provisioner.Block) 74 75 p.schema = resp.Provisioner 76 77 return resp 78 } 79 80 func (p *GRPCProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) { 81 schema := p.GetSchema() 82 if schema.Diagnostics.HasErrors() { 83 resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) 84 return resp 85 } 86 87 mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) 88 if err != nil { 89 resp.Diagnostics = resp.Diagnostics.Append(err) 90 return resp 91 } 92 93 protoReq := &proto.ValidateProvisionerConfig_Request{ 94 Config: &proto.DynamicValue{Msgpack: mp}, 95 } 96 protoResp, err := p.client.ValidateProvisionerConfig(p.ctx, protoReq) 97 if err != nil { 98 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 99 return resp 100 } 101 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) 102 return resp 103 } 104 105 func (p *GRPCProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) { 106 schema := p.GetSchema() 107 if schema.Diagnostics.HasErrors() { 108 resp.Diagnostics = resp.Diagnostics.Append(schema.Diagnostics) 109 return resp 110 } 111 112 mp, err := msgpack.Marshal(r.Config, schema.Provisioner.ImpliedType()) 113 if err != nil { 114 resp.Diagnostics = resp.Diagnostics.Append(err) 115 return resp 116 } 117 118 // connection is always assumed to be a simple string map 119 connMP, err := msgpack.Marshal(r.Connection, cty.Map(cty.String)) 120 if err != nil { 121 resp.Diagnostics = resp.Diagnostics.Append(err) 122 return resp 123 } 124 125 protoReq := &proto.ProvisionResource_Request{ 126 Config: &proto.DynamicValue{Msgpack: mp}, 127 Connection: &proto.DynamicValue{Msgpack: connMP}, 128 } 129 130 outputClient, err := p.client.ProvisionResource(p.ctx, protoReq) 131 if err != nil { 132 resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) 133 return resp 134 } 135 136 for { 137 rcv, err := outputClient.Recv() 138 if rcv != nil { 139 r.UIOutput.Output(rcv.Output) 140 } 141 if err != nil { 142 if err != io.EOF { 143 resp.Diagnostics = resp.Diagnostics.Append(err) 144 } 145 break 146 } 147 148 if len(rcv.Diagnostics) > 0 { 149 resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(rcv.Diagnostics)) 150 break 151 } 152 } 153 154 return resp 155 } 156 157 func (p *GRPCProvisioner) Stop() error { 158 protoResp, err := p.client.Stop(p.ctx, &proto.Stop_Request{}) 159 if err != nil { 160 return err 161 } 162 if protoResp.Error != "" { 163 return errors.New(protoResp.Error) 164 } 165 return nil 166 } 167 168 func (p *GRPCProvisioner) Close() error { 169 // check this since it's not automatically inserted during plugin creation 170 if p.PluginClient == nil { 171 logger.Debug("provisioner has no plugin.Client") 172 return nil 173 } 174 175 p.PluginClient.Kill() 176 return nil 177 }