From e5b1afd324212a7bc0bae6a06f65fe66ed213f4b Mon Sep 17 00:00:00 2001 From: Ramesh Malla Date: Sun, 10 Dec 2023 17:30:18 +0100 Subject: [PATCH] Added DEVELOPMENT.md and improved tests --- CODE_OF_CONDUCT.md | 2 + DEVELOPMENT.md | 47 +++++++++++++ README.md | 2 + install/cluster/Config-Operator.yaml | 1 - .../conifgvalidator/ConfigValidatorTest.java | 70 +++++++++++++++---- src/test/resources/app-deployment.yaml | 64 +++++++++++++++++ 6 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 src/test/resources/app-deployment.yaml diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index fc6012b..c36d6e8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,7 @@ # Contributor Covenant Code of Conduct +_**In the grand play of code, let’s make kindness our main function. After all, a little humor and a sprinkle of 'please' and 'thank you' never hurt anyone's development stack!**_ + ## Our Pledge In the interest of fostering an open and welcoming environment, we as diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..992fcf0 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,47 @@ +# Developers guide + +The operator is a Spring Boot project. To build the project, you need the following prerequisites: +- A local installation of JDK17 +- Gradle `./gradlew clean build` to execute the tests and build the operator. + +To test and deploy the operator in your local environment, you need the following setup: +- [Docker](https://www.docker.com/) to build the operator image with your local changes. +- [minikube](https://minikube.sigs.k8s.io/docs/start/) to deploy the operator and test your changes. + +## How I test my changes + +Once I have made some changes to the code, I follow the below workflow: + +- Run unit and integration tests. +- Build operator + ``` + ~ ./gradlew clean build + ``` +- Build the operator docker image with the new changes. + ``` + ~ minikube image build -t configs-operator:test_5 . + ``` + Note that I used minikube client to build the docker image. Quote from [minikube](https://minikube.sigs.k8s.io/docs/handbook/pushing/#1-pushing-directly-to-the-in-cluster-docker-daemon-docker-env) + > When using a container or VM driver (all drivers except none), you can reuse the Docker daemon inside minikube cluster. This means you don’t have to build on your host machine and push the image into a docker registry. You can just build inside the same docker daemon as minikube which speeds up local experiments. +- Update [./install/cluster/Config-Operator.yaml](https://github.com/rameshmalla/k8s-custom-configmap/blob/main/install/cluster/Config-Operator.yaml#L31) with your new test image `configs-operator:test_5`. +- Apply the CRD and operator manifest in the `./install` folder. + ```shell + ~ kubectl apply -f ./install/cluster/configmapcustomresources.rcube.com-v1.yml + customresourcedefinition.apiextensions.k8s.io/configmapcustomresources.rcube.com created + + ~ kubectl apply -f ./install/cluster/Config-Operator.yaml + namespace/configs-operator created + serviceaccount/configs-operator created + deployment.apps/configs-operator created + clusterrole.rbac.authorization.k8s.io/configs-operator-role created + clusterrolebinding.rbac.authorization.k8s.io/configs-operator-admin created + ``` +- Apply application [./install/testing/app-deployment.yaml](https://github.com/rameshmalla/k8s-custom-configmap/blob/main/install/testing/app-deployment.yaml) manifest. + ```shell + ~ kubectl apply -f ./install/testing/app-deployment.yaml + namespace/local created + configmapcustomresource.rcube.com/application-toggles-config created + deployment.apps/nginx-deployment created + ``` + +- Test my changes against the deployed application and the operator. \ No newline at end of file diff --git a/README.md b/README.md index cd47be3..9d9494a 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ data: ## Example Apply this [example](./install/testing/app-deployment.yaml) to play with the custom config-map resource. +## Developers guide +Refer to this [page](DEVELOPMENT.md). ## Getting help If you have questions, concerns, bug reports, etc., please file an issue in this repository's [Issue Tracker](https://github.com/rameshmalla/k8s-custom-configmap/issues). diff --git a/install/cluster/Config-Operator.yaml b/install/cluster/Config-Operator.yaml index 5d588ce..04ef462 100644 --- a/install/cluster/Config-Operator.yaml +++ b/install/cluster/Config-Operator.yaml @@ -29,7 +29,6 @@ spec: containers: - name: operator image: malla/configs-operator:main - imagePullPolicy: IfNotPresent resources: requests: memory: 512Mi diff --git a/src/test/java/com/rcube/configmap/conifgvalidator/ConfigValidatorTest.java b/src/test/java/com/rcube/configmap/conifgvalidator/ConfigValidatorTest.java index 15084bd..ae1a7a3 100644 --- a/src/test/java/com/rcube/configmap/conifgvalidator/ConfigValidatorTest.java +++ b/src/test/java/com/rcube/configmap/conifgvalidator/ConfigValidatorTest.java @@ -1,28 +1,35 @@ package com.rcube.configmap.conifgvalidator; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import com.rcube.configmap.TestDataUtil; import com.rcube.configmap.operator.ConfigMapCustomResource; import com.rcube.configmap.operator.CustomConfigMapSpec; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.springboot.starter.test.EnableMockOperator; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + @SpringBootTest @EnableMockOperator( crdPaths = "install/cluster/configmapcustomresources.rcube.com-v1.yml" @@ -32,7 +39,7 @@ public class ConfigValidatorTest { @Autowired private KubernetesClient client; - private static Pair TEST_DATA = TestDataUtil.validSchemaData(); + private static final Pair TEST_DATA = TestDataUtil.validSchemaData(); @Test public void contextLoads() { @@ -46,7 +53,7 @@ public void contextLoads() { } @Test - public void test() { + public void testShouldCreateConfigMapFromCustomResource() { String testNS = "test-ns"; client.namespaces().resource(namespace(testNS)).create(); assertNotNull(client.namespaces().withName(testNS).get()); @@ -54,10 +61,7 @@ public void test() { client.resources(ConfigMapCustomResource.class).inNamespace(testNS) .resource(getValidConfigSpec()) .create(); - final ConfigMapCustomResource resource = client.resources(ConfigMapCustomResource.class) - .inNamespace(testNS) - .withName("test-resource") - .get(); + final ConfigMapCustomResource resource = getResource(testNS,"test-resource", ConfigMapCustomResource.class); assertNotNull(resource); assertEquals(TEST_DATA.getRight(), resource.getSpec().getConfig().getData().get("test.json")); @@ -73,6 +77,34 @@ public void test() { .withName("test-resource").get())); } + @Test + public void testShouldCreateVersionedConfigMapFromCustomResource() throws URISyntaxException { + final String nameSpace = "local"; + final String deploymentName = "nginx-deployment"; + client.load(fetchResource("app-deployment.yaml")) + .create(); + await() + .atMost(5, TimeUnit.SECONDS) + .untilAsserted( + () -> + { + final Deployment resource = getResource(nameSpace, deploymentName, Deployment.class); + assertNotNull(resource); + resource.getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .stream() + .map(Volume::getConfigMap) + .filter(Objects::nonNull) + .map(ConfigMapVolumeSource::getName) + .forEach(requestedConfigMaps -> { + System.out.println(requestedConfigMaps); + assertNotNull(getResource(nameSpace, requestedConfigMaps, ConfigMap.class)); + }); + }); + } + private ConfigMapCustomResource getValidConfigSpec() { final ObjectMeta objectMeta = new ObjectMeta(); objectMeta.setName("test-resource"); @@ -85,6 +117,14 @@ private ConfigMapCustomResource getValidConfigSpec() { return resource; } + private T getResource(final String nameSpace, + final String name, + final Class resourceType) { + return client.resources(resourceType) + .inNamespace(nameSpace) + .withName(name).get(); + } + private Namespace namespace(String ns) { return new NamespaceBuilder() .withMetadata( @@ -92,4 +132,8 @@ private Namespace namespace(String ns) { .build(); } + private InputStream fetchResource(String fileName) { + return ConfigValidatorTest.class.getClassLoader().getResourceAsStream(fileName); + } + } diff --git a/src/test/resources/app-deployment.yaml b/src/test/resources/app-deployment.yaml new file mode 100644 index 0000000..82df053 --- /dev/null +++ b/src/test/resources/app-deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: local +--- +apiVersion: "rcube.com/v1" +kind: ConfigMapCustomResource +metadata: + namespace: local + name: application-toggles-config + labels: + application: nginx-deployment +spec: + config: + data: + application-toggles-config.json: | + { + "startNewFlow": true + } + schema: + application-toggles-config.json: | + { + "type": "object", + "properties": { + "startNewFlow": { + "type": "boolean" + } + }, + "required": [ + "startNewFlow" + ] + } + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + namespace: local + labels: + app: nginx + config-map-version: "master-10" +spec: + replicas: 2 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + volumeMounts: + - name: application-toggles-config + mountPath: /etc/config + ports: + - containerPort: 80 + volumes: + - name: application-toggles-config + configMap: + name: "application-toggles-config-master-10"