You can extend BotKube functionality by writing additional filters. The FilterEngine runs these filters on the Event struct before forwarding it as a notification to a channel. These filters can check resource specs, validate some checks and add messages to the Event struct.
We have already defined a filter to add suggestions in the notifications if container image in pod specs is using latest tag.
Let’s see, how we can write a filter like this.
Prerequisites:
Create a new file (e.g image_tag_checker.go) in botkube/pkg/filterengine/filters/ directory
Set package name as “filters” and import required packages:
package filters
import (
"strings"
"github.com/sirupsen/logrus"
apiV1 "k8s.io/api/core/v1"
"github.com/kubeshop/botkube/pkg/events"
)
FilterEngine has an interface Filter defined for the filters:
type Filter interface {
Run(context.Context, interface{}, *events.Event)
Name() string
Describe() string
}
Create a struct which implements the Filter interface. Use logger instance taken as an argument from the constructor:
// ImageTagChecker add recommendations to the event object if latest image tag is used in pod containers
type ImageTagChecker struct {
log logrus.FieldLogger
}
// NewImageTagChecker creates a new ImageTagChecker instance
func NewImageTagChecker(log logrus.FieldLogger) *ImageTagChecker {
return &ImageTagChecker{log: log}
}
// Run filer and modifies event struct
func (f *ImageTagChecker) Run(ctx context.Context, object interface{}, event *events.Event) {
// your logic goes here
}
// Name returns the filter's name
func (f *ImageTagChecker) Name() string {
return "ImageTagChecker"
}
// Describe describes the filter
func (f *ImageTagChecker) Describe() string {
return "Checks and adds recommendation if 'latest' image tag is used for container image."
}
Now, put your logic in the Run() function to parse resource object, run validation and modify Event struct. The fields in the Event struct can be found here.
// Run filers and modifies event struct
func (f *ImageTagChecker) Run(_ context.Context, object interface{}, event *events.Event) error {
if event.Kind != "Pod" || event.Type != config.CreateEvent || utils.GetObjectTypeMetaData(object).Kind == "Event" {
return nil
}
var podObj coreV1.Pod
err := utils.TransformIntoTypedObject(object.(*unstructured.Unstructured), &podObj)
if err != nil {
return fmt.Errorf("while transforming object type %T into type: %T: %w", object, podObj, err)
}
// Check image tag in initContainers
for _, ic := range podObj.Spec.InitContainers {
images := strings.Split(ic.Image, ":")
if len(images) == 1 || images[1] == "latest" {
event.Recommendations = append(event.Recommendations, fmt.Sprintf(":latest tag used in image '%s' of initContainer '%s' should be avoided.", ic.Image, ic.Name))
}
}
// Check image tag in Containers
for _, c := range podObj.Spec.Containers {
images := strings.Split(c.Image, ":")
if len(images) == 1 || images[1] == "latest" {
event.Recommendations = append(event.Recommendations, fmt.Sprintf(":latest tag used in image '%s' of Container '%s' should be avoided.", c.Image, c.Name))
}
}
f.log.Debug("Image tag filter successful!")
return nil
}
Open pkg/filterengine/with_all_filters.go file and call the constructor of your new filter in the WithAllFilters
method:
// WithAllFilters returns new DefaultFilterEngine instance with all filters registered.
func WithAllFilters(logger *logrus.Logger, dynamicCli dynamic.Interface, mapper meta.RESTMapper, conf *config.Config) *DefaultFilterEngine {
filterEngine := New(logger.WithField(componentLogFieldKey, "Filter Engine"))
filterEngine.Register([]Filter{
filters.NewIngressValidator(logger.WithField(filterLogFieldKey, "Ingress Validator"), dynamicCli),
// ...
// Your filter goes here:
filters.NewImageTagChecker(logger.WithField(filterLogFieldKey, "Image Tag Checker")), // make sure to use `logger.WithField`
}...)
return filterEngine
}
make container-image
.The implementation of built-in filters can be found at: https://github.com/kubeshop/botkube/tree/main/pkg/filterengine/filters