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:
1
protobuf/plugin/doc.go
Normal file
1
protobuf/plugin/doc.go
Normal file
@@ -0,0 +1 @@
|
||||
package plugin
|
||||
73
protobuf/plugin/fieldpath.pb.go
Normal file
73
protobuf/plugin/fieldpath.pb.go
Normal 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,
|
||||
}
|
||||
40
protobuf/plugin/fieldpath.proto
Normal file
40
protobuf/plugin/fieldpath.proto
Normal 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;
|
||||
}
|
||||
157
protobuf/plugin/fieldpath/fieldpath.go
Normal file
157
protobuf/plugin/fieldpath/fieldpath.go
Normal 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"
|
||||
}
|
||||
10
protobuf/plugin/helpers.go
Normal file
10
protobuf/plugin/helpers.go
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user