Add the ability to specify analyzers

This change adds the ability to specify analyzers in two ways:

1) By listing them in the project file, for example:

        analyzers:
            - acmecorp/security
            - acmecorp/gitflow

2) By explicitly listing them on the CLI, as a "one off":

        $ coco deploy <env> \
            --analyzer=acmecorp/security \
            --analyzer=acmecorp/gitflow

This closes out pulumi/coconut#119.
This commit is contained in:
joeduffy 2017-03-11 10:07:34 -08:00
parent b4b4d26844
commit 705880cb7f
9 changed files with 91 additions and 67 deletions

View file

@ -7,6 +7,7 @@ import (
)
func newDeployCmd() *cobra.Command {
var analyzers []string
var dryRun bool
var showConfig bool
var showReplaceSteps bool
@ -37,6 +38,7 @@ func newDeployCmd() *cobra.Command {
apply(cmd, info, applyOptions{
Delete: false,
DryRun: dryRun,
Analyzers: analyzers,
ShowConfig: showConfig,
ShowReplaceSteps: showReplaceSteps,
ShowUnchanged: showUnchanged,
@ -47,6 +49,9 @@ func newDeployCmd() *cobra.Command {
}),
}
cmd.PersistentFlags().StringSliceVar(
&analyzers, "analyzer", []string{},
"Run one or more analyzers as part of this deployment")
cmd.PersistentFlags().BoolVarP(
&dryRun, "dry-run", "n", false,
"Don't actually update resources; just print out the planned updates")

View file

@ -219,11 +219,11 @@ func verify(cmd *cobra.Command, args []string) bool {
}
// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan.
func plan(cmd *cobra.Command, info *envCmdInfo, delete bool) *planResult {
func plan(cmd *cobra.Command, info *envCmdInfo, opts applyOptions) *planResult {
// If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package.
var new resource.Snapshot
var result *compileResult
if !delete {
if !opts.Delete {
// First, compile; if that yields errors or an empty heap, exit early.
if result = compile(cmd, info.Args, info.Env.Config); result == nil || result.Heap == nil {
return nil
@ -241,9 +241,19 @@ func plan(cmd *cobra.Command, info *envCmdInfo, delete bool) *planResult {
}
}
// If there are any analyzers to run, queue them up.
var analyzers []tokens.QName
for _, a := range opts.Analyzers {
analyzers = append(analyzers, tokens.QName(a)) // from the command line.
}
if as := result.Pkg.Node.Analyzers; as != nil {
for _, a := range *as {
analyzers = append(analyzers, a) // from the project file.
}
}
// Generate a plan; this API handles all interesting cases (create, update, delete).
// TODO: take analyzers from the project and/or the command line.
plan, err := resource.NewPlan(info.Ctx, info.Old, new, nil)
plan, err := resource.NewPlan(info.Ctx, info.Old, new, analyzers)
if err != nil {
result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
return nil
@ -268,7 +278,7 @@ type planResult struct {
}
func apply(cmd *cobra.Command, info *envCmdInfo, opts applyOptions) {
if result := plan(cmd, info, opts.Delete); result != nil {
if result := plan(cmd, info, opts); result != nil {
// Now based on whether a dry run was specified, or not, either print or perform the planned operations.
if opts.DryRun {
// If no output file was requested, or "-", print to stdout; else write to that file.
@ -449,14 +459,15 @@ func saveEnv(env *resource.Env, snap resource.Snapshot, file string, existok boo
}
type applyOptions struct {
Create bool // true if we are creating resources.
Delete bool // true if we are deleting resources.
DryRun bool // true if we should just print the plan without performing it.
ShowConfig bool // true to show the configuration variables being used.
ShowReplaceSteps bool // true to show the replacement steps in the plan.
ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are.
Summary bool // true if we should only summarize resources and operations.
Output string // the place to store the output, if any.
Create bool // true if we are creating resources.
Delete bool // true if we are deleting resources.
DryRun bool // true if we should just print the plan without performing it.
Analyzers []string // an optional set of analyzers to run as part of this deployment.
ShowConfig bool // true to show the configuration variables being used.
ShowReplaceSteps bool // true to show the replacement steps in the plan.
ShowUnchanged bool // true to show the resources that aren't updated, in addition to those that are.
Summary bool // true if we should only summarize resources and operations.
Output string // the place to store the output, if any.
}
// applyProgress pretty-prints the plan application process as it goes.

View file

@ -29,7 +29,7 @@ func main() {
}
// The resource provider protocol requires that we now write out the port we have chosen to listen on.
fmt.Printf("%n\n", port)
fmt.Printf("%d\n", port)
// Finally, wait for the server to stop serving.
if err := <-done; err != nil {

View file

@ -18,5 +18,8 @@ var (
ErrorResourcePropertyValueInvalid = newError(2010, "Resource '%v's property '%v' value is invalid: %v")
ErrorAnalyzeFailure = newError(2011, "Analyzer '%v' reported an error: %v")
ErrorAnalyzeResourceFailure = newError(2012,
"Analyzer '%v' reported a resource error with '%v's property '%v': %v")
"Analyzer '%v' reported a resource error:\n"+
"\tResource: %v\n"+
"\tProperty: %v\n"+
"\tReason: %v")
)

View file

@ -18,6 +18,7 @@ type Package struct {
Website *string `json:"website,omitempty"` // an optional website for additional info.
License *string `json:"license,omitempty"` // an optional license governing this package's usage.
Analyzers *Analyzers `json:"analyzers,omitempty"` // any analyzers enabled for this project.
Dependencies *Dependencies `json:"dependencies,omitempty"` // all of the package dependencies.
Modules *ast.Modules `json:"modules,omitempty"` // a collection of top-level modules.
Aliases *ModuleAliases `json:"aliases,omitempty"` // an optional mapping of aliased module names.
@ -31,6 +32,9 @@ func (s *Package) Where() (*diag.Document, *diag.Location) {
return s.Doc, nil
}
// Analyzers is a list of analyzers to run on this project.
type Analyzers []tokens.QName
// Dependencies maps dependency names to the full URL, including version, of the package.
type Dependencies map[tokens.PackageName]PackageURLString

View file

@ -20,7 +20,7 @@ type analyzer struct {
ctx *Context
name tokens.QName
plug *plugin
client cocorpc.ResourceAnalyzerClient
client cocorpc.AnalyzerClient
}
// NewAnalyzer binds to a given analyzer's plugin by name and creates a gRPC connection to it. If the associated plugin
@ -39,7 +39,7 @@ func NewAnalyzer(ctx *Context, name tokens.QName) (Analyzer, error) {
ctx: ctx,
name: name,
plug: plug,
client: cocorpc.NewResourceAnalyzerClient(plug.Conn),
client: cocorpc.NewAnalyzerClient(plug.Conn),
}, nil
}
@ -54,7 +54,7 @@ func (a *analyzer) Analyze(url pack.PackageURL) ([]AnalyzeFailure, error) {
resp, err := a.client.Analyze(a.ctx.Request(), req)
if err != nil {
glog.V(7).Infof("analyzer[%v].Analyze(url=%v) failed: err=%v", a.name, url)
glog.V(7).Infof("analyzer[%v].Analyze(url=%v) failed: err=%v", a.name, url, err)
return nil, err
}
@ -79,7 +79,7 @@ func (a *analyzer) AnalyzeResource(t tokens.Type, props PropertyMap) ([]AnalyzeR
resp, err := a.client.AnalyzeResource(a.ctx.Request(), req)
if err != nil {
glog.V(7).Infof("analyzer[%v].AnalyzeResource(t=%v,...) failed: err=%v", a.name, t)
glog.V(7).Infof("analyzer[%v].AnalyzeResource(t=%v,...) failed: err=%v", a.name, t, err)
return nil, err
}

View file

@ -33,6 +33,7 @@ type urnIDMap map[URN]ID
func NewContext(d diag.Sink) *Context {
return &Context{
Diag: d,
Analyzers: make(map[tokens.QName]Analyzer),
Providers: make(map[tokens.Package]Provider),
ObjRes: make(objectResourceMap),
ObjURN: make(objectURNMap),

View file

@ -183,101 +183,101 @@ var _ grpc.ClientConn
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for ResourceAnalyzer service
// Client API for Analyzer service
type ResourceAnalyzerClient interface {
type AnalyzerClient interface {
// Analyze analyzes an entire project/stack/snapshot, and returns any errors that it finds.
Analyze(ctx context.Context, in *AnalyzeRequest, opts ...grpc.CallOption) (*AnalyzeResponse, error)
// AnalyzeResource analyzes a single resource object, and returns any errors that it finds.
AnalyzeResource(ctx context.Context, in *AnalyzeResourceRequest, opts ...grpc.CallOption) (*AnalyzeResourceResponse, error)
}
type resourceAnalyzerClient struct {
type analyzerClient struct {
cc *grpc.ClientConn
}
func NewResourceAnalyzerClient(cc *grpc.ClientConn) ResourceAnalyzerClient {
return &resourceAnalyzerClient{cc}
func NewAnalyzerClient(cc *grpc.ClientConn) AnalyzerClient {
return &analyzerClient{cc}
}
func (c *resourceAnalyzerClient) Analyze(ctx context.Context, in *AnalyzeRequest, opts ...grpc.CallOption) (*AnalyzeResponse, error) {
func (c *analyzerClient) Analyze(ctx context.Context, in *AnalyzeRequest, opts ...grpc.CallOption) (*AnalyzeResponse, error) {
out := new(AnalyzeResponse)
err := grpc.Invoke(ctx, "/cocorpc.ResourceAnalyzer/Analyze", in, out, c.cc, opts...)
err := grpc.Invoke(ctx, "/cocorpc.Analyzer/Analyze", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resourceAnalyzerClient) AnalyzeResource(ctx context.Context, in *AnalyzeResourceRequest, opts ...grpc.CallOption) (*AnalyzeResourceResponse, error) {
func (c *analyzerClient) AnalyzeResource(ctx context.Context, in *AnalyzeResourceRequest, opts ...grpc.CallOption) (*AnalyzeResourceResponse, error) {
out := new(AnalyzeResourceResponse)
err := grpc.Invoke(ctx, "/cocorpc.ResourceAnalyzer/AnalyzeResource", in, out, c.cc, opts...)
err := grpc.Invoke(ctx, "/cocorpc.Analyzer/AnalyzeResource", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for ResourceAnalyzer service
// Server API for Analyzer service
type ResourceAnalyzerServer interface {
type AnalyzerServer interface {
// Analyze analyzes an entire project/stack/snapshot, and returns any errors that it finds.
Analyze(context.Context, *AnalyzeRequest) (*AnalyzeResponse, error)
// AnalyzeResource analyzes a single resource object, and returns any errors that it finds.
AnalyzeResource(context.Context, *AnalyzeResourceRequest) (*AnalyzeResourceResponse, error)
}
func RegisterResourceAnalyzerServer(s *grpc.Server, srv ResourceAnalyzerServer) {
s.RegisterService(&_ResourceAnalyzer_serviceDesc, srv)
func RegisterAnalyzerServer(s *grpc.Server, srv AnalyzerServer) {
s.RegisterService(&_Analyzer_serviceDesc, srv)
}
func _ResourceAnalyzer_Analyze_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
func _Analyzer_Analyze_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AnalyzeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceAnalyzerServer).Analyze(ctx, in)
return srv.(AnalyzerServer).Analyze(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/cocorpc.ResourceAnalyzer/Analyze",
FullMethod: "/cocorpc.Analyzer/Analyze",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceAnalyzerServer).Analyze(ctx, req.(*AnalyzeRequest))
return srv.(AnalyzerServer).Analyze(ctx, req.(*AnalyzeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ResourceAnalyzer_AnalyzeResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
func _Analyzer_AnalyzeResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AnalyzeResourceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceAnalyzerServer).AnalyzeResource(ctx, in)
return srv.(AnalyzerServer).AnalyzeResource(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/cocorpc.ResourceAnalyzer/AnalyzeResource",
FullMethod: "/cocorpc.Analyzer/AnalyzeResource",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceAnalyzerServer).AnalyzeResource(ctx, req.(*AnalyzeResourceRequest))
return srv.(AnalyzerServer).AnalyzeResource(ctx, req.(*AnalyzeResourceRequest))
}
return interceptor(ctx, in, info, handler)
}
var _ResourceAnalyzer_serviceDesc = grpc.ServiceDesc{
ServiceName: "cocorpc.ResourceAnalyzer",
HandlerType: (*ResourceAnalyzerServer)(nil),
var _Analyzer_serviceDesc = grpc.ServiceDesc{
ServiceName: "cocorpc.Analyzer",
HandlerType: (*AnalyzerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Analyze",
Handler: _ResourceAnalyzer_Analyze_Handler,
Handler: _Analyzer_Analyze_Handler,
},
{
MethodName: "AnalyzeResource",
Handler: _ResourceAnalyzer_AnalyzeResource_Handler,
Handler: _Analyzer_AnalyzeResource_Handler,
},
},
Streams: []grpc.StreamDesc{},
@ -287,24 +287,24 @@ var _ResourceAnalyzer_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("analyzer.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 303 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x91, 0x31, 0x6b, 0xc3, 0x30,
0x10, 0x85, 0xeb, 0xa4, 0x24, 0xe9, 0x15, 0xd2, 0xa0, 0x21, 0x36, 0xa6, 0x50, 0xa3, 0xc9, 0x93,
0x03, 0xc9, 0xd0, 0xa1, 0x50, 0xe8, 0x92, 0xa9, 0x93, 0x0b, 0xd9, 0x1d, 0x73, 0x31, 0xa1, 0xc6,
0x52, 0x25, 0x79, 0x70, 0x7f, 0x52, 0x7f, 0x65, 0xa9, 0x7c, 0x72, 0x4d, 0xd5, 0x6c, 0x27, 0xeb,
0xe9, 0xbd, 0xf7, 0x9d, 0x61, 0x59, 0x34, 0x45, 0xdd, 0x7d, 0xa2, 0xca, 0xa4, 0x12, 0x46, 0xb0,
0x79, 0x29, 0x4a, 0xa1, 0x64, 0x19, 0xdf, 0x57, 0x42, 0x54, 0x35, 0x6e, 0xec, 0xe7, 0x63, 0x7b,
0xda, 0x68, 0xa3, 0xda, 0xd2, 0xf4, 0x32, 0xce, 0x61, 0xf9, 0xd2, 0x3f, 0xcc, 0xf1, 0xa3, 0x45,
0x6d, 0xd8, 0x0a, 0xa6, 0xf2, 0xbd, 0x8a, 0x82, 0x24, 0x48, 0x6f, 0xf2, 0x9f, 0x91, 0xef, 0xe1,
0x6e, 0xd0, 0x68, 0x29, 0x1a, 0x8d, 0x6c, 0x07, 0x8b, 0x53, 0x71, 0xae, 0x5b, 0x85, 0x3a, 0x0a,
0x92, 0x69, 0x7a, 0xbb, 0x0d, 0x33, 0x0a, 0xcc, 0x48, 0xbb, 0xef, 0xef, 0xf3, 0x41, 0xc8, 0xd3,
0x21, 0x8b, 0xee, 0xd8, 0x1a, 0x66, 0x0a, 0x0b, 0x2d, 0x1a, 0x8a, 0xa3, 0x13, 0x47, 0x58, 0xff,
0x26, 0x8a, 0x56, 0x95, 0x43, 0x3b, 0x06, 0xd7, 0xa6, 0x93, 0x48, 0x7a, 0x3b, 0xb3, 0x47, 0x00,
0xa9, 0x84, 0x44, 0x65, 0xce, 0xa8, 0xa3, 0x49, 0x12, 0xd8, 0x3a, 0x3d, 0x76, 0xe6, 0xb0, 0xb3,
0x37, 0x8b, 0x9d, 0x8f, 0xa4, 0xfc, 0x00, 0xa1, 0x17, 0x43, 0x80, 0x4f, 0x1e, 0xe0, 0xc3, 0x5f,
0x40, 0xf7, 0xc6, 0x07, 0x7d, 0xf5, 0xea, 0x3b, 0xe0, 0x18, 0x16, 0x94, 0xdf, 0x11, 0xc2, 0x70,
0x1e, 0x2d, 0x63, 0x32, 0x5e, 0xc6, 0xf6, 0x2b, 0x80, 0x95, 0xf3, 0x21, 0x5b, 0xc5, 0x9e, 0x61,
0x4e, 0x33, 0x0b, 0xfd, 0x62, 0x76, 0x57, 0x71, 0xf4, 0x4f, 0x63, 0x4b, 0xc7, 0xaf, 0xd8, 0x61,
0xfc, 0x4f, 0xad, 0x35, 0xbb, 0x08, 0xe8, 0xfc, 0x92, 0xcb, 0x02, 0xe7, 0x7b, 0x9c, 0xd9, 0x7d,
0xef, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0xa7, 0x48, 0x96, 0x9a, 0x8f, 0x02, 0x00, 0x00,
// 302 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x91, 0x41, 0x6b, 0xbb, 0x40,
0x10, 0xc5, 0xff, 0x26, 0x7f, 0x12, 0x3b, 0x85, 0xb4, 0xec, 0x21, 0x8a, 0x14, 0x2a, 0x7b, 0xf2,
0x64, 0x20, 0x39, 0xf4, 0x50, 0x28, 0xf4, 0x92, 0x53, 0x4f, 0x16, 0x72, 0x37, 0x32, 0x91, 0x50,
0x71, 0xb7, 0xb3, 0xeb, 0xc1, 0x7e, 0x9c, 0x7e, 0xd2, 0xd2, 0x75, 0xb4, 0x52, 0x9b, 0xdb, 0xac,
0xfb, 0x76, 0xde, 0xfb, 0x3d, 0x61, 0x95, 0xd7, 0x79, 0xd5, 0x7e, 0x20, 0xa5, 0x9a, 0x94, 0x55,
0x62, 0x59, 0xa8, 0x42, 0x91, 0x2e, 0xa2, 0xbb, 0x52, 0xa9, 0xb2, 0xc2, 0x8d, 0xfb, 0x7c, 0x6c,
0x4e, 0x1b, 0x63, 0xa9, 0x29, 0x6c, 0x27, 0x93, 0x12, 0x56, 0xcf, 0xdd, 0xc3, 0x0c, 0xdf, 0x1b,
0x34, 0x56, 0xdc, 0xc2, 0x5c, 0xbf, 0x95, 0xa1, 0x17, 0x7b, 0xc9, 0x55, 0xf6, 0x3d, 0xca, 0x3d,
0xdc, 0x0c, 0x1a, 0xa3, 0x55, 0x6d, 0x50, 0xec, 0xc0, 0x3f, 0xe5, 0xe7, 0xaa, 0x21, 0x34, 0xa1,
0x17, 0xcf, 0x93, 0xeb, 0x6d, 0x90, 0xb2, 0x61, 0xca, 0xda, 0x7d, 0x77, 0x9f, 0x0d, 0x42, 0x99,
0x0c, 0x5e, 0x7c, 0x27, 0xd6, 0xb0, 0x20, 0xcc, 0x8d, 0xaa, 0xd9, 0x8e, 0x4f, 0x12, 0x61, 0xfd,
0xe3, 0xa8, 0x1a, 0x2a, 0x86, 0x74, 0x02, 0xfe, 0xdb, 0x56, 0x23, 0xeb, 0xdd, 0x2c, 0x1e, 0x00,
0x34, 0x29, 0x8d, 0x64, 0xcf, 0x68, 0xc2, 0x59, 0xec, 0xb9, 0x38, 0x1d, 0x76, 0xda, 0x63, 0xa7,
0xaf, 0x0e, 0x3b, 0x1b, 0x49, 0xe5, 0x01, 0x82, 0x89, 0x0d, 0x03, 0x3e, 0x4e, 0x00, 0xef, 0x7f,
0x03, 0xf6, 0x6f, 0xa6, 0xa0, 0x2f, 0x93, 0xf8, 0x3d, 0x70, 0x04, 0x3e, 0xfb, 0xb7, 0x8c, 0x30,
0x9c, 0x47, 0x65, 0xcc, 0xc6, 0x65, 0x6c, 0x3f, 0x3d, 0xf0, 0x79, 0x1d, 0x89, 0x27, 0x58, 0xf2,
0x2c, 0x82, 0x69, 0x20, 0xd7, 0x51, 0x14, 0xfe, 0x91, 0xd4, 0x51, 0xc9, 0x7f, 0xe2, 0x30, 0xfe,
0x97, 0x2e, 0x9a, 0xb8, 0x08, 0xd6, 0xef, 0x8b, 0x2f, 0x0b, 0xfa, 0xbd, 0xc7, 0x85, 0xeb, 0x79,
0xf7, 0x15, 0x00, 0x00, 0xff, 0xff, 0x41, 0x89, 0x3f, 0x7e, 0x87, 0x02, 0x00, 0x00,
}

View file

@ -6,9 +6,9 @@ import "google/protobuf/struct.proto";
package cocorpc;
// ResourceAnalyzer is a pluggable service that checks entire projects/stacks/snapshots, and/or individual resources,
// Analyzer is a pluggable service that checks entire projects/stacks/snapshots, and/or individual resources,
// for arbitrary issues. These might be style, policy, correctness, security, or performance related.
service ResourceAnalyzer {
service Analyzer {
// Analyze analyzes an entire project/stack/snapshot, and returns any errors that it finds.
rpc Analyze(AnalyzeRequest) returns (AnalyzeResponse) {}
// AnalyzeResource analyzes a single resource object, and returns any errors that it finds.