github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/deploytest/pluginhost.go (about)

     1  // Copyright 2016-2018, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package deploytest
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"path/filepath"
    23  	"sync"
    24  
    25  	"github.com/blang/semver"
    26  	pbempty "github.com/golang/protobuf/ptypes/empty"
    27  
    28  	"google.golang.org/grpc"
    29  
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    33  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    35  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
    36  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    37  	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
    38  )
    39  
    40  var UseGrpcPluginsByDefault = false
    41  
    42  type LoadPluginFunc func(opts interface{}) (interface{}, error)
    43  type LoadPluginWithHostFunc func(opts interface{}, host plugin.Host) (interface{}, error)
    44  
    45  type LoadProviderFunc func() (plugin.Provider, error)
    46  type LoadProviderWithHostFunc func(host plugin.Host) (plugin.Provider, error)
    47  
    48  type LoadAnalyzerFunc func(opts *plugin.PolicyAnalyzerOptions) (plugin.Analyzer, error)
    49  type LoadAnalyzerWithHostFunc func(opts *plugin.PolicyAnalyzerOptions, host plugin.Host) (plugin.Analyzer, error)
    50  
    51  type PluginOption func(p *PluginLoader)
    52  
    53  func WithoutGrpc(p *PluginLoader) {
    54  	p.useGRPC = false
    55  }
    56  
    57  func WithGrpc(p *PluginLoader) {
    58  	p.useGRPC = true
    59  }
    60  
    61  func WithPath(path string) func(p *PluginLoader) {
    62  	return func(p *PluginLoader) {
    63  		p.path = path
    64  	}
    65  }
    66  
    67  type PluginLoader struct {
    68  	kind         workspace.PluginKind
    69  	name         string
    70  	version      semver.Version
    71  	load         LoadPluginFunc
    72  	loadWithHost LoadPluginWithHostFunc
    73  	path         string
    74  	useGRPC      bool
    75  }
    76  
    77  type ProviderOption = PluginOption
    78  type ProviderLoader = PluginLoader
    79  
    80  func NewProviderLoader(pkg tokens.Package, version semver.Version, load LoadProviderFunc,
    81  	opts ...ProviderOption) *ProviderLoader {
    82  
    83  	p := &ProviderLoader{
    84  		kind:    workspace.ResourcePlugin,
    85  		name:    string(pkg),
    86  		version: version,
    87  		load:    func(_ interface{}) (interface{}, error) { return load() },
    88  		useGRPC: UseGrpcPluginsByDefault,
    89  	}
    90  	for _, o := range opts {
    91  		o(p)
    92  	}
    93  	return p
    94  }
    95  
    96  func NewProviderLoaderWithHost(pkg tokens.Package, version semver.Version,
    97  	load LoadProviderWithHostFunc, opts ...ProviderOption) *ProviderLoader {
    98  
    99  	p := &ProviderLoader{
   100  		kind:         workspace.ResourcePlugin,
   101  		name:         string(pkg),
   102  		version:      version,
   103  		loadWithHost: func(_ interface{}, host plugin.Host) (interface{}, error) { return load(host) },
   104  		useGRPC:      UseGrpcPluginsByDefault,
   105  	}
   106  	for _, o := range opts {
   107  		o(p)
   108  	}
   109  	return p
   110  }
   111  
   112  func NewAnalyzerLoader(name string, load LoadAnalyzerFunc, opts ...PluginOption) *PluginLoader {
   113  	p := &PluginLoader{
   114  		kind: workspace.AnalyzerPlugin,
   115  		name: name,
   116  		load: func(optsI interface{}) (interface{}, error) {
   117  			opts, _ := optsI.(*plugin.PolicyAnalyzerOptions)
   118  			return load(opts)
   119  		},
   120  	}
   121  	for _, o := range opts {
   122  		o(p)
   123  	}
   124  	return p
   125  }
   126  
   127  func NewAnalyzerLoaderWithHost(name string, load LoadAnalyzerWithHostFunc, opts ...PluginOption) *PluginLoader {
   128  	p := &PluginLoader{
   129  		kind: workspace.AnalyzerPlugin,
   130  		name: name,
   131  		loadWithHost: func(optsI interface{}, host plugin.Host) (interface{}, error) {
   132  			opts, _ := optsI.(*plugin.PolicyAnalyzerOptions)
   133  			return load(opts, host)
   134  		},
   135  	}
   136  	for _, o := range opts {
   137  		o(p)
   138  	}
   139  	return p
   140  }
   141  
   142  type nopCloserT int
   143  
   144  func (nopCloserT) Close() error { return nil }
   145  
   146  var nopCloser io.Closer = nopCloserT(0)
   147  
   148  type grpcWrapper struct {
   149  	stop chan bool
   150  }
   151  
   152  func (w *grpcWrapper) Close() error {
   153  	go func() { w.stop <- true }()
   154  	return nil
   155  }
   156  
   157  func wrapProviderWithGrpc(provider plugin.Provider) (plugin.Provider, io.Closer, error) {
   158  	wrapper := &grpcWrapper{stop: make(chan bool)}
   159  	handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
   160  		Cancel: wrapper.stop,
   161  		Init: func(srv *grpc.Server) error {
   162  			pulumirpc.RegisterResourceProviderServer(srv, plugin.NewProviderServer(provider))
   163  			return nil
   164  		},
   165  		Options: rpcutil.OpenTracingServerInterceptorOptions(nil),
   166  	})
   167  	if err != nil {
   168  		return nil, nil, fmt.Errorf("could not start resource provider service: %w", err)
   169  	}
   170  	conn, err := grpc.Dial(
   171  		fmt.Sprintf("127.0.0.1:%v", handle.Port),
   172  		grpc.WithInsecure(),
   173  		grpc.WithUnaryInterceptor(rpcutil.OpenTracingClientInterceptor()),
   174  		grpc.WithStreamInterceptor(rpcutil.OpenTracingStreamClientInterceptor()),
   175  		rpcutil.GrpcChannelOptions(),
   176  	)
   177  	if err != nil {
   178  		contract.IgnoreClose(wrapper)
   179  		return nil, nil, fmt.Errorf("could not connect to resource provider service: %v", err)
   180  	}
   181  	wrapped := plugin.NewProviderWithClient(nil, provider.Pkg(), pulumirpc.NewResourceProviderClient(conn), false)
   182  	return wrapped, wrapper, nil
   183  }
   184  
   185  type hostEngine struct {
   186  	sink       diag.Sink
   187  	statusSink diag.Sink
   188  
   189  	address string
   190  	stop    chan bool
   191  }
   192  
   193  func (e *hostEngine) Log(_ context.Context, req *pulumirpc.LogRequest) (*pbempty.Empty, error) {
   194  	var sev diag.Severity
   195  	switch req.Severity {
   196  	case pulumirpc.LogSeverity_DEBUG:
   197  		sev = diag.Debug
   198  	case pulumirpc.LogSeverity_INFO:
   199  		sev = diag.Info
   200  	case pulumirpc.LogSeverity_WARNING:
   201  		sev = diag.Warning
   202  	case pulumirpc.LogSeverity_ERROR:
   203  		sev = diag.Error
   204  	default:
   205  		return nil, fmt.Errorf("Unrecognized logging severity: %v", req.Severity)
   206  	}
   207  
   208  	if req.Ephemeral {
   209  		e.statusSink.Logf(sev, diag.StreamMessage(resource.URN(req.Urn), req.Message, req.StreamId))
   210  	} else {
   211  		e.sink.Logf(sev, diag.StreamMessage(resource.URN(req.Urn), req.Message, req.StreamId))
   212  	}
   213  	return &pbempty.Empty{}, nil
   214  }
   215  func (e *hostEngine) GetRootResource(_ context.Context,
   216  	req *pulumirpc.GetRootResourceRequest) (*pulumirpc.GetRootResourceResponse, error) {
   217  	return nil, errors.New("unsupported")
   218  }
   219  func (e *hostEngine) SetRootResource(_ context.Context,
   220  	req *pulumirpc.SetRootResourceRequest) (*pulumirpc.SetRootResourceResponse, error) {
   221  	return nil, errors.New("unsupported")
   222  }
   223  
   224  type pluginHost struct {
   225  	pluginLoaders   []*ProviderLoader
   226  	languageRuntime plugin.LanguageRuntime
   227  	sink            diag.Sink
   228  	statusSink      diag.Sink
   229  
   230  	engine *hostEngine
   231  
   232  	providers []plugin.Provider
   233  	analyzers []plugin.Analyzer
   234  	plugins   map[interface{}]io.Closer
   235  	closed    bool
   236  	m         sync.Mutex
   237  }
   238  
   239  func NewPluginHost(sink, statusSink diag.Sink, languageRuntime plugin.LanguageRuntime,
   240  	pluginLoaders ...*ProviderLoader) plugin.Host {
   241  
   242  	engine := &hostEngine{
   243  		sink:       sink,
   244  		statusSink: statusSink,
   245  		stop:       make(chan bool),
   246  	}
   247  	handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
   248  		Cancel: engine.stop,
   249  		Init: func(srv *grpc.Server) error {
   250  			pulumirpc.RegisterEngineServer(srv, engine)
   251  			return nil
   252  		},
   253  		Options: rpcutil.OpenTracingServerInterceptorOptions(nil),
   254  	})
   255  	if err != nil {
   256  		panic(fmt.Errorf("could not start engine service: %v", err))
   257  	}
   258  	engine.address = fmt.Sprintf("127.0.0.1:%v", handle.Port)
   259  
   260  	return &pluginHost{
   261  		pluginLoaders:   pluginLoaders,
   262  		languageRuntime: languageRuntime,
   263  		sink:            sink,
   264  		statusSink:      statusSink,
   265  		engine:          engine,
   266  		plugins:         map[interface{}]io.Closer{},
   267  	}
   268  }
   269  
   270  func (host *pluginHost) isClosed() bool {
   271  	host.m.Lock()
   272  	defer host.m.Unlock()
   273  	return host.closed
   274  }
   275  
   276  func (host *pluginHost) plugin(kind workspace.PluginKind, name string, version *semver.Version,
   277  	opts interface{}) (interface{}, error) {
   278  
   279  	var best *PluginLoader
   280  	for _, l := range host.pluginLoaders {
   281  		if l.kind != kind || l.name != name {
   282  			continue
   283  		}
   284  
   285  		if version != nil {
   286  			if l.version.EQ(*version) {
   287  				best = l
   288  				break
   289  			}
   290  		} else if best == nil || l.version.GT(best.version) {
   291  			best = l
   292  		}
   293  	}
   294  	if best == nil {
   295  		return nil, nil
   296  	}
   297  
   298  	load := best.load
   299  	if load == nil {
   300  		load = func(opts interface{}) (interface{}, error) {
   301  			return best.loadWithHost(opts, host)
   302  		}
   303  	}
   304  
   305  	plug, err := load(opts)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	closer := nopCloser
   311  	if best.useGRPC {
   312  		plug, closer, err = wrapProviderWithGrpc(plug.(plugin.Provider))
   313  		if err != nil {
   314  			return nil, err
   315  		}
   316  	}
   317  
   318  	host.m.Lock()
   319  	defer host.m.Unlock()
   320  
   321  	switch kind {
   322  	case workspace.AnalyzerPlugin:
   323  		host.analyzers = append(host.analyzers, plug.(plugin.Analyzer))
   324  	case workspace.ResourcePlugin:
   325  		host.providers = append(host.providers, plug.(plugin.Provider))
   326  	}
   327  
   328  	host.plugins[plug] = closer
   329  	return plug, nil
   330  }
   331  
   332  func (host *pluginHost) Provider(pkg tokens.Package, version *semver.Version) (plugin.Provider, error) {
   333  	plug, err := host.plugin(workspace.ResourcePlugin, string(pkg), version, nil)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  	if plug == nil {
   338  		v := "nil"
   339  		if version != nil {
   340  			v = version.String()
   341  		}
   342  		return nil, fmt.Errorf("Could not find plugin for (%s, %s)", pkg.String(), v)
   343  	}
   344  	return plug.(plugin.Provider), nil
   345  }
   346  
   347  func (host *pluginHost) LanguageRuntime(runtime string) (plugin.LanguageRuntime, error) {
   348  	return host.languageRuntime, nil
   349  }
   350  
   351  func (host *pluginHost) SignalCancellation() error {
   352  	host.m.Lock()
   353  	defer host.m.Unlock()
   354  
   355  	var err error
   356  	for _, prov := range host.providers {
   357  		if pErr := prov.SignalCancellation(); pErr != nil {
   358  			err = pErr
   359  		}
   360  	}
   361  	return err
   362  }
   363  func (host *pluginHost) Close() error {
   364  	host.m.Lock()
   365  	defer host.m.Unlock()
   366  
   367  	var err error
   368  	for _, closer := range host.plugins {
   369  		if pErr := closer.Close(); pErr != nil {
   370  			err = pErr
   371  		}
   372  	}
   373  
   374  	go func() { host.engine.stop <- true }()
   375  	host.closed = true
   376  	return err
   377  }
   378  func (host *pluginHost) ServerAddr() string {
   379  	return host.engine.address
   380  }
   381  func (host *pluginHost) Log(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
   382  	if !host.isClosed() {
   383  		host.sink.Logf(sev, diag.StreamMessage(urn, msg, streamID))
   384  	}
   385  }
   386  func (host *pluginHost) LogStatus(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
   387  	if !host.isClosed() {
   388  		host.statusSink.Logf(sev, diag.StreamMessage(urn, msg, streamID))
   389  	}
   390  }
   391  func (host *pluginHost) Analyzer(nm tokens.QName) (plugin.Analyzer, error) {
   392  	return host.PolicyAnalyzer(nm, "", nil)
   393  }
   394  func (host *pluginHost) CloseProvider(provider plugin.Provider) error {
   395  	host.m.Lock()
   396  	defer host.m.Unlock()
   397  
   398  	delete(host.plugins, provider)
   399  	return nil
   400  }
   401  func (host *pluginHost) EnsurePlugins(plugins []workspace.PluginSpec, kinds plugin.Flags) error {
   402  	return nil
   403  }
   404  func (host *pluginHost) InstallPlugin(plugin workspace.PluginSpec) error {
   405  	return nil
   406  }
   407  func (host *pluginHost) ResolvePlugin(
   408  	kind workspace.PluginKind, name string, version *semver.Version) (*workspace.PluginInfo, error) {
   409  	plugins := make([]workspace.PluginInfo, 0, len(host.pluginLoaders))
   410  
   411  	for _, v := range host.pluginLoaders {
   412  		p := workspace.PluginInfo{
   413  			Kind:       v.kind,
   414  			Name:       v.name,
   415  			Path:       v.path,
   416  			Version:    &v.version,
   417  			SchemaPath: filepath.Join(v.path, name+"-"+v.version.String()+".json"),
   418  			// SchemaTime not set as caching is indefinite.
   419  		}
   420  		plugins = append(plugins, p)
   421  	}
   422  
   423  	var semverRange semver.Range
   424  	if version == nil {
   425  		semverRange = func(v semver.Version) bool {
   426  			return true
   427  		}
   428  	} else {
   429  		// Require an exact match:
   430  		semverRange = version.EQ
   431  	}
   432  
   433  	match, err := workspace.SelectCompatiblePlugin(plugins, kind, name, semverRange)
   434  	if err == nil {
   435  		return &match, nil
   436  	}
   437  	return nil, fmt.Errorf("could not locate a compatible plugin in deploytest, the makefile and "+
   438  		"& constructor of the plugin host must define the location of the schema: %w", err)
   439  }
   440  
   441  func (host *pluginHost) GetRequiredPlugins(info plugin.ProgInfo,
   442  	kinds plugin.Flags) ([]workspace.PluginSpec, error) {
   443  	return host.languageRuntime.GetRequiredPlugins(info)
   444  }
   445  
   446  func (host *pluginHost) GetProjectPlugins() []workspace.ProjectPlugin {
   447  	return nil
   448  }
   449  
   450  func (host *pluginHost) PolicyAnalyzer(name tokens.QName, path string,
   451  	opts *plugin.PolicyAnalyzerOptions) (plugin.Analyzer, error) {
   452  
   453  	plug, err := host.plugin(workspace.AnalyzerPlugin, string(name), nil, opts)
   454  	if err != nil || plug == nil {
   455  		return nil, err
   456  	}
   457  	return plug.(plugin.Analyzer), nil
   458  }
   459  
   460  func (host *pluginHost) ListAnalyzers() []plugin.Analyzer {
   461  	host.m.Lock()
   462  	defer host.m.Unlock()
   463  
   464  	return host.analyzers
   465  }