Skip to content

Commit

Permalink
Cluster Scoped Parent (#94)
Browse files Browse the repository at this point in the history
Signed-off-by: Attila Mészáros <csviri@gmail.com>
  • Loading branch information
csviri committed May 13, 2024
1 parent a9deb9e commit 6bec465
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 46 deletions.
17 changes: 11 additions & 6 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ The `DependentResource` implementation of JOSDK makes all kinds of optimizations

## [GlueOperator resource](https://github.com/csviri/kubernetes-glue-operator/releases/latest/download/glueoperators.glue-v1.yml)

The specs of `GlueOperator` are almost identical to `Glue`, it just adds one additional attribute **`parent`**,
which has the following sub-attributes:
- **`apiVersion`** and **`kind`** - specifies the resources to reconciler according to the spec.
Targets are usually custom resources but not necessarily, it also works with built-in Kubernetes
resources.
- **`labelSelector`** - an optional label selector for the target resources
The specs of `GlueOperator` are almost identical to `Glue`, it just adds some additional attributes:

- **`parent`** - specifies the resources handled by the operator. Targets are usually custom resources but not necessarily,
it also works with built-in Kubernetes resources. With the following sub-attributes:
- **`apiVersion`** and **`kind`** - of the target custom resources.
- **`labelSelector`** - optional label selector for the target resources.
- **`clusterScoped`** - optional boolean value, if the parent resource is cluster scoped. Default is `false`.
- **`glueMetadata`** - optionally, you can customize the `Glue` resource created for each parent resource.
This is especially important when the parent is a cluster scoped resource - in that case it is mandatory to set.
Using this you can specify the **`name`** and **`namespace`** of the created `Glue`.
See usage on the sample [secret-copy-operator](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/sample/secretcopy/secret-copy.operator.yaml#L10-L12).

See minimal `GlueOperator` [here](https://github.com/csviri/kubernetes-glue-operator/blob/main/src/test/resources/glueoperator/SimpleGlueOperator.yaml).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.csviri.operator.glue.customresource.operator;

import java.util.Objects;

public class GlueMetadata {

private String name;
private String namespace;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getNamespace() {
return namespace;
}

public void setNamespace(String namespace) {
this.namespace = namespace;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
GlueMetadata that = (GlueMetadata) o;
return Objects.equals(name, that.name) && Objects.equals(namespace, that.namespace);
}

@Override
public int hashCode() {
return Objects.hash(name, namespace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class GlueOperatorSpec extends GlueSpec {
@Required
private Parent parent;

private GlueMetadata glueMetadata;

public Parent getParent() {
return parent;
}
Expand All @@ -19,6 +21,14 @@ public GlueOperatorSpec setParent(Parent parent) {
return this;
}

public GlueMetadata getGlueMetadata() {
return glueMetadata;
}

public void setGlueMetadata(GlueMetadata glueMetadata) {
this.glueMetadata = glueMetadata;
}

@Override
public boolean equals(Object o) {
if (this == o)
Expand All @@ -28,12 +38,11 @@ public boolean equals(Object o) {
if (!super.equals(o))
return false;
GlueOperatorSpec that = (GlueOperatorSpec) o;
return Objects.equals(parent, that.parent);
return Objects.equals(parent, that.parent) && Objects.equals(glueMetadata, that.glueMetadata);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), parent);
return Objects.hash(super.hashCode(), parent, glueMetadata);
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package io.csviri.operator.glue.customresource.operator;

import java.util.Objects;

public class Parent {

private String apiVersion;
private String kind;
private boolean clusterScoped = false;
private String labelSelector;


public Parent() {}

public Parent(String apiVersion, String kind) {
Expand Down Expand Up @@ -38,4 +42,28 @@ public String getLabelSelector() {
public void setLabelSelector(String labelSelector) {
this.labelSelector = labelSelector;
}

public boolean isClusterScoped() {
return clusterScoped;
}

public void setClusterScoped(boolean clusterScoped) {
this.clusterScoped = clusterScoped;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Parent parent = (Parent) o;
return clusterScoped == parent.clusterScoped && Objects.equals(apiVersion, parent.apiVersion)
&& Objects.equals(kind, parent.kind) && Objects.equals(labelSelector, parent.labelSelector);
}

@Override
public int hashCode() {
return Objects.hash(apiVersion, kind, clusterScoped, labelSelector);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package io.csviri.operator.glue.dependent;

import io.csviri.operator.glue.customresource.glue.Glue;
import io.csviri.operator.glue.templating.GenericTemplateHandler;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;

public class GCGenericDependentResource extends GenericDependentResource
implements GarbageCollected<Glue> {

public GCGenericDependentResource(GenericKubernetesResource desired, String name,
public GCGenericDependentResource(GenericTemplateHandler genericTemplateHandler,
GenericKubernetesResource desired, String name,
boolean clusterScoped) {
super(desired, name, clusterScoped);
super(genericTemplateHandler, desired, name, clusterScoped);
}

public GCGenericDependentResource(String desiredTemplate, String name, boolean clusterScoped) {
super(desiredTemplate, name, clusterScoped);
public GCGenericDependentResource(GenericTemplateHandler genericTemplateHandler,
String desiredTemplate, String name, boolean clusterScoped) {
super(genericTemplateHandler, desiredTemplate, name, clusterScoped);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,24 @@ public class GenericDependentResource
private final boolean clusterScoped;

// optimize share between instances
private final GenericTemplateHandler genericTemplateHandler = new GenericTemplateHandler();
private final GenericTemplateHandler genericTemplateHandler;

public GenericDependentResource(GenericKubernetesResource desired, String name,
public GenericDependentResource(GenericTemplateHandler genericTemplateHandler,
GenericKubernetesResource desired, String name,
boolean clusterScoped) {
super(new GroupVersionKind(desired.getApiVersion(), desired.getKind()));
this.desired = desired;
this.desiredTemplate = null;
this.name = name;
this.clusterScoped = clusterScoped;
this.genericTemplateHandler = genericTemplateHandler;
}

public GenericDependentResource(String desiredTemplate, String name, boolean clusterScoped) {
public GenericDependentResource(GenericTemplateHandler genericTemplateHandler,
String desiredTemplate, String name, boolean clusterScoped) {
super(new GroupVersionKind(Utils.getApiVersionFromTemplate(desiredTemplate),
Utils.getKindFromTemplate(desiredTemplate)));
this.genericTemplateHandler = genericTemplateHandler;
this.name = name;
this.desiredTemplate = desiredTemplate;
this.desired = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ public class GlueReconciler implements Reconciler<Glue>, Cleaner<Glue>, ErrorSta
private final KubernetesResourceDeletedCondition deletePostCondition =
new KubernetesResourceDeletedCondition();

private final GenericTemplateHandler genericTemplateHandler = new GenericTemplateHandler();

private final GenericTemplateHandler genericTemplateHandler;

public GlueReconciler(ValidationAndErrorHandler validationAndErrorHandler,
InformerRegister informerRegister) {
InformerRegister informerRegister,
GenericTemplateHandler genericTemplateHandler) {
this.validationAndErrorHandler = validationAndErrorHandler;
this.informerRegister = informerRegister;
this.genericTemplateHandler = genericTemplateHandler;
}

/**
Expand Down Expand Up @@ -131,7 +132,6 @@ private void registerRelatedResourceInformers(Context<Glue> context,
// todo test
private void cleanupRemovedResourcesFromWorkflow(Context<Glue> context,
Glue primary) {

context.getSecondaryResources(GenericKubernetesResource.class).forEach(r -> {
String dependentName = r.getMetadata().getAnnotations().get(DEPENDENT_NAME_ANNOTATION_KEY);
// dependent name is null for related resources
Expand Down Expand Up @@ -200,21 +200,23 @@ private void createAndAddDependentToWorkflow(Glue primary, Context<Glue> context
.ifPresent(c -> builder.withReconcilePrecondition(toCondition(c)));
}

private static GenericDependentResource createDependentResource(DependentResourceSpec spec,
private GenericDependentResource createDependentResource(DependentResourceSpec spec,
boolean leafDependent, Boolean resourceInSameNamespaceAsPrimary) {

if (leafDependent && resourceInSameNamespaceAsPrimary && !spec.isClusterScoped()) {
return spec.getResourceTemplate() != null
? new GCGenericDependentResource(spec.getResourceTemplate(), spec.getName(),
? new GCGenericDependentResource(genericTemplateHandler, spec.getResourceTemplate(),
spec.getName(),
spec.isClusterScoped())
: new GCGenericDependentResource(spec.getResource(), spec.getName(),
: new GCGenericDependentResource(genericTemplateHandler, spec.getResource(),
spec.getName(),
spec.isClusterScoped());
} else {
return spec.getResourceTemplate() != null
? new GenericDependentResource(spec.getResourceTemplate(), spec.getName(),
spec.isClusterScoped())
: new GenericDependentResource(spec.getResource(), spec.getName(),
spec.isClusterScoped());
? new GenericDependentResource(genericTemplateHandler,
spec.getResourceTemplate(), spec.getName(), spec.isClusterScoped())
: new GenericDependentResource(genericTemplateHandler,
spec.getResource(), spec.getName(), spec.isClusterScoped());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import io.csviri.operator.glue.customresource.operator.GlueOperatorSpec;
import io.csviri.operator.glue.customresource.operator.ResourceFlowOperatorStatus;
import io.csviri.operator.glue.reconciler.ValidationAndErrorHandler;
import io.csviri.operator.glue.templating.GenericTemplateHandler;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
Expand All @@ -26,7 +28,6 @@
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;

import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;

import static io.csviri.operator.glue.reconciler.glue.GlueReconciler.GLUE_RECONCILER_NAME;

Expand All @@ -42,19 +43,25 @@ public class GlueOperatorReconciler
public static final String PARENT_RELATED_RESOURCE_NAME = "parent";
public static final String GLUE_OPERATOR_RECONCILER_NAME = "glue-operator";

@Inject
ValidationAndErrorHandler validationAndErrorHandler;

@ConfigProperty(name = "quarkus.operator-sdk.controllers." + GLUE_RECONCILER_NAME + ".selector")
Optional<String> glueLabelSelector;

@Inject
ControllerConfig controllerConfig;
private final ControllerConfig controllerConfig;
private final ValidationAndErrorHandler validationAndErrorHandler;
private final GenericTemplateHandler genericTemplateHandler;

private Map<String, String> defaultGlueLabels;

private InformerEventSource<Glue, GlueOperator> glueEventSource;

public GlueOperatorReconciler(ControllerConfig controllerConfig,
ValidationAndErrorHandler validationAndErrorHandler,
GenericTemplateHandler genericTemplateHandler) {
this.controllerConfig = controllerConfig;
this.validationAndErrorHandler = validationAndErrorHandler;
this.genericTemplateHandler = genericTemplateHandler;
}

@PostConstruct
void init() {
defaultGlueLabels = initDefaultLabelsToAddToGlue();
Expand Down Expand Up @@ -94,12 +101,10 @@ private Glue createGlue(GenericKubernetesResource targetParentResource,
GlueOperator glueOperator) {
var glue = new Glue();

glue.setMetadata(new ObjectMetaBuilder()
.withName(
glueName(targetParentResource.getMetadata().getName(), targetParentResource.getKind()))
.withNamespace(targetParentResource.getMetadata().getNamespace())
.withLabels(Map.of(FOR_GLUE_OPERATOR_LABEL_KEY, FOR_GLUE_OPERATOR_LABEL_VALUE))
.build());
ObjectMeta glueMetadata = glueMetadata(glueOperator, targetParentResource);


glue.setMetadata(glueMetadata);
glue.setSpec(toWorkflowSpec(glueOperator.getSpec()));

if (!defaultGlueLabels.isEmpty()) {
Expand All @@ -113,13 +118,38 @@ private Glue createGlue(GenericKubernetesResource targetParentResource,
parentRelatedSpec.setKind(parent.getKind());
parentRelatedSpec.setResourceNames(List.of(targetParentResource.getMetadata().getName()));
parentRelatedSpec.setNamespace(targetParentResource.getMetadata().getNamespace());
parentRelatedSpec.setClusterScoped(glueOperator.getSpec().getParent().isClusterScoped());

glue.getSpec().getRelatedResources().add(parentRelatedSpec);

glue.addOwnerReference(targetParentResource);
return glue;
}

private ObjectMeta glueMetadata(GlueOperator glueOperator,
GenericKubernetesResource parent) {

ObjectMetaBuilder objectMetaBuilder = new ObjectMetaBuilder();

var glueMeta = glueOperator.getSpec().getGlueMetadata();
if (glueMeta != null) {
// optimize
var data = Map.of(PARENT_RELATED_RESOURCE_NAME, parent);
var glueName = genericTemplateHandler.processInputAndTemplate(data, glueMeta.getName());
var glueNamespace =
genericTemplateHandler.processInputAndTemplate(data, glueMeta.getNamespace());
objectMetaBuilder.withName(glueName);
objectMetaBuilder.withNamespace(glueNamespace);
} else {
objectMetaBuilder.withName(
glueName(parent.getMetadata().getName(), parent.getKind()))
.withNamespace(parent.getMetadata().getNamespace());
}

objectMetaBuilder
.withLabels(Map.of(FOR_GLUE_OPERATOR_LABEL_KEY, FOR_GLUE_OPERATOR_LABEL_VALUE));
return objectMetaBuilder.build();
}

private GlueSpec toWorkflowSpec(GlueOperatorSpec spec) {
var res = new GlueSpec();
res.setChildResources(new ArrayList<>(spec.getChildResources()));
Expand Down
Loading

0 comments on commit 6bec465

Please sign in to comment.