events: autogenerate fieldpath filters

To ensure consistent fieldpath matching for events, we generate the
fieldpath matching using protobuf definitions. This is done through a
plugin called "fieldpath" that defines a `Field` method for each type
with the plugin enabled. Generated code handles top-level envelope
fields, as well as deferred serialization for matching any types.

In practice, this means that we can cheaply match events on `topic` and
`namespace`. If we want to match on attributes within the event, we can
use the `event` prefix to address these fields. For example, the
following will match all envelopes that have a field named
`container_id` that has the value `testing`:

```
ctr events "event.container_id==testing"
```

The above will decode the underlying event and check that particular
field. Accordingly, if only `topic` or `namespace` is used, the event
will not be decoded and only match on the envelope.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day
2017-07-31 19:14:39 -07:00
parent bb7b41ad7b
commit c857ba2d0b
24 changed files with 1005 additions and 215 deletions

1
protobuf/plugin/doc.go Normal file
View File

@@ -0,0 +1 @@
package plugin

View File

@@ -0,0 +1,73 @@
// Code generated by protoc-gen-gogo.
// source: github.com/containerd/containerd/protobuf/plugin/fieldpath.proto
// DO NOT EDIT!
/*
Package plugin is a generated protocol buffer package.
It is generated from these files:
github.com/containerd/containerd/protobuf/plugin/fieldpath.proto
It has these top-level messages:
*/
package plugin
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
var E_FieldpathAll = &proto.ExtensionDesc{
ExtendedType: (*google_protobuf.FileOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 63300,
Name: "containerd.plugin.fieldpath_all",
Tag: "varint,63300,opt,name=fieldpath_all,json=fieldpathAll",
Filename: "github.com/containerd/containerd/protobuf/plugin/fieldpath.proto",
}
var E_Fieldpath = &proto.ExtensionDesc{
ExtendedType: (*google_protobuf.MessageOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 64400,
Name: "containerd.plugin.fieldpath",
Tag: "varint,64400,opt,name=fieldpath",
Filename: "github.com/containerd/containerd/protobuf/plugin/fieldpath.proto",
}
func init() {
proto.RegisterExtension(E_FieldpathAll)
proto.RegisterExtension(E_Fieldpath)
}
func init() {
proto.RegisterFile("github.com/containerd/containerd/protobuf/plugin/fieldpath.proto", fileDescriptorFieldpath)
}
var fileDescriptorFieldpath = []byte{
// 203 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x72, 0x48, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0xcf, 0x2b, 0x49, 0xcc, 0xcc, 0x4b, 0x2d,
0x4a, 0x41, 0x66, 0x16, 0x14, 0xe5, 0x97, 0xe4, 0x27, 0x95, 0xa6, 0xe9, 0x17, 0xe4, 0x94, 0xa6,
0x67, 0xe6, 0xe9, 0xa7, 0x65, 0xa6, 0xe6, 0xa4, 0x14, 0x24, 0x96, 0x64, 0xe8, 0x81, 0x65, 0x84,
0x04, 0x11, 0x6a, 0xf5, 0x20, 0x4a, 0xa4, 0x14, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0x11, 0x5a,
0x53, 0x52, 0x8b, 0x93, 0x8b, 0x32, 0x0b, 0x4a, 0xf2, 0x8b, 0x20, 0x9a, 0xac, 0x9c, 0xb9, 0x78,
0xe1, 0xe6, 0xc4, 0x27, 0xe6, 0xe4, 0x08, 0xc9, 0xe8, 0x41, 0xf4, 0xe8, 0xc1, 0xf4, 0xe8, 0xb9,
0x65, 0xe6, 0xa4, 0xfa, 0x17, 0x94, 0x64, 0xe6, 0xe7, 0x15, 0x4b, 0x1c, 0x79, 0xc7, 0xac, 0xc0,
0xa8, 0xc1, 0x11, 0xc4, 0x03, 0xd7, 0xe4, 0x98, 0x93, 0x63, 0x65, 0xcf, 0xc5, 0x09, 0xe7, 0x0b,
0xc9, 0x63, 0x18, 0xe0, 0x9b, 0x5a, 0x5c, 0x9c, 0x98, 0x0e, 0x37, 0x63, 0xc2, 0x77, 0x88, 0x19,
0x08, 0x3d, 0x4e, 0x12, 0x27, 0x1e, 0xca, 0x31, 0xdc, 0x78, 0x28, 0xc7, 0xd0, 0xf0, 0x48, 0x8e,
0xf1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x04, 0x04, 0x00,
0x00, 0xff, 0xff, 0xd6, 0x21, 0x2a, 0xb6, 0x17, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,40 @@
// Protocol Buffers for Go with Gadgets
//
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
// http://github.com/gogo/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto2";
package containerd.plugin;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions {
optional bool fieldpath_all = 63300;
}
extend google.protobuf.MessageOptions {
optional bool fieldpath = 64400;
}

View File

@@ -0,0 +1,157 @@
package fieldpath
import (
"strings"
"github.com/containerd/containerd/protobuf/plugin"
"github.com/gogo/protobuf/gogoproto"
"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
"github.com/gogo/protobuf/protoc-gen-gogo/generator"
)
type fieldpathGenerator struct {
*generator.Generator
generator.PluginImports
typeurlPkg generator.Single
}
func init() {
generator.RegisterPlugin(new(fieldpathGenerator))
}
func (p *fieldpathGenerator) Name() string {
return "fieldpath"
}
func (p *fieldpathGenerator) Init(g *generator.Generator) {
p.Generator = g
}
func (p *fieldpathGenerator) Generate(file *generator.FileDescriptor) {
p.PluginImports = generator.NewPluginImports(p.Generator)
p.typeurlPkg = p.NewImport("github.com/containerd/containerd/typeurl")
for _, m := range file.Messages() {
if m.DescriptorProto.GetOptions().GetMapEntry() {
continue
}
if plugin.FieldpathEnabled(file.FileDescriptorProto, m.DescriptorProto) {
p.generateMessage(m)
}
}
}
func (p *fieldpathGenerator) generateMessage(m *generator.Descriptor) {
ccTypeName := generator.CamelCaseSlice(m.TypeName())
p.P()
p.P(`// Field returns the value for the given fieldpath as a string, if defined.`)
p.P(`// If the value is not defined, the second value will be false.`)
p.P("func(m *", ccTypeName, ") Field(fieldpath []string) (string, bool) {")
p.In()
var (
fields []*descriptor.FieldDescriptorProto
unhandled []*descriptor.FieldDescriptorProto
)
for _, f := range m.Field {
if f.IsBool() || f.IsString() || isLabelsField(f) || isAnyField(f) || isMessageField(f) {
fields = append(fields, f)
} else {
unhandled = append(unhandled, f)
}
}
if len(fields) > 0 {
p.P(`if len(fieldpath) == 0 {`)
p.In()
p.P(`return "", false`)
p.Out()
p.P(`}`)
p.P()
p.P("switch fieldpath[0] {")
for _, f := range unhandled {
p.P("// unhandled: ", f.GetName())
}
for _, f := range fields {
fName := generator.CamelCase(*f.Name)
if gogoproto.IsCustomName(f) {
fName = gogoproto.GetCustomName(f)
}
p.P(`case "`, f.GetName(), `":`)
p.In()
switch {
case isLabelsField(f):
p.P(`// Labels fields have been special-cased by name. If this breaks,`)
p.P(`// add better special casing to fieldpath plugin.`)
p.P(`if len(m.`, fName, `) == 0 {`)
p.In()
p.P(`return "", false`)
p.Out()
p.P("}")
p.P(`value, ok := m.`, fName, `[strings.Join(fieldpath[1:], ".")]`)
p.P(`return value, ok`)
case isAnyField(f):
p.P(`decoded, err := `, p.typeurlPkg.Use(), `.UnmarshalAny(m.`, fName, `)`)
p.P(`if err != nil {`)
p.In()
p.P(`return "", false`)
p.Out()
p.P(`}`)
p.P()
p.P(`adaptor, ok := decoded.(interface { Field([]string) (string, bool) })`)
p.P(`if !ok {`)
p.In()
p.P(`return "", false`)
p.Out()
p.P(`}`)
p.P(`return adaptor.Field(fieldpath[1:])`)
case isMessageField(f):
p.P(`// NOTE(stevvooe): This is probably not correct in many cases.`)
p.P(`// We assume that the target message also implements the Field`)
p.P(`// method, which isn't likely true in a lot of cases.`)
p.P(`//`)
p.P(`// If you have a broken build and have found this comment,`)
p.P(`// you may be closer to a solution.`)
p.P(`if m.`, fName, ` == nil {`)
p.In()
p.P(`return "", false`)
p.Out()
p.P(`}`)
p.P()
p.P(`return m.`, fName, `.Field(fieldpath[1:])`)
case f.IsString():
p.P(`return string(m.`, fName, `), len(m.`, fName, `) > 0`)
case f.IsBool():
p.P(`return fmt.Sprint(m.`, fName, `), true`)
}
p.Out()
}
p.P(`}`)
} else {
for _, f := range unhandled {
p.P("// unhandled: ", f.GetName())
}
}
p.P(`return "", false`)
p.Out()
p.P("}")
}
func isMessageField(f *descriptor.FieldDescriptorProto) bool {
return !f.IsRepeated() && f.IsMessage() && f.GetTypeName() != ".google.protobuf.Timestamp"
}
func isLabelsField(f *descriptor.FieldDescriptorProto) bool {
return f.IsMessage() && f.GetName() == "labels" && strings.HasSuffix(f.GetTypeName(), ".LabelsEntry")
}
func isAnyField(f *descriptor.FieldDescriptorProto) bool {
return !f.IsRepeated() && f.IsMessage() && f.GetTypeName() == ".google.protobuf.Any"
}

View File

@@ -0,0 +1,10 @@
package plugin
import (
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
)
func FieldpathEnabled(file *descriptor.FileDescriptorProto, message *descriptor.DescriptorProto) bool {
return proto.GetBoolExtension(message.Options, E_Fieldpath, proto.GetBoolExtension(file.Options, E_FieldpathAll, false))
}