Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cluster Scoped Parent #94

Merged
merged 9 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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