diff --git a/Readme.md b/Readme.md index 541dc63..ddec947 100644 --- a/Readme.md +++ b/Readme.md @@ -20,7 +20,7 @@ You should start from this schema and implement the following feature: - feat n.2: When a custom resource has been deleted, remove the linked resources (Pod) - feat n.3: When a new custom resource has been created or updated, the operator should deploy a deployment manifest and take the number of replicas from the new custom resource - feat n.4: When the pod starts, update the default nginx index.html and put inside a short description from the custom resource -- feat n.5: Show time: 🎉 Create a NodePort service for that deployment and retreive the public ip address. +- feat n.5: Show time: 🎉 Create a NodePort service for that deployment and retrieve the public ip address. ## How to generate manifests @@ -51,3 +51,7 @@ Apply it: (*) Reconciles: it stands for the act of the operator to edit (reconcile) the status of the kubernetes cluster as we want (looking at the custom resource) + + +https://docs.openshift.com/container-platform/4.11/operators/operator_sdk/java/osdk-java-tutorial.html +https://github.com/kubernetes-client/java/tree/b42fae2b5437836f8c0254d8f19f3a7ba9ebd503/kubernetes/docs diff --git a/kubernetes-charts/operator-role.yaml b/kubernetes-charts/operator-role.yaml index 5d1bc59..11eea73 100644 --- a/kubernetes-charts/operator-role.yaml +++ b/kubernetes-charts/operator-role.yaml @@ -8,12 +8,26 @@ rules: resources: - events - pods + - configmaps + - services verbs: - get - list - create - update - delete +- apiGroups: + - "apps" + resources: + - deployments + verbs: + - get + - list + - create + - update + - delete + - watch + - patch - apiGroups: - coordination.k8s.io resources: diff --git a/kubernetes-charts/websites.intre.it-v1.yml b/kubernetes-charts/websites.intre.it-v1.yml new file mode 100644 index 0000000..f59528f --- /dev/null +++ b/kubernetes-charts/websites.intre.it-v1.yml @@ -0,0 +1,40 @@ +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: websites.intre.it +spec: + group: intre.it + names: + kind: WebSite + plural: websites + singular: website + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + replicas: + type: integer + shortDescription: + type: string + url: + type: string + webSiteName: + type: string + type: object + status: + properties: + areWeGood: + type: boolean + observedGeneration: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/src/main/java/intre/it/javaoperator/controller/WebSiteReconciler.java b/src/main/java/intre/it/javaoperator/controller/WebSiteReconciler.java index ae78af1..2d70ffa 100644 --- a/src/main/java/intre/it/javaoperator/controller/WebSiteReconciler.java +++ b/src/main/java/intre/it/javaoperator/controller/WebSiteReconciler.java @@ -1,9 +1,16 @@ package intre.it.javaoperator.controller; import intre.it.javaoperator.model.WebSite; +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +import java.util.HashMap; +import java.util.Map; @ControllerConfiguration public class WebSiteReconciler implements Reconciler, Cleaner { @@ -15,11 +22,91 @@ public WebSiteReconciler() { @Override public UpdateControl reconcile(WebSite WebSite, Context context) throws Exception { + Map html = new HashMap<>(); + html.put("index.html", "" + WebSite.getSpec().getShortDescription() + ""); + + ConfigMap configMap = new ConfigMapBuilder() + .withNewMetadata() + .withName(WebSite.getSpec().getWebSiteName()+"-cm") + .addToLabels("app", WebSite.getSpec().getWebSiteName()) + .endMetadata() + .withData(html) + .build(); + client.configMaps().resource(configMap).create(); + + Deployment deployment = new DeploymentBuilder() + .withNewMetadata() + .withName(WebSite.getSpec().getWebSiteName()+"-deployment") + .addToLabels("app", WebSite.getSpec().getWebSiteName()) + .endMetadata() + .withNewSpec() + .withReplicas(WebSite.getSpec().getReplicas()) + .withNewSelector() + .addToMatchLabels("app", WebSite.getSpec().getWebSiteName()) + .endSelector() + .withNewTemplate() + .withNewMetadata() + .addToLabels("app", WebSite.getSpec().getWebSiteName()) + .endMetadata() + .withNewSpec() + .addNewContainer() + .withName("nginx") + .withImage("nginx") + .addNewPort() + .withContainerPort(80) + .endPort() + .addNewVolumeMount() + .withName("nginx-conf") + .withSubPath("index.html") + .withMountPath("/usr/share/nginx/html/index.html") + .endVolumeMount() + .endContainer() + .addNewVolume() + .withName("nginx-conf") + .withNewConfigMap() + .withName(WebSite.getSpec().getWebSiteName()+"-cm") + .addNewItem() + .withKey("index.html") + .withPath("index.html") + .endItem() + .endConfigMap() + .endVolume() + .endSpec() + .endTemplate() + .endSpec() + .build(); + + //Configurazione service + Map selector = new HashMap<>(); + selector.put("app", WebSite.getSpec().getWebSiteName()); + + Service service = new ServiceBuilder() + .withNewMetadata() + .withName(WebSite.getSpec().getWebSiteName()+"-service") + .endMetadata() + .withNewSpec() + .withType("LoadBalancer") + .addToSelector(selector) + .addNewPort() + .withName("deploy-port") + .withPort(80) + .withNodePort(30007) + .withTargetPort(new IntOrString(80)) + .withProtocol("TCP") + .endPort() + .endSpec() + .build(); + client.services().resource(service).create();//Generiamo il service + + client.apps().deployments().resource(deployment).create(); return UpdateControl.patchStatus(WebSite); } @Override public DeleteControl cleanup(WebSite webSite, Context context) { + client.apps().deployments().withName(webSite.getSpec().getWebSiteName()+"-deployment").delete(); // cancello il deployment + client.services().withName(webSite.getSpec().getWebSiteName()+"-service").delete(); //Cancello il service + client.configMaps().withName(webSite.getSpec().getWebSiteName()+"-cm").delete(); //Cancello la config map return DeleteControl.defaultDelete(); } } diff --git a/src/main/java/intre/it/javaoperator/model/WebSiteSpec.java b/src/main/java/intre/it/javaoperator/model/WebSiteSpec.java index 7aeccf8..a0fb575 100644 --- a/src/main/java/intre/it/javaoperator/model/WebSiteSpec.java +++ b/src/main/java/intre/it/javaoperator/model/WebSiteSpec.java @@ -5,6 +5,7 @@ public class WebSiteSpec { private String webSiteName; private String url; private String shortDescription; + private int replicas; public String getWebSiteName() { return webSiteName; @@ -29,4 +30,12 @@ public String getShortDescription() { public void setShortDescription(String shortDescription) { this.shortDescription = shortDescription; } + + public int getReplicas() { + return replicas; + } + + public void setReplicas(int replicas) { + this.replicas = replicas; + } }