
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>
158 lines
4.1 KiB
Go
158 lines
4.1 KiB
Go
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"
|
|
}
|