pulumi/pkg/resource/plugin/analyzer_plugin.go
Joe Duffy 16ade183d8
Add a manifest to checkpoint files (#630)
This change adds a new manifest section to the checkpoint files.
The existing time moves into it, and we add to it the version of
the Pulumi CLI that created it, along with the names, types, and
versions of all plugins used to generate the file.  There is a
magic cookie that we also use during verification.

This is to help keep us sane when debugging problems "in the wild,"
and I'm sure we will add more to it over time (checksum, etc).

For example, after an up, you can now see this in `pulumi stack`:

```
Current stack is demo:
    Last updated at 2017-12-01 13:48:49.815740523 -0800 PST
    Pulumi version v0.8.3-79-g1ab99ad
    Plugin pulumi-provider-aws [resource] version v0.8.3-22-g4363e77
    Plugin pulumi-langhost-nodejs [language] version v0.8.3-79-g77bb6b6
    Checkpoint file is /Users/joeduffy/dev/code/src/github.com/pulumi/pulumi-aws/.pulumi/stacks/webserver/demo.json
```

This addresses pulumi/pulumi#628.
2017-12-01 13:50:32 -08:00

95 lines
2.9 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package plugin
import (
"fmt"
"strings"
"github.com/golang/glog"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/tokens"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
const AnalyzerPluginPrefix = "pulumi-analyzer-"
// analyzer reflects an analyzer plugin, loaded dynamically for a single suite of checks.
type analyzer struct {
ctx *Context
name tokens.QName
plug *plugin
client pulumirpc.AnalyzerClient
}
// NewAnalyzer binds to a given analyzer's plugin by name and creates a gRPC connection to it. If the associated plugin
// could not be found by name on the PATH, or an error occurs while creating the child process, an error is returned.
func NewAnalyzer(host Host, ctx *Context, name tokens.QName) (Analyzer, error) {
// Go ahead and attempt to load the plugin from the PATH.
srvexe := AnalyzerPluginPrefix + strings.Replace(string(name), tokens.QNameDelimiter, "_", -1)
plug, err := newPlugin(ctx, srvexe, fmt.Sprintf("%v (analyzer)", name), []string{host.ServerAddr()})
if err != nil {
return nil, err
} else if plug == nil {
return nil, nil
}
return &analyzer{
ctx: ctx,
name: name,
plug: plug,
client: pulumirpc.NewAnalyzerClient(plug.Conn),
}, nil
}
func (a *analyzer) Name() tokens.QName { return a.name }
// Analyze analyzes a single resource object, and returns any errors that it finds.
func (a *analyzer) Analyze(t tokens.Type, props resource.PropertyMap) ([]AnalyzeFailure, error) {
glog.V(7).Infof("analyzer[%v].Analyze(t=%v,#props=%v) executing", a.name, t, len(props))
mprops, err := MarshalProperties(props, MarshalOptions{})
if err != nil {
return nil, err
}
resp, err := a.client.Analyze(a.ctx.Request(), &pulumirpc.AnalyzeRequest{
Type: string(t),
Properties: mprops,
})
if err != nil {
glog.V(7).Infof("analyzer[%v].Analyze(t=%v,...) failed: err=%v", a.name, t, err)
return nil, err
}
var failures []AnalyzeFailure
for _, failure := range resp.GetFailures() {
failures = append(failures, AnalyzeFailure{
Property: resource.PropertyKey(failure.Property),
Reason: failure.Reason,
})
}
glog.V(7).Infof("analyzer[%v].Analyze(t=%v,...) success: failures=#%v", a.name, t, len(failures))
return failures, nil
}
// GetPluginInfo returns this plugin's information.
func (a *analyzer) GetPluginInfo() (Info, error) {
glog.V(7).Infof("analyzer[%v].GetPluginInfo() executing", a.name)
resp, err := a.client.GetPluginInfo(a.ctx.Request(), &pbempty.Empty{})
if err != nil {
glog.V(7).Infof("analyzer[%v].GetPluginInfo() failed: err=%v", a.name, err)
return Info{}, err
}
return Info{
Name: a.plug.Bin,
Type: AnalyzerType,
Version: resp.Version,
}, nil
}
// Close tears down the underlying plugin RPC connection and process.
func (a *analyzer) Close() error {
return a.plug.Close()
}