Manifest methods interception
Sometimes defining Kubernetes manifests involves code repetition, and you have to do the same repetitive job again and again. For example - you almost always have to define metadata labels at the top level of deployment manifest (metadata.labels
), in pod spec template (spec.template.metadata.labels
), and then section spec.selector.matchLabels
should contain same labels. Thus you need copy-paste same labels 3 times every time you write a manifest. It would be great if there was a way to automate such repetitive tasks, and manifests methods interception is a way to do that in K8S Framework.
Dealroadshow K8S Framework uses Proxy pattern to wrap every instance of your manifest classes into a proxy-object. Then, when manifests are processed, all methods of your manifests classes are called on proxy objects. This proxy objects wrap every method of original manifest instance in order to dispatch events before and after calling original methods, thus allowing you to intercept any method call. K8S Framework uses PSR-14 EventDispatcherInterface
in order to dispatch all events, and K8S Bundle uses Symfony Event Dispatcher component that implements this interface. Before every manifest method call ManifestMethodEvent
is dispatched. After manifest method was called, ManifestMethodCalledEvent
is dispatched. Among other things, you then may replace method's return value in event subscriber.
Let's demonstrate possibilities of methods interceptions by automating the definition of selector.matchLabels
and metadata.labels
sections in all manifests, so that you don't have to write those ever again. For that, we will define event subscriber class SelectorLabelsSubscriber
in a standard Symfony project location src/EventListener
:
src/EventListener/SelectorLabelSubscriber.php | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
As we see, our class implements Symfony's native EventSubscriberInterface
, so thanks to autoconfiguration, just defining this class and implementing method getSubscribedEvents()
as above is enough for it to recieve event every time manifest's method is about to be called. All logic of interception resides in method onManifestMethod()
, which recieves events, so let's analyze it:
- lines 8-10: we are checking if method being called is called
selector
, and if that manifest, on which this method is about to be called, is an instance ofDeploymentInterface
. If one of this conditions is false, we do nothing. Thus we are limiting our event subscriber to calls of methodselector()
on deployments. - line 14: event object contains all parameters, passed to method being called. Method
DeploymentInterface::selector()
must recieveSelectorConfigurator
instance as$selector
argument, so we getting it from array of method params. - line 17: there is no method that returns app in
ManifestInterface
, but any manifest that inherits K8S Framework's corresponding abstract classes (likeAbstractDeployment
for deployments) inherits property$app
that contains app instance. Since this property is protected, we useDealroadshow\Bundle\K8SBundle\Util\PropertyAccessUtil
class to retrieve it from manifest instance. - lines 19-22: we are using
$selector
argument ofDeploymentInterface::selector()
method to define two labels:app
, that contains our app alias, andcomponent
, which contains short name of the deployment.
Thus any generated deployment will have selector labels automatically. K8S Framework copies all labels from selector.matchLabels
to metadata.labels
and spec.template.metadata.labels
sections automatically - so you'll never have to write this labels again in your deployments - all thanks to one event subscriber.
K8S Bundle also has predefined abstract classes that you may inherit from if you want to intercept manifest's methods: AbstractMethodSubscriber
and AbstractMethodResultSubscriber
- for intercepting method before it was called, or after, respectively. Below is a new version of our SelectorLabelsSubscriber
that inherits AbstractMethodSubscriber
:
src/EventListener/SelectorLabelSubscriber.php | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
This class is pretty self-explanatory now: method supports()
is used to determine whether this subscriber should be called for the given event. Method beforeMethod()
(for method result subscribers - afterMethod()
) then does the real job, if supports()
returned true.
Now that we have our subscriber, let's test it. First, let's define a simplest possible deployment class ExampleDeployment
in ExampleApp
:
class ExampleDeployment extends AbstractContainerDeployment
{
public function image(): Image
{
return Image::fromName('my-cool/image');
}
public static function shortName(): string
{
return 'example';
}
}
Now we can call console command to dump manifests into YAML files:
bin/console k8s:dump:all
After command finished, let's look what YAML was generated from our ExampleDeployment
class:
example/example.deployment.yaml | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Notice lines 4-6, 11-13 and 19-21: we have our labels everywhere we need them. Isn't this cool?
Now that you know about manifests methods interception, you may use this technique to automate many things in your manifests. Please note that method interception also works for container methods: for example, if your deployment class inherits AbstractContainerDeployment
, you may intercept it's method resources()
or any other method from ContainerInterrface
.