2023-03-04 09:13:30 +00:00
|
|
|
// Copyright 2016-2023, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
|
|
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
2023-06-05 16:09:31 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2023-03-04 09:13:30 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
|
|
|
2024-04-25 17:30:30 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
2023-03-04 09:13:30 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil/rpcerror"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
|
|
|
)
|
|
|
|
|
2023-03-24 14:52:32 +00:00
|
|
|
// converter reflects a converter plugin, loaded dynamically from another process over gRPC.
|
2023-03-04 09:13:30 +00:00
|
|
|
type converter struct {
|
|
|
|
name string
|
|
|
|
plug *plugin // the actual plugin process wrapper.
|
|
|
|
clientRaw pulumirpc.ConverterClient // the raw provider client; usually unsafe to use directly.
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewConverter(ctx *Context, name string, version *semver.Version) (Converter, error) {
|
|
|
|
prefix := fmt.Sprintf("%v (converter)", name)
|
|
|
|
|
|
|
|
// Load the plugin's path by using the standard workspace logic.
|
2024-04-25 17:30:30 +00:00
|
|
|
path, err := workspace.GetPluginPath(ctx.Diag, apitype.ConverterPlugin, name, version, ctx.Host.GetProjectPlugins())
|
2023-03-04 09:13:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
contract.Assertf(path != "", "unexpected empty path for plugin %s", name)
|
|
|
|
|
2024-12-04 13:03:11 +00:00
|
|
|
plug, _, runTrace, err := newPlugin(ctx, ctx.Pwd, path, prefix,
|
Add `Handshake` to the provider protocol (#17819)
Fixes https://github.com/pulumi/pulumi/issues/16876
The pulumi provider protocol gRPC always starts by calling CheckConfig
and then Configure. The problem is that CheckConfig accepts a property
bag, which could contain secrets or outputs or resource references, etc.
However, the engine doesn't know if the provider supports these items
(and vice versa) until Configure is called, since Configure is the call
where the engine and the provider agree on which parts of the protocol
they support.
This introduces a Handshake component to the provider protocol that
establishes which version of the protocol the provider and engine
support. This also adds the plugins root and program directories to that
handshake request allowing a provider to know where it was started up
from. This _also_ replaces `Provider.Attach` as the handshake request
includes the engine address to connect to.
Tasting notes:
* Add `Handshake` with request/response to the protocol. `Handshake`
starts as a high watermark for accepts secrets, accepts resources, etc
but includes the plugins root and program directories (if possible), as
well as the engine address. This pretty much replaces the need for
`Attach`.
* Modify `dialPlugin` (where the engine establishes gRPC connections to
plugins) to take a callback that is used to initialise the connection
* For non-provider plugins, pass `testConnection`, which captures the
logic we have today -- call a dummy gRPC method and observed not
implemented error to confirm connection is live
* For provider plugins, pass `handshake`, which sends a `Handshake` and
captures the response
* All providers thus handshake at boot, as opposed to `Configure`, which
a. happens later and b. is asynchronous
* Modify provider implementation to track a `protocol`; move
`acceptSecrets` and company from `configSource` to there
* If `Handshake` is implemented, populate `protocol` on `dialPlugin`. If
not, fallback to populating in `Configure`
* Invariant: `Configure` implies `protocol`
* The rest is largely plumbing
* We can add similar Handshake methods for the other plugin types as
well
---------
Co-authored-by: Fraser Waters <fraser@pulumi.com>
2024-11-26 17:35:47 +00:00
|
|
|
apitype.ConverterPlugin, []string{}, os.Environ(),
|
|
|
|
testConnection, converterPluginDialOptions(ctx, name, ""))
|
2023-03-04 09:13:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-12-04 13:03:11 +00:00
|
|
|
runTrace.init(ctx)
|
2023-03-04 09:13:30 +00:00
|
|
|
|
|
|
|
contract.Assertf(plug != nil, "unexpected nil converter plugin for %s", name)
|
|
|
|
|
|
|
|
c := &converter{
|
|
|
|
name: name,
|
|
|
|
plug: plug,
|
|
|
|
clientRaw: pulumirpc.NewConverterClient(plug.Conn),
|
|
|
|
}
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func converterPluginDialOptions(ctx *Context, name string, path string) []grpc.DialOption {
|
|
|
|
dialOpts := append(
|
|
|
|
rpcutil.OpenTracingInterceptorDialOptions(otgrpc.SpanDecorator(decorateProviderSpans)),
|
|
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
|
|
rpcutil.GrpcChannelOptions(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if ctx.DialOptions != nil {
|
|
|
|
metadata := map[string]interface{}{
|
|
|
|
"mode": "client",
|
|
|
|
"kind": "converter",
|
|
|
|
}
|
|
|
|
if name != "" {
|
|
|
|
metadata["name"] = name
|
|
|
|
}
|
|
|
|
if path != "" {
|
|
|
|
metadata["path"] = path
|
|
|
|
}
|
|
|
|
dialOpts = append(dialOpts, ctx.DialOptions(metadata)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dialOpts
|
|
|
|
}
|
|
|
|
|
|
|
|
// label returns a base label for tracing functions.
|
|
|
|
func (c *converter) label() string {
|
|
|
|
return fmt.Sprintf("Converter[%s, %p]", c.name, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *converter) Close() error {
|
|
|
|
if c.plug == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return c.plug.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *converter) ConvertState(ctx context.Context, req *ConvertStateRequest) (*ConvertStateResponse, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := c.label() + ".ConvertState"
|
2023-03-04 09:13:30 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
|
|
|
|
2023-03-24 14:52:32 +00:00
|
|
|
resp, err := c.clientRaw.ConvertState(ctx, &pulumirpc.ConvertStateRequest{
|
2023-07-27 10:34:30 +00:00
|
|
|
MapperTarget: req.MapperTarget,
|
2023-09-01 18:07:49 +00:00
|
|
|
Args: req.Args,
|
2023-03-24 14:52:32 +00:00
|
|
|
})
|
2023-03-04 09:13:30 +00:00
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(8).Infof("%s converter received rpc error `%s`: `%s`", label, rpcError.Code(), rpcError.Message())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resources := make([]ResourceImport, len(resp.Resources))
|
|
|
|
for i, resource := range resp.Resources {
|
|
|
|
resources[i] = ResourceImport{
|
|
|
|
Type: resource.Type,
|
|
|
|
Name: resource.Name,
|
|
|
|
ID: resource.Id,
|
|
|
|
Version: resource.Version,
|
|
|
|
PluginDownloadURL: resource.PluginDownloadURL,
|
2024-01-24 17:15:30 +00:00
|
|
|
LogicalName: resource.LogicalName,
|
|
|
|
IsRemote: resource.IsRemote,
|
|
|
|
IsComponent: resource.IsComponent,
|
2023-03-04 09:13:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-17 08:08:39 +00:00
|
|
|
// Translate the rpc diagnostics into hcl.Diagnostics.
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
for _, rpcDiag := range resp.Diagnostics {
|
|
|
|
diags = append(diags, RPCDiagnosticToHclDiagnostic(rpcDiag))
|
|
|
|
}
|
|
|
|
|
2023-03-04 09:13:30 +00:00
|
|
|
logging.V(7).Infof("%s success", label)
|
2023-10-17 08:08:39 +00:00
|
|
|
return &ConvertStateResponse{
|
|
|
|
Resources: resources,
|
|
|
|
Diagnostics: diags,
|
|
|
|
}, nil
|
2023-03-04 09:13:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *converter) ConvertProgram(ctx context.Context, req *ConvertProgramRequest) (*ConvertProgramResponse, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := c.label() + ".ConvertProgram"
|
2023-03-04 09:13:30 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
|
|
|
|
2023-06-05 16:09:31 +00:00
|
|
|
resp, err := c.clientRaw.ConvertProgram(ctx, &pulumirpc.ConvertProgramRequest{
|
2023-03-04 09:13:30 +00:00
|
|
|
SourceDirectory: req.SourceDirectory,
|
|
|
|
TargetDirectory: req.TargetDirectory,
|
2023-07-27 10:34:30 +00:00
|
|
|
MapperTarget: req.MapperTarget,
|
2023-08-09 19:02:40 +00:00
|
|
|
LoaderTarget: req.LoaderTarget,
|
2023-09-18 11:01:13 +00:00
|
|
|
Args: req.Args,
|
2023-03-04 09:13:30 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(8).Infof("%s converter received rpc error `%s`: `%s`", label, rpcError.Code(), rpcError.Message())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-06-05 16:09:31 +00:00
|
|
|
// Translate the rpc diagnostics into hcl.Diagnostics.
|
2023-06-08 16:33:48 +00:00
|
|
|
var diags hcl.Diagnostics
|
2023-06-05 16:09:31 +00:00
|
|
|
for _, rpcDiag := range resp.Diagnostics {
|
|
|
|
diags = append(diags, RPCDiagnosticToHclDiagnostic(rpcDiag))
|
|
|
|
}
|
|
|
|
|
2023-03-04 09:13:30 +00:00
|
|
|
logging.V(7).Infof("%s success", label)
|
2023-06-05 16:09:31 +00:00
|
|
|
return &ConvertProgramResponse{
|
|
|
|
Diagnostics: diags,
|
|
|
|
}, nil
|
2023-03-04 09:13:30 +00:00
|
|
|
}
|