pulumi/pkg/resource/plugin/langruntime_plugin.go

187 lines
6.4 KiB
Go
Raw Normal View History

2018-05-22 21:43:36 +02:00
// Copyright 2016-2018, 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 (
"strings"
"github.com/blang/semver"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/logging"
"github.com/pulumi/pulumi/pkg/util/rpcutil/rpcerror"
"github.com/pulumi/pulumi/pkg/workspace"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
// langhost reflects a language host plugin, loaded dynamically for a single language/runtime pair.
type langhost struct {
ctx *Context
runtime string
plug *plugin
client pulumirpc.LanguageRuntimeClient
}
// NewLanguageRuntime binds to a language's runtime plugin and then creates a gRPC connection to it. If the
// plugin could not be found, or an error occurs while creating the child process, an error is returned.
func NewLanguageRuntime(host Host, ctx *Context, runtime string) (LanguageRuntime, error) {
// Load the plugin's path by using the standard workspace logic.
_, path, err := workspace.GetPluginPath(
workspace.LanguagePlugin, strings.Replace(runtime, tokens.QNameDelimiter, "_", -1), nil)
if err != nil {
return nil, err
} else if path == "" {
return nil, NewMissingError(workspace.PluginInfo{
Kind: workspace.LanguagePlugin,
Name: runtime,
})
}
plug, err := newPlugin(ctx, path, runtime, []string{host.ServerAddr()})
if err != nil {
return nil, err
}
contract.Assertf(plug != nil, "unexpected nil language plugin for %s", runtime)
return &langhost{
ctx: ctx,
runtime: runtime,
plug: plug,
client: pulumirpc.NewLanguageRuntimeClient(plug.Conn),
}, nil
}
func (h *langhost) Runtime() string { return h.runtime }
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
func (h *langhost) GetRequiredPlugins(info ProgInfo) ([]workspace.PluginInfo, error) {
proj := string(info.Proj.Name)
logging.V(7).Infof("langhost[%v].GetRequiredPlugins(proj=%s,pwd=%s,program=%s) executing",
h.runtime, proj, info.Pwd, info.Program)
resp, err := h.client.GetRequiredPlugins(h.ctx.Request(), &pulumirpc.GetRequiredPluginsRequest{
Project: proj,
Pwd: info.Pwd,
Program: info.Program,
})
if err != nil {
rpcError := rpcerror.Convert(err)
logging.V(7).Infof("langhost[%v].GetRequiredPlugins(proj=%s,pwd=%s,program=%s) failed: err=%v",
h.runtime, proj, info.Pwd, info.Program, rpcError)
// It's possible this is just an older language host, prior to the emergence of the GetRequiredPlugins
// method. In such cases, we will silently error (with the above log left behind).
if rpcError.Code() == codes.Unimplemented {
return nil, nil
}
return nil, rpcError
}
var results []workspace.PluginInfo
for _, info := range resp.GetPlugins() {
var version *semver.Version
if v := info.GetVersion(); v != "" {
sv, err := semver.ParseTolerant(v)
if err != nil {
return nil, errors.Wrapf(err, "illegal semver returned by language host: %s@%s", info.GetName(), v)
}
version = &sv
}
if !workspace.IsPluginKind(info.GetKind()) {
return nil, errors.Errorf("unrecognized plugin kind: %s", info.GetKind())
}
results = append(results, workspace.PluginInfo{
Name: info.GetName(),
Kind: workspace.PluginKind(info.GetKind()),
Version: version,
})
}
logging.V(7).Infof("langhost[%v].GetRequiredPlugins(proj=%s,pwd=%s,program=%s) success: #versions=%d",
h.runtime, proj, info.Pwd, info.Program, len(results))
return results, nil
}
// Run executes a program in the language runtime for planning or deployment purposes. If info.DryRun is true,
// the code must not assume that side-effects or final values resulting from resource deployments are actually
// available. If it is false, on the other hand, a real deployment is occurring and it may safely depend on these.
func (h *langhost) Run(info RunInfo) (string, error) {
logging.V(7).Infof("langhost[%v].Run(pwd=%v,program=%v,#args=%v,proj=%s,stack=%v,#config=%v,dryrun=%v) executing",
2017-11-19 19:16:47 +01:00
h.runtime, info.Pwd, info.Program, len(info.Args), info.Project, info.Stack, len(info.Config), info.DryRun)
config := make(map[string]string)
for k, v := range info.Config {
config[k.String()] = v
}
resp, err := h.client.Run(h.ctx.Request(), &pulumirpc.RunRequest{
MonitorAddress: info.MonitorAddress,
Pwd: info.Pwd,
Program: info.Program,
Args: info.Args,
Project: info.Project,
Stack: info.Stack,
Config: config,
DryRun: info.DryRun,
Parallel: int32(info.Parallel),
})
if err != nil {
rpcError := rpcerror.Convert(err)
logging.V(7).Infof("langhost[%v].Run(pwd=%v,program=%v,...,dryrun=%v) failed: err=%v",
h.runtime, info.Pwd, info.Program, info.DryRun, rpcError)
return "", rpcError
}
progerr := resp.GetError()
logging.V(7).Infof("langhost[%v].RunPlan(pwd=%v,program=%v,...,dryrun=%v) success: progerr=%v",
h.runtime, info.Pwd, info.Program, info.DryRun, progerr)
return progerr, nil
}
// GetPluginInfo returns this plugin's information.
func (h *langhost) GetPluginInfo() (workspace.PluginInfo, error) {
logging.V(7).Infof("langhost[%v].GetPluginInfo() executing", h.runtime)
resp, err := h.client.GetPluginInfo(h.ctx.Request(), &pbempty.Empty{})
if err != nil {
rpcError := rpcerror.Convert(err)
logging.V(7).Infof("langhost[%v].GetPluginInfo() failed: err=%v", h.runtime, rpcError)
return workspace.PluginInfo{}, rpcError
}
var version *semver.Version
if v := resp.Version; v != "" {
sv, err := semver.ParseTolerant(v)
if err != nil {
return workspace.PluginInfo{}, err
}
version = &sv
}
return workspace.PluginInfo{
Name: h.runtime,
Path: h.plug.Bin,
Kind: workspace.LanguagePlugin,
Version: version,
}, nil
}
// Close tears down the underlying plugin RPC connection and process.
func (h *langhost) Close() error {
return h.plug.Close()
}