diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c079ed1d2..a35576539a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,32 +22,50 @@ workflows: build-then-test: jobs: - build - - test: + - fabric8_istio: requires: - build + - test: + requires: + - fabric8_istio jobs: test: parallelism: 5 # parallel containers to split the tests among machine: - image: ubuntu-2004:202201-02 + image: ubuntu-2204:2022.04.1 environment: _JAVA_OPTIONS: "-Xms1024m -Xmx2048m" _SERVICE_OCCURENCE: 5 steps: - run: - name: Install OpenJDK 17 + name: testcontainers reuse support command: | - wget -qO - https://adoptium.jfrog.io/adoptium/api/gpg/key/public | sudo apt-key add - - sudo add-apt-repository --yes https://adoptium.jfrog.io/adoptium/deb/ - sudo apt-get update && sudo apt-get install temurin-17-jdk - sudo update-alternatives --set java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java - sudo update-alternatives --set javac /usr/lib/jvm/temurin-17-jdk-amd64/bin/javac - java -version + # needed for .withReuse(true) to work + echo "testcontainers.reuse.enable=true" > ~/.testcontainers.properties - checkout + - attach_workspace: + at: /tmp/docker + - run: + name: Load Controller Images From Workspace + command: | + VIEW=$(ls -l /tmp/docker/images) + echo "${VIEW}" + docker load -i /tmp/docker/images/spring-cloud-kubernetes-configuration-watcher.tar + docker load -i /tmp/docker/images/spring-cloud-kubernetes-discoveryserver.tar + docker load -i /tmp/docker/images/spring-cloud-kubernetes-configserver.tar - run: name: Run regular tests command: | - CLASSNAMES=$(circleci tests glob "**/src/test/**/**.java" | grep -v 'spring-cloud-kubernetes-integration-tests' \ + + ########################################################################################################################### + ################################################# Build test support dependency ########################################### + cd spring-cloud-kubernetes-test-support + .././mvnw clean install + cd .. + + ########################################################################################################################### + ##################################################### Split and run tests ################################################# + CLASSNAMES=$(circleci tests glob "**/src/test/**/**.java" | grep -v 'Fabric8IstioIT' \ | xargs grep -l '@Test' \ | sed 's/.*src.test.java.//g' | sed 's@/@.@g' \ | sed 's/.\{5\}$//' \ @@ -55,25 +73,14 @@ jobs: echo $CLASSNAMES TEST_ARG=$(echo $CLASSNAMES | sed 's/ /,/g') echo $TEST_ARG - ./mvnw -s .settings.xml -DfailIfNoTests=false -DtestsToRun=$TEST_ARG -e clean org.jacoco:jacoco-maven-plugin:prepare-agent test -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn + ./mvnw -s .settings.xml -DfailIfNoTests=false -DtestsToRun=$TEST_ARG -e clean org.jacoco:jacoco-maven-plugin:prepare-agent install \ + -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true \ + -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn mkdir -p $HOME/artifacts/junit/ find . -type f -regex ".*/spring-cloud-*.*/target/*.*" -exec cp {} $HOME/artifacts/ \; find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $HOME/artifacts/junit/ \; bash <(curl -s https://codecov.io/bash) - kube-orb/install-kubectl - - attach_workspace: - at: ./ - - run: - name: Load Controller Images From Workspace - command: | - docker load -i ./docker-images/spring-cloud-kubernetes-configuration-watcher.tar - docker load -i ./docker-images/spring-cloud-kubernetes-discoveryserver.tar - docker load -i ./docker-images/spring-cloud-kubernetes-configserver.tar - - run: - name: Run Kind Integration Tests - command: | - cd spring-cloud-kubernetes-integration-tests - ./run.sh - run: name: "Aggregate test results" when: always @@ -89,20 +96,11 @@ jobs: destination: testartifacts build: machine: - image: ubuntu-2004:202201-02 + image: ubuntu-2204:2022.04.1 environment: _JAVA_OPTIONS: "-Xms2g -Xmx2g" _SERVICE_OCCURENCE: 5 steps: - - run: - name: Install OpenJDK 17 - command: | - wget -qO - https://adoptium.jfrog.io/adoptium/api/gpg/key/public | sudo apt-key add - - sudo add-apt-repository --yes https://adoptium.jfrog.io/adoptium/deb/ - sudo apt-get update && sudo apt-get install temurin-17-jdk - sudo update-alternatives --set java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java - sudo update-alternatives --set javac /usr/lib/jvm/temurin-17-jdk-amd64/bin/javac - java -version - checkout - restore_cache: keys: @@ -122,17 +120,46 @@ jobs: command: | TAG=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) echo $TAG - mkdir docker-images - docker save -o docker-images/spring-cloud-kubernetes-configuration-watcher.tar docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${TAG} - docker save -o docker-images/spring-cloud-kubernetes-discoveryserver.tar docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${TAG} - docker save -o docker-images/spring-cloud-kubernetes-configserver.tar docker.io/springcloud/spring-cloud-kubernetes-configserver:${TAG} + mkdir -p /tmp/docker/images/ + docker save -o /tmp/docker/images/spring-cloud-kubernetes-configuration-watcher.tar docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${TAG} + docker save -o /tmp/docker/images/spring-cloud-kubernetes-discoveryserver.tar docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${TAG} + docker save -o /tmp/docker/images/spring-cloud-kubernetes-configserver.tar docker.io/springcloud/spring-cloud-kubernetes-configserver:${TAG} + VIEW=$(ls -l /tmp/docker/images) + echo "${VIEW}" - persist_to_workspace: - root: ./ - paths: docker-images + root: /tmp/docker/ + paths: + - images - save_cache: paths: - ~/.m2 key: spring-cloud-kubernetes-{{ .Branch }}-{{ checksum "pom.xml" }} + fabric8_istio: + machine: + image: ubuntu-2204:2022.04.1 + steps: + - checkout + - restore_cache: + keys: + - spring-cloud-kubernetes-{{ .Branch }}-{{ checksum "pom.xml" }} + - spring-cloud-kubernetes-{{ .Branch }} + - spring-cloud-kubernetes + - run: + name: Run fabric8 istio test + command: | + + # we need to run some test, so that K3s container is started and then all other instances will re-use this one. + # otherwise (since we use static ports) there might be two instances starting at the same time, and ports might conflict + + ########################################################################################################################### + ######################################## Build test support dependency and Run test ####################################### + cd spring-cloud-kubernetes-test-support + .././mvnw clean install + cd .. + + cd spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/ + ../.././mvnw clean install + cd ../.. notify: webhooks: - url: https://webhooks.gitter.im/e/22e6bb4eb945dd61ba54 diff --git a/.circleci/istio-test-namespace.yml b/.circleci/istio-test-namespace.yml deleted file mode 100644 index a942181185..0000000000 --- a/.circleci/istio-test-namespace.yml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: istio-test - labels: - istio-injection: enabled \ No newline at end of file diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml new file mode 100644 index 0000000000..1fa45cf60f --- /dev/null +++ b/.github/workflows/maven.yaml @@ -0,0 +1,42 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '17' + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + - name: Publish Test Report + uses: mikepenz/action-junit-report@v2 + if: always() # always run even if the previous step fails + with: + report_paths: '**/surefire-reports/TEST-*.xml' + - name: Archive code coverage results + uses: actions/upload-artifact@v2 + with: + name: surefire-reports + path: '**/surefire-reports/*' diff --git a/README.adoc b/README.adoc index 26d66826ec..c95c59b606 100644 --- a/README.adoc +++ b/README.adoc @@ -245,18 +245,30 @@ an `application-profile.properties` or `application-profile.yaml` file that cont application or Spring Boot starters. You can override these properties by specifying system properties or environment variables. +To enable this functionality you need to set `spring.config.import=kubernetes:` in your application's configuration properties. +Currently you can not specify a ConfigMap or Secret to load using `spring.config.import`, by default Spring Cloud Kubernetes +will load a ConfigMap and/or Secret based on the `spring.application.name` property. If `spring.application.name` is not set it will +load a ConfigMap and/or Secret with the name `application`. + +If you would like to load Kubernetes `PropertySource`s during the bootstrap phase like it worked prior to the 3.0.x release +you can either add `spring-cloud-starter-bootstrap` to your application's classpath or set `spring.cloud.bootstrap.enabled=true` +as an environment variable. + [[configmap-propertysource]] === Using a `ConfigMap` `PropertySource` Kubernetes provides a resource named https://kubernetes.io/docs/user-guide/configmap/[`ConfigMap`] to externalize the parameters to pass to your application in the form of key-value pairs or embedded `application.properties` or `application.yaml` files. The link:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-fabric8-config[Spring Cloud Kubernetes Config] project makes Kubernetes `ConfigMap` instances available -during application bootstrapping and triggers hot reloading of beans or Spring context when changes are detected on +during application startup and triggers hot reloading of beans or Spring context when changes are detected on observed `ConfigMap` instances. -The default behavior is to create a `Fabric8ConfigMapPropertySource` based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of +Everything that follows is explained mainly referring to examples using ConfigMaps, but the same stands for +Secrets, i.e.: every feature is supported for both. + +The default behavior is to create a `Fabric8ConfigMapPropertySource` (or a `KubernetesClientConfigMapPropertySource`) based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of your Spring application (as defined by its `spring.application.name` property) or a custom name defined within the -`bootstrap.properties` file under the following key: `spring.cloud.kubernetes.config.name`. +`application.properties` file under the following key: `spring.cloud.kubernetes.config.name`. However, more advanced configuration is possible where you can use multiple `ConfigMap` instances. The `spring.cloud.kubernetes.config.sources` list makes this possible. @@ -293,8 +305,38 @@ of the application is resolved. Any matching `ConfigMap` that is found is processed as follows: * Apply individual configuration properties. -* Apply as `yaml` the content of any property named `application.yaml`. -* Apply as a properties file the content of any property named `application.properties`. +* Apply as `yaml` (or `properties`) the content of any property that is named by the value of `spring.application.name` + (if it's not present, by `application.yaml/properties`) +* Apply as a properties file the content of the above name + each active profile. + +An example should make a lot more sense. Let's suppose that `spring.application.name=my-app` and that +we have a single active profile called `k8s`. For a configuration as below: + + +==== +[source] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: my-app +data: + my-app.yaml: |- + ... + my-app-k8s.yaml: |- + .. + my-app-dev.yaml: |- + .. + someProp: someValue +---- +==== + +These is what we will end-up loading: + + - `my-app.yaml` treated as a file + - `my-app-k8s.yaml` treated as a file + - `my-app-dev.yaml` _ignored_, since `dev` is _not_ an active profile + - `someProp: someValue` plain property The single exception to the aforementioned flow is when the `ConfigMap` contains a *single* key that indicates the file is a YAML or properties file. In that case, the name of the key does NOT have to be `application.yaml` or @@ -366,6 +408,31 @@ data: ---- ==== +You can also define the search to happen based on labels, for example: + + +==== +[source,yaml] +---- +spring: + application: + name: labeled-configmap-with-prefix + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a +---- +==== + +This will search for every configmap in namespace `spring-k8s` that has labels `{letter : a}`. The important +thing to notice here is that unlike reading a configmap by name, this can result in _multiple_ config maps read. +As usual, the same feature is supported for secrets. + You can also configure Spring Boot applications differently depending on active profiles that are merged together when the `ConfigMap` is read. You can provide different property values for different profiles by using an `application.properties` or `application.yaml` property, specifying profile-specific values, each in their own document @@ -479,8 +546,8 @@ data: ==== -To tell Spring Boot which `profile` should be enabled at bootstrap, you can pass `SPRING_PROFILES_ACTIVE` environment variable. - To do so, you can launch your Spring Boot application with an environment variable that you can define it in the PodSpec at the container specification. +To tell Spring Boot which `profile` should be enabled see the https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles[Spring Boot documentation]. +One option for activating a specific profile when deploying to Kubernetes is to launch your Spring Boot application with an environment variable that you can define in the PodSpec at the container specification. Deployment resource file, as follows: ==== @@ -640,6 +707,71 @@ will result in three properties being generated: - `config-map-three.greetings.message` equal to `Say Hello from three`. +The same way you configure a prefix for configmaps, you can do it for secrets also; both for secrets that are based on name +and the ones based on labels. For example: + +==== +[source.yaml] +---- +spring: + application: + name: prefix-based-secrets + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true + - name: my-secret +---- +==== + +The same processing rules apply when generating property source as for config maps. The only difference is that +potentially, looking up secrets by labels can mean that we find more than one source. In such a case, prefix (if specified via `useNameAsPrefix`) +will be the names of all secrets found for those particular labels. + +One more thing to bear in mind is that we support `prefix` per _source_, not per secret. The easiest way to explain this is via an example: + +==== +[source.yaml] +---- +spring: + application: + name: prefix-based-secrets + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + color: blue + useNameAsPrefix: true +---- +==== + +Suppose that a query matching such a label will provide two secrets as a result: `secret-a` and `secret-b`. +Both of these secrets have the same property name: `color=sea-blue` and `color=ocean-blue`. It is undefined which +`color` will end-up as part of property sources, but the prefix for it will be `secret-a.secret-b` +(concatenated sorted naturally, names of the secrets). + +If you need more fine-grained results, adding more labels to identify the secret uniquely would be an option. + + + By default, besides reading the config map that is specified in the `sources` configuration, Spring will also try to read all properties from "profile aware" sources. The easiest way to explain this is via an example. Let's suppose your application enables a profile called "dev" and you have a configuration like the one below: @@ -1102,10 +1234,11 @@ that setting via `spring.main.cloud-platform`. For example, if you need to test some features, but do not want to deploy to a cluster, it is enough to set the: `spring.main.cloud-platform=KUBERNETES`. This will make `spring-cloud-kubernetes` act as-if it is deployed in a real cluster. -Be aware that when `spring-cloud-kubernetes-config` is on the classpath, `spring.main.cloud-platform` should be set in `bootstrap.{properties|yml}` -(or the profile specific one), otherwise it should be in `application.{properties|yml}` (or the profile specific one). -Also note that these properties: `spring.cloud.kubernetes.config.enabled` and `spring.cloud.kubernetes.secrets.enabled` -only take effect when set in `bootstrap.{properties|yml}`. + +NOTE: If you have `spring-cloud-bootstrap-starter` on your classpath or are setting `spring.cloud.bootstrap.enabled=true` then +you will have to set `spring.main.cloud-platform` should be set in `bootstrap.{properties|yml}` +(or the profile specific one). Also note that these properties: `spring.cloud.kubernetes.config.enabled` and `spring.cloud.kubernetes.secrets.enabled` +will only take effect when set in `bootstrap.{properties|yml}` when you have `spring-cloud-bootstrap-starter` on your classpath or are setting `spring.cloud.bootstrap.enabled=true`. === Breaking Changes In 3.0.x @@ -1695,10 +1828,10 @@ the name of the Kubernetes service and service instance information. Below is a ---- ==== -#### `/app/{name}` +#### `/apps/{name}` -A `GET` request to `/app/{name}` can be used to get instance data for all instances of a given -service. Below is a sample response when a `GET` request is made to `/app/kubernetes`. +A `GET` request to `/apps/{name}` can be used to get instance data for all instances of a given +service. Below is a sample response when a `GET` request is made to `/apps/kubernetes`. ==== [source,json] diff --git a/docs/pom.xml b/docs/pom.xml index bdf491ef7d..f1f249bd0e 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -18,6 +18,8 @@ ${basedir}/.. spring.cloud.kubernetes.* deploy + + none diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 5043f759e3..26ad821554 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -2,7 +2,7 @@ |Name | Default | Description |spring.cloud.kubernetes.client.api-version | | -|spring.cloud.kubernetes.client.apiVersion | `v1` | Kubernetes API Version +|spring.cloud.kubernetes.client.apiVersion | `+++v1+++` | Kubernetes API Version |spring.cloud.kubernetes.client.ca-cert-data | | |spring.cloud.kubernetes.client.ca-cert-file | | |spring.cloud.kubernetes.client.caCertData | | Kubernetes API CACertData @@ -15,19 +15,19 @@ |spring.cloud.kubernetes.client.client-key-passphrase | | |spring.cloud.kubernetes.client.clientCertData | | Kubernetes API ClientCertData |spring.cloud.kubernetes.client.clientCertFile | | Kubernetes API ClientCertFile -|spring.cloud.kubernetes.client.clientKeyAlgo | `RSA` | Kubernetes API ClientKeyAlgo +|spring.cloud.kubernetes.client.clientKeyAlgo | `+++RSA+++` | Kubernetes API ClientKeyAlgo |spring.cloud.kubernetes.client.clientKeyData | | Kubernetes API ClientKeyData |spring.cloud.kubernetes.client.clientKeyFile | | Kubernetes API ClientKeyFile -|spring.cloud.kubernetes.client.clientKeyPassphrase | `changeit` | Kubernetes API ClientKeyPassphrase +|spring.cloud.kubernetes.client.clientKeyPassphrase | `+++changeit+++` | Kubernetes API ClientKeyPassphrase |spring.cloud.kubernetes.client.connection-timeout | | -|spring.cloud.kubernetes.client.connectionTimeout | `10s` | Connection timeout +|spring.cloud.kubernetes.client.connectionTimeout | `+++10s+++` | Connection timeout |spring.cloud.kubernetes.client.http-proxy | | |spring.cloud.kubernetes.client.https-proxy | | |spring.cloud.kubernetes.client.logging-interval | | -|spring.cloud.kubernetes.client.loggingInterval | `20s` | Logging interval +|spring.cloud.kubernetes.client.loggingInterval | `+++20s+++` | Logging interval |spring.cloud.kubernetes.client.master-url | | -|spring.cloud.kubernetes.client.masterUrl | `https://kubernetes.default.svc` | Kubernetes API Master Node URL -|spring.cloud.kubernetes.client.namespace | `true` | Kubernetes Namespace +|spring.cloud.kubernetes.client.masterUrl | `+++https://kubernetes.default.svc+++` | Kubernetes API Master Node URL +|spring.cloud.kubernetes.client.namespace | `+++true+++` | Kubernetes Namespace |spring.cloud.kubernetes.client.no-proxy | | |spring.cloud.kubernetes.client.oauth-token | | |spring.cloud.kubernetes.client.oauthToken | | Kubernetes API Oauth Token @@ -35,73 +35,82 @@ |spring.cloud.kubernetes.client.proxy-password | | |spring.cloud.kubernetes.client.proxy-username | | |spring.cloud.kubernetes.client.request-timeout | | -|spring.cloud.kubernetes.client.requestTimeout | `10s` | Request timeout +|spring.cloud.kubernetes.client.requestTimeout | `+++10s+++` | Request timeout |spring.cloud.kubernetes.client.rolling-timeout | | -|spring.cloud.kubernetes.client.rollingTimeout | `900s` | Rolling timeout -|spring.cloud.kubernetes.client.service-account-namespace-path | `/var/run/secrets/kubernetes.io/serviceaccount/namespace` | +|spring.cloud.kubernetes.client.rollingTimeout | `+++900s+++` | Rolling timeout +|spring.cloud.kubernetes.client.service-account-namespace-path | `+++/var/run/secrets/kubernetes.io/serviceaccount/namespace+++` | |spring.cloud.kubernetes.client.trust-certs | | -|spring.cloud.kubernetes.client.trustCerts | `false` | Kubernetes API Trust Certificates -|spring.cloud.kubernetes.client.user-agent | `Spring-Cloud-Kubernetes-Application` | +|spring.cloud.kubernetes.client.trustCerts | `+++false+++` | Kubernetes API Trust Certificates +|spring.cloud.kubernetes.client.user-agent | `+++Spring-Cloud-Kubernetes-Application+++` | |spring.cloud.kubernetes.client.username | | Kubernetes API Username |spring.cloud.kubernetes.client.watch-reconnect-interval | | |spring.cloud.kubernetes.client.watch-reconnect-limit | | -|spring.cloud.kubernetes.client.watchReconnectInterval | `1s` | Reconnect Interval -|spring.cloud.kubernetes.client.watchReconnectLimit | `-1` | Reconnect Interval limit retries -|spring.cloud.kubernetes.config.enable-api | `true` | -|spring.cloud.kubernetes.config.enabled | `true` | Enable the ConfigMap property source locator. -|spring.cloud.kubernetes.config.fail-fast | `false` | -|spring.cloud.kubernetes.config.include-profile-specific-sources | `true` | +|spring.cloud.kubernetes.client.watchReconnectInterval | `+++1s+++` | Reconnect Interval +|spring.cloud.kubernetes.client.watchReconnectLimit | `+++-1+++` | Reconnect Interval limit retries +|spring.cloud.kubernetes.config.enable-api | `+++true+++` | +|spring.cloud.kubernetes.config.enabled | `+++true+++` | Enable the ConfigMap property source locator. +|spring.cloud.kubernetes.config.fail-fast | `+++false+++` | +|spring.cloud.kubernetes.config.include-profile-specific-sources | `+++true+++` | +|spring.cloud.kubernetes.config.labels | | |spring.cloud.kubernetes.config.name | | |spring.cloud.kubernetes.config.namespace | | |spring.cloud.kubernetes.config.paths | | -|spring.cloud.kubernetes.config.retry | | +|spring.cloud.kubernetes.config.retry.enabled | `+++true+++` | +|spring.cloud.kubernetes.config.retry.initial-interval | `+++1000+++` | Initial retry interval in milliseconds. +|spring.cloud.kubernetes.config.retry.max-attempts | `+++6+++` | Maximum number of attempts. +|spring.cloud.kubernetes.config.retry.max-interval | `+++2000+++` | Maximum interval for backoff. +|spring.cloud.kubernetes.config.retry.multiplier | `+++1.1+++` | Multiplier for next interval. |spring.cloud.kubernetes.config.sources | | -|spring.cloud.kubernetes.config.use-name-as-prefix | `false` | -|spring.cloud.kubernetes.discovery.all-namespaces | `false` | If discovering all namespaces. -|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `60` | Timeout for initializing discovery cache, will abort the application if exceeded. -|spring.cloud.kubernetes.discovery.enabled | `true` | If Kubernetes Discovery is enabled. +|spring.cloud.kubernetes.config.use-name-as-prefix | `+++false+++` | +|spring.cloud.kubernetes.discovery.all-namespaces | `+++false+++` | If discovering all namespaces. +|spring.cloud.kubernetes.discovery.cache-loading-timeout-seconds | `+++60+++` | Timeout for initializing discovery cache, will abort the application if exceeded. +|spring.cloud.kubernetes.discovery.enabled | `+++true+++` | If Kubernetes Discovery is enabled. |spring.cloud.kubernetes.discovery.filter | | SpEL expression to filter services AFTER they have been retrieved from the Kubernetes API server. -|spring.cloud.kubernetes.discovery.include-not-ready-addresses | `false` | If endpoint addresses not marked 'ready' by the k8s api server should be discovered. +|spring.cloud.kubernetes.discovery.include-not-ready-addresses | `+++false+++` | If endpoint addresses not marked 'ready' by the k8s api server should be discovered. |spring.cloud.kubernetes.discovery.known-secure-ports | | Set the port numbers that are considered secure and use HTTPS. -|spring.cloud.kubernetes.discovery.metadata.add-annotations | `true` | When set, the Kubernetes annotations of the services will be included as metadata of the returned ServiceInstance. -|spring.cloud.kubernetes.discovery.metadata.add-labels | `true` | When set, the Kubernetes labels of the services will be included as metadata of the returned ServiceInstance. -|spring.cloud.kubernetes.discovery.metadata.add-ports | `true` | When set, any named Kubernetes service ports will be included as metadata of the returned ServiceInstance. +|spring.cloud.kubernetes.discovery.metadata.add-annotations | `+++true+++` | When set, the Kubernetes annotations of the services will be included as metadata of the returned ServiceInstance. +|spring.cloud.kubernetes.discovery.metadata.add-labels | `+++true+++` | When set, the Kubernetes labels of the services will be included as metadata of the returned ServiceInstance. +|spring.cloud.kubernetes.discovery.metadata.add-ports | `+++true+++` | When set, any named Kubernetes service ports will be included as metadata of the returned ServiceInstance. |spring.cloud.kubernetes.discovery.metadata.annotations-prefix | | When addAnnotations is set, then this will be used as a prefix to the key names in the metadata map. |spring.cloud.kubernetes.discovery.metadata.labels-prefix | | When addLabels is set, then this will be used as a prefix to the key names in the metadata map. -|spring.cloud.kubernetes.discovery.metadata.ports-prefix | `port.` | When addPorts is set, then this will be used as a prefix to the key names in the metadata map. +|spring.cloud.kubernetes.discovery.metadata.ports-prefix | `+++port.+++` | When addPorts is set, then this will be used as a prefix to the key names in the metadata map. |spring.cloud.kubernetes.discovery.order | | |spring.cloud.kubernetes.discovery.primary-port-name | | If set then the port with a given name is used as primary when multiple ports are defined for a service. |spring.cloud.kubernetes.discovery.service-labels | | If set, then only the services matching these labels will be fetched from the Kubernetes API server. -|spring.cloud.kubernetes.discovery.wait-cache-ready | `true` | -|spring.cloud.kubernetes.leader.auto-startup | `true` | Should leader election be started automatically on startup. Default: true -|spring.cloud.kubernetes.leader.config-map-name | `leaders` | Kubernetes ConfigMap where leaders information will be stored. Default: leaders -|spring.cloud.kubernetes.leader.enabled | `true` | Should leader election be enabled. Default: true -|spring.cloud.kubernetes.leader.leader-id-prefix | `leader.id.` | Leader id property prefix for the ConfigMap. Default: leader.id. +|spring.cloud.kubernetes.discovery.wait-cache-ready | `+++true+++` | +|spring.cloud.kubernetes.leader.auto-startup | `+++true+++` | Should leader election be started automatically on startup. Default: true +|spring.cloud.kubernetes.leader.config-map-name | `+++leaders+++` | Kubernetes ConfigMap where leaders information will be stored. Default: leaders +|spring.cloud.kubernetes.leader.enabled | `+++true+++` | Should leader election be enabled. Default: true +|spring.cloud.kubernetes.leader.leader-id-prefix | `+++leader.id.+++` | Leader id property prefix for the ConfigMap. Default: leader.id. |spring.cloud.kubernetes.leader.namespace | | Kubernetes namespace where the leaders ConfigMap and candidates are located. -|spring.cloud.kubernetes.leader.publish-failed-events | `false` | Enable/disable publishing events in case leadership acquisition fails. Default: false +|spring.cloud.kubernetes.leader.publish-failed-events | `+++false+++` | Enable/disable publishing events in case leadership acquisition fails. Default: false |spring.cloud.kubernetes.leader.role | | Role for which leadership this candidate will compete. -|spring.cloud.kubernetes.leader.update-period | `60000ms` | Leadership status check period. Default: 60s -|spring.cloud.kubernetes.loadbalancer.cluster-domain | `cluster.local` | cluster domain. -|spring.cloud.kubernetes.loadbalancer.enabled | `true` | Load balancer enabled,default true. +|spring.cloud.kubernetes.leader.update-period | `+++60000ms+++` | Leadership status check period. Default: 60s +|spring.cloud.kubernetes.loadbalancer.cluster-domain | `+++cluster.local+++` | cluster domain. +|spring.cloud.kubernetes.loadbalancer.enabled | `+++true+++` | Load balancer enabled,default true. |spring.cloud.kubernetes.loadbalancer.mode | | {@link KubernetesLoadBalancerMode} setting load balancer server list with ip of pod or service name. default value is POD. -|spring.cloud.kubernetes.loadbalancer.port-name | `http` | service port name. -|spring.cloud.kubernetes.reload.enabled | `false` | Enables the Kubernetes configuration reload on change. -|spring.cloud.kubernetes.reload.max-wait-for-restart | `2s` | If Restart or Shutdown strategies are used, Spring Cloud Kubernetes waits a random amount of time before restarting. This is done in order to avoid having all instances of the same application restart at the same time. This property configures the maximum of amount of wait time from the moment the signal is received that a restart is needed until the moment the restart is actually triggered +|spring.cloud.kubernetes.loadbalancer.port-name | `+++http+++` | service port name. +|spring.cloud.kubernetes.reload.enabled | `+++false+++` | Enables the Kubernetes configuration reload on change. +|spring.cloud.kubernetes.reload.max-wait-for-restart | `+++2s+++` | If Restart or Shutdown strategies are used, Spring Cloud Kubernetes waits a random amount of time before restarting. This is done in order to avoid having all instances of the same application restart at the same time. This property configures the maximum of amount of wait time from the moment the signal is received that a restart is needed until the moment the restart is actually triggered |spring.cloud.kubernetes.reload.mode | | Sets the detection mode for Kubernetes configuration reload. -|spring.cloud.kubernetes.reload.monitoring-config-maps | `true` | Enables monitoring on config maps to detect changes. -|spring.cloud.kubernetes.reload.monitoring-secrets | `false` | Enables monitoring on secrets to detect changes. -|spring.cloud.kubernetes.reload.period | `15000ms` | Sets the polling period to use when the detection mode is POLLING. -|spring.cloud.kubernetes.reload.strategy | | Sets the reload strategy for Kubernetes configuration reload on change. -|spring.cloud.kubernetes.secrets.enable-api | `false` | -|spring.cloud.kubernetes.secrets.enabled | `true` | Enable the Secrets property source locator. -|spring.cloud.kubernetes.secrets.fail-fast | `false` | -|spring.cloud.kubernetes.secrets.include-profile-specific-sources | `true` | +|spring.cloud.kubernetes.reload.monitoring-config-maps | `+++true+++` | Enables monitoring on config maps to detect changes. +|spring.cloud.kubernetes.reload.monitoring-secrets | `+++false+++` | Enables monitoring on secrets to detect changes. +|spring.cloud.kubernetes.reload.period | `+++15000ms+++` | Sets the polling period to use when the detection mode is POLLING. +|spring.cloud.kubernetes.reload.strategy | | Sets reload strategy for Kubernetes configuration reload on change. +|spring.cloud.kubernetes.secrets.enable-api | `+++false+++` | +|spring.cloud.kubernetes.secrets.enabled | `+++true+++` | Enable the Secrets property source locator. +|spring.cloud.kubernetes.secrets.fail-fast | `+++false+++` | +|spring.cloud.kubernetes.secrets.include-profile-specific-sources | `+++true+++` | |spring.cloud.kubernetes.secrets.labels | | |spring.cloud.kubernetes.secrets.name | | |spring.cloud.kubernetes.secrets.namespace | | |spring.cloud.kubernetes.secrets.paths | | -|spring.cloud.kubernetes.secrets.retry | | +|spring.cloud.kubernetes.secrets.retry.enabled | `+++true+++` | +|spring.cloud.kubernetes.secrets.retry.initial-interval | `+++1000+++` | Initial retry interval in milliseconds. +|spring.cloud.kubernetes.secrets.retry.max-attempts | `+++6+++` | Maximum number of attempts. +|spring.cloud.kubernetes.secrets.retry.max-interval | `+++2000+++` | Maximum interval for backoff. +|spring.cloud.kubernetes.secrets.retry.multiplier | `+++1.1+++` | Multiplier for next interval. |spring.cloud.kubernetes.secrets.sources | | -|spring.cloud.kubernetes.secrets.use-name-as-prefix | `false` | +|spring.cloud.kubernetes.secrets.use-name-as-prefix | `+++false+++` | |=== \ No newline at end of file diff --git a/docs/src/main/asciidoc/kubernetes-awareness.adoc b/docs/src/main/asciidoc/kubernetes-awareness.adoc index ea7ee32555..f64d0123ab 100644 --- a/docs/src/main/asciidoc/kubernetes-awareness.adoc +++ b/docs/src/main/asciidoc/kubernetes-awareness.adoc @@ -14,10 +14,11 @@ that setting via `spring.main.cloud-platform`. For example, if you need to test some features, but do not want to deploy to a cluster, it is enough to set the: `spring.main.cloud-platform=KUBERNETES`. This will make `spring-cloud-kubernetes` act as-if it is deployed in a real cluster. -Be aware that when `spring-cloud-kubernetes-config` is on the classpath, `spring.main.cloud-platform` should be set in `bootstrap.{properties|yml}` -(or the profile specific one), otherwise it should be in `application.{properties|yml}` (or the profile specific one). -Also note that these properties: `spring.cloud.kubernetes.config.enabled` and `spring.cloud.kubernetes.secrets.enabled` -only take effect when set in `bootstrap.{properties|yml}`. + +NOTE: If you have `spring-cloud-bootstrap-starter` on your classpath or are setting `spring.cloud.bootstrap.enabled=true` then +you will have to set `spring.main.cloud-platform` should be set in `bootstrap.{properties|yml}` +(or the profile specific one). Also note that these properties: `spring.cloud.kubernetes.config.enabled` and `spring.cloud.kubernetes.secrets.enabled` +will only take effect when set in `bootstrap.{properties|yml}` when you have `spring-cloud-bootstrap-starter` on your classpath or are setting `spring.cloud.bootstrap.enabled=true`. === Breaking Changes In 3.0.x diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index 62eeef34d6..5e6db21f79 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -5,18 +5,30 @@ an `application-profile.properties` or `application-profile.yaml` file that cont application or Spring Boot starters. You can override these properties by specifying system properties or environment variables. +To enable this functionality you need to set `spring.config.import=kubernetes:` in your application's configuration properties. +Currently you can not specify a ConfigMap or Secret to load using `spring.config.import`, by default Spring Cloud Kubernetes +will load a ConfigMap and/or Secret based on the `spring.application.name` property. If `spring.application.name` is not set it will +load a ConfigMap and/or Secret with the name `application`. + +If you would like to load Kubernetes `PropertySource`s during the bootstrap phase like it worked prior to the 3.0.x release +you can either add `spring-cloud-starter-bootstrap` to your application's classpath or set `spring.cloud.bootstrap.enabled=true` +as an environment variable. + [[configmap-propertysource]] === Using a `ConfigMap` `PropertySource` Kubernetes provides a resource named https://kubernetes.io/docs/user-guide/configmap/[`ConfigMap`] to externalize the parameters to pass to your application in the form of key-value pairs or embedded `application.properties` or `application.yaml` files. The link:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-fabric8-config[Spring Cloud Kubernetes Config] project makes Kubernetes `ConfigMap` instances available -during application bootstrapping and triggers hot reloading of beans or Spring context when changes are detected on +during application startup and triggers hot reloading of beans or Spring context when changes are detected on observed `ConfigMap` instances. -The default behavior is to create a `Fabric8ConfigMapPropertySource` based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of +Everything that follows is explained mainly referring to examples using ConfigMaps, but the same stands for +Secrets, i.e.: every feature is supported for both. + +The default behavior is to create a `Fabric8ConfigMapPropertySource` (or a `KubernetesClientConfigMapPropertySource`) based on a Kubernetes `ConfigMap` that has a `metadata.name` value of either the name of your Spring application (as defined by its `spring.application.name` property) or a custom name defined within the -`bootstrap.properties` file under the following key: `spring.cloud.kubernetes.config.name`. +`application.properties` file under the following key: `spring.cloud.kubernetes.config.name`. However, more advanced configuration is possible where you can use multiple `ConfigMap` instances. The `spring.cloud.kubernetes.config.sources` list makes this possible. @@ -53,8 +65,38 @@ of the application is resolved. Any matching `ConfigMap` that is found is processed as follows: * Apply individual configuration properties. -* Apply as `yaml` the content of any property named `application.yaml`. -* Apply as a properties file the content of any property named `application.properties`. +* Apply as `yaml` (or `properties`) the content of any property that is named by the value of `spring.application.name` + (if it's not present, by `application.yaml/properties`) +* Apply as a properties file the content of the above name + each active profile. + +An example should make a lot more sense. Let's suppose that `spring.application.name=my-app` and that +we have a single active profile called `k8s`. For a configuration as below: + + +==== +[source] +---- +kind: ConfigMap +apiVersion: v1 +metadata: + name: my-app +data: + my-app.yaml: |- + ... + my-app-k8s.yaml: |- + .. + my-app-dev.yaml: |- + .. + someProp: someValue +---- +==== + +These is what we will end-up loading: + + - `my-app.yaml` treated as a file + - `my-app-k8s.yaml` treated as a file + - `my-app-dev.yaml` _ignored_, since `dev` is _not_ an active profile + - `someProp: someValue` plain property The single exception to the aforementioned flow is when the `ConfigMap` contains a *single* key that indicates the file is a YAML or properties file. In that case, the name of the key does NOT have to be `application.yaml` or @@ -126,6 +168,31 @@ data: ---- ==== +You can also define the search to happen based on labels, for example: + + +==== +[source,yaml] +---- +spring: + application: + name: labeled-configmap-with-prefix + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a +---- +==== + +This will search for every configmap in namespace `spring-k8s` that has labels `{letter : a}`. The important +thing to notice here is that unlike reading a configmap by name, this can result in _multiple_ config maps read. +As usual, the same feature is supported for secrets. + You can also configure Spring Boot applications differently depending on active profiles that are merged together when the `ConfigMap` is read. You can provide different property values for different profiles by using an `application.properties` or `application.yaml` property, specifying profile-specific values, each in their own document @@ -239,8 +306,8 @@ data: ==== -To tell Spring Boot which `profile` should be enabled at bootstrap, you can pass `SPRING_PROFILES_ACTIVE` environment variable. - To do so, you can launch your Spring Boot application with an environment variable that you can define it in the PodSpec at the container specification. +To tell Spring Boot which `profile` should be enabled see the https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles[Spring Boot documentation]. +One option for activating a specific profile when deploying to Kubernetes is to launch your Spring Boot application with an environment variable that you can define in the PodSpec at the container specification. Deployment resource file, as follows: ==== @@ -400,6 +467,71 @@ will result in three properties being generated: - `config-map-three.greetings.message` equal to `Say Hello from three`. +The same way you configure a prefix for configmaps, you can do it for secrets also; both for secrets that are based on name +and the ones based on labels. For example: + +==== +[source.yaml] +---- +spring: + application: + name: prefix-based-secrets + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true + - name: my-secret +---- +==== + +The same processing rules apply when generating property source as for config maps. The only difference is that +potentially, looking up secrets by labels can mean that we find more than one source. In such a case, prefix (if specified via `useNameAsPrefix`) +will be the names of all secrets found for those particular labels. + +One more thing to bear in mind is that we support `prefix` per _source_, not per secret. The easiest way to explain this is via an example: + +==== +[source.yaml] +---- +spring: + application: + name: prefix-based-secrets + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + color: blue + useNameAsPrefix: true +---- +==== + +Suppose that a query matching such a label will provide two secrets as a result: `secret-a` and `secret-b`. +Both of these secrets have the same property name: `color=sea-blue` and `color=ocean-blue`. It is undefined which +`color` will end-up as part of property sources, but the prefix for it will be `secret-a.secret-b` +(concatenated sorted naturally, names of the secrets). + +If you need more fine-grained results, adding more labels to identify the secret uniquely would be an option. + + + By default, besides reading the config map that is specified in the `sources` configuration, Spring will also try to read all properties from "profile aware" sources. The easiest way to explain this is via an example. Let's suppose your application enables a profile called "dev" and you have a configuration like the one below: diff --git a/docs/src/main/asciidoc/spring-cloud-kubernetes-discoveryserver.adoc b/docs/src/main/asciidoc/spring-cloud-kubernetes-discoveryserver.adoc index 08aa768fed..8a7e163d9e 100644 --- a/docs/src/main/asciidoc/spring-cloud-kubernetes-discoveryserver.adoc +++ b/docs/src/main/asciidoc/spring-cloud-kubernetes-discoveryserver.adoc @@ -69,10 +69,10 @@ the name of the Kubernetes service and service instance information. Below is a ---- ==== -#### `/app/{name}` +#### `/apps/{name}` -A `GET` request to `/app/{name}` can be used to get instance data for all instances of a given -service. Below is a sample response when a `GET` request is made to `/app/kubernetes`. +A `GET` request to `/apps/{name}` can be used to get instance data for all instances of a given +service. Below is a sample response when a `GET` request is made to `/apps/kubernetes`. ==== [source,json] diff --git a/istio-cli/istio-1.13.3/bin/istioctl b/istio-cli/istio-1.13.3/bin/istioctl new file mode 100755 index 0000000000..0319896aab Binary files /dev/null and b/istio-cli/istio-1.13.3/bin/istioctl differ diff --git a/pom.xml b/pom.xml index 6b558734f1..ced5fbc136 100644 --- a/pom.xml +++ b/pom.xml @@ -76,14 +76,11 @@ 2.4.12 3.0.2 - **/*IT.java - true true true - **/*IT.java @@ -199,14 +196,9 @@ all false - - ${testsToRun} - - ${excludeITTests} - @@ -359,16 +351,6 @@ all false - - - - ${testsToRun} - - - ${surefireArgLine} - - ${excludeITTests} - diff --git a/scripts/integration-tests.sh b/scripts/integration-tests.sh index bdc887d909..4d5fa33886 100755 --- a/scripts/integration-tests.sh +++ b/scripts/integration-tests.sh @@ -2,6 +2,4 @@ set -e ./mvnw clean install -B -Pdocs ${@} -cd spring-cloud-kubernetes-integration-tests -./run.sh ${@} diff --git a/spring-cloud-kubernetes-client-config/pom.xml b/spring-cloud-kubernetes-client-config/pom.xml index f54aa48a5f..eba0fe4ede 100644 --- a/spring-cloud-kubernetes-client-config/pom.xml +++ b/spring-cloud-kubernetes-client-config/pom.xml @@ -53,10 +53,6 @@ spring-cloud-starter - - org.springframework.cloud - spring-cloud-starter-bootstrap - org.springframework.retry spring-retry diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigDataLocationResolver.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigDataLocationResolver.java new file mode 100644 index 0000000000..a9fb96ea23 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigDataLocationResolver.java @@ -0,0 +1,148 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; + +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.Profiles; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.cloud.kubernetes.client.KubernetesClientPodUtils; +import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableSecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.KubernetesConfigDataLocationResolver; +import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; +import org.springframework.core.env.Environment; + +import static org.springframework.cloud.kubernetes.client.KubernetesClientUtils.kubernetesApiClient; + +/** + * @author Ryan Baxter + */ +public class KubernetesClientConfigDataLocationResolver extends KubernetesConfigDataLocationResolver { + + public KubernetesClientConfigDataLocationResolver(DeferredLogFactory factory) { + super(factory); + } + + @Override + protected void registerBeans(ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, + Profiles profiles, KubernetesConfigDataLocationResolver.PropertyHolder propertyHolder, + KubernetesNamespaceProvider namespaceProvider) { + ConfigMapConfigProperties configMapProperties = propertyHolder.configMapConfigProperties(); + SecretsConfigProperties secretsProperties = propertyHolder.secretsProperties(); + + ConfigurableBootstrapContext bootstrapContext = resolverContext.getBootstrapContext(); + ApiClient apiClient = kubernetesApiClient(); + bootstrapContext.registerIfAbsent(ApiClient.class, InstanceSupplier.of(apiClient)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configDataApiClient", event.getBootstrapContext().get(ApiClient.class))); + + CoreV1Api coreV1Api = coreApi(apiClient); + bootstrapContext.registerIfAbsent(CoreV1Api.class, InstanceSupplier.of(coreV1Api)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configCoreV1Api", event.getBootstrapContext().get(CoreV1Api.class))); + + if (isRetryEnabled(configMapProperties, secretsProperties)) { + registerRetryBeans(configMapProperties, secretsProperties, bootstrapContext, coreV1Api, namespaceProvider); + } + else { + if (configMapProperties.isEnabled()) { + KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator = new KubernetesClientConfigMapPropertySourceLocator( + coreV1Api, configMapProperties, namespaceProvider); + bootstrapContext.registerIfAbsent(ConfigMapPropertySourceLocator.class, + InstanceSupplier.of(configMapPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configDataConfigMapPropertySourceLocator", + event.getBootstrapContext().get(ConfigMapPropertySourceLocator.class))); + } + + if (secretsProperties.isEnabled()) { + KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator = new KubernetesClientSecretsPropertySourceLocator( + coreV1Api, namespaceProvider, secretsProperties); + bootstrapContext.registerIfAbsent(SecretsPropertySourceLocator.class, + InstanceSupplier.of(secretsPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configDataSecretsPropertySourceLocator", + event.getBootstrapContext().get(SecretsPropertySourceLocator.class))); + } + } + } + + private void registerRetryBeans(ConfigMapConfigProperties configMapProperties, + SecretsConfigProperties secretsProperties, ConfigurableBootstrapContext bootstrapContext, + CoreV1Api coreV1Api, KubernetesNamespaceProvider namespaceProvider) { + if (configMapProperties.isEnabled()) { + ConfigMapPropertySourceLocator configMapPropertySourceLocator = new KubernetesClientConfigMapPropertySourceLocator( + coreV1Api, configMapProperties, namespaceProvider); + if (isRetryEnabledForConfigMap(configMapProperties)) { + configMapPropertySourceLocator = new ConfigDataRetryableConfigMapPropertySourceLocator( + configMapPropertySourceLocator, configMapProperties); + } + + bootstrapContext.registerIfAbsent(ConfigMapPropertySourceLocator.class, + InstanceSupplier.of(configMapPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory().registerSingleton( + "configDataConfigMapPropertySourceLocator", + event.getBootstrapContext().get(ConfigMapPropertySourceLocator.class))); + } + + if (secretsProperties.isEnabled()) { + SecretsPropertySourceLocator secretsPropertySourceLocator = new KubernetesClientSecretsPropertySourceLocator( + coreV1Api, namespaceProvider, secretsProperties); + if (isRetryEnabledForSecrets(secretsProperties)) { + secretsPropertySourceLocator = new ConfigDataRetryableSecretsPropertySourceLocator( + secretsPropertySourceLocator, secretsProperties); + } + + bootstrapContext.registerIfAbsent(SecretsPropertySourceLocator.class, + InstanceSupplier.of(secretsPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory().registerSingleton( + "configDataSecretsPropertySourceLocator", + event.getBootstrapContext().get(SecretsPropertySourceLocator.class))); + } + } + + protected ApiClient apiClient(KubernetesClientProperties properties) { + ApiClient apiClient = kubernetesApiClient(); + apiClient.setUserAgent(properties.getUserAgent()); + return apiClient; + } + + protected CoreV1Api coreApi(ApiClient apiClient) { + return new CoreV1Api(apiClient); + } + + protected KubernetesNamespaceProvider kubernetesNamespaceProvider(Environment environment) { + return new KubernetesNamespaceProvider(environment); + } + + protected KubernetesClientPodUtils kubernetesPodUtils(CoreV1Api client, + KubernetesNamespaceProvider kubernetesNamespaceProvider) { + return new KubernetesClientPodUtils(client, kubernetesNamespaceProvider.getNamespace()); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySource.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySource.java index 46cd1c2347..d66f16d582 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySource.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySource.java @@ -19,24 +19,22 @@ import java.util.EnumMap; import java.util.Optional; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSourceType; import org.springframework.cloud.kubernetes.commons.config.SourceData; +import org.springframework.cloud.kubernetes.commons.config.SourceDataEntriesProcessor; /** * @author Ryan Baxter * @author Isik Erhan */ -public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySource { +public class KubernetesClientConfigMapPropertySource extends SourceDataEntriesProcessor { private static final EnumMap STRATEGIES = new EnumMap<>( NormalizedSourceType.class); - // there is a single strategy here at the moment (unlike secrets), - // but this can change. - // to be on par with secrets implementation, I am keeping it the same static { STRATEGIES.put(NormalizedSourceType.NAMED_CONFIG_MAP, namedConfigMap()); + STRATEGIES.put(NormalizedSourceType.LABELED_CONFIG_MAP, labeledConfigMap()); } public KubernetesClientConfigMapPropertySource(KubernetesClientConfigContext context) { @@ -49,11 +47,12 @@ private static SourceData getSourceData(KubernetesClientConfigContext context) { .orElseThrow(() -> new IllegalArgumentException("no strategy found for : " + type)); } - // we need to pass various functions because the code we are interested in - // is protected in ConfigMapPropertySource, and must stay that way. private static KubernetesClientContextToSourceData namedConfigMap() { - return NamedConfigMapContextToSourceDataProvider.of(ConfigMapPropertySource::processAllEntries, - ConfigMapPropertySource::getSourceName, ConfigMapPropertySource::withPrefix).get(); + return new NamedConfigMapContextToSourceDataProvider().get(); + } + + private static KubernetesClientContextToSourceData labeledConfigMap() { + return new LabeledConfigMapContextToSourceDataProvider().get(); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java index 9bee82d0c2..857cb352eb 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigUtils.java @@ -16,17 +16,24 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.Base64; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1Secret; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; -import org.springframework.util.CollectionUtils; +import org.springframework.cloud.kubernetes.commons.config.StrippedSourceContainer; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; /** @@ -36,6 +43,10 @@ public final class KubernetesClientConfigUtils { private static final Log LOG = LogFactory.getLog(KubernetesClientConfigUtils.class); + // k8s-native client already returns data from secrets as being decoded + // this flags makes sure we use it everywhere + private static final boolean DECODE = Boolean.FALSE; + private KubernetesClientConfigUtils() { } @@ -78,23 +89,123 @@ static String getApplicationNamespace(String namespace, String configurationTarg } /** - * return decoded data from a secret within a namespace. + *
+	 *     1. read all secrets in the provided namespace
+	 *     2. from the above, filter the ones that we care about (filter by labels)
+	 *     3. with secret names from (2), find out if there are any profile based secrets (if profiles is not empty)
+	 *     4. concat (2) and (3) and these are the secrets we are interested in
+	 *     5. see if any of the secrets from (4) has a single yaml/properties file
+	 *     6. gather all the names of the secrets (from 4) + data they hold
+	 * 
+ */ + static MultipleSourcesContainer secretsDataByLabels(CoreV1Api client, String namespace, Map labels, + Environment environment, Set profiles) { + List secrets = secretsSearch(client, namespace); + if (ConfigUtils.noSources(secrets, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedSecrets(secrets); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, DECODE); + + } + + /** + *
+	 *     1. read all config maps in the provided namespace
+	 *     2. from the above, filter the ones that we care about (filter by labels)
+	 *     3. with config maps names from (2), find out if there are any profile based ones (if profiles is not empty)
+	 *     4. concat (2) and (3) and these are the config maps we are interested in
+	 *     5. see if any from (4) has a single yaml/properties file
+	 *     6. gather all the names of the config maps (from 4) + data they hold
+	 * 
+ */ + static MultipleSourcesContainer configMapsDataByLabels(CoreV1Api client, String namespace, + Map labels, Environment environment, Set profiles) { + + List configMaps = configMapsSearch(client, namespace); + if (ConfigUtils.noSources(configMaps, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedConfigMaps(configMaps); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, DECODE); + } + + /** + *
+	 *     1. read all secrets in the provided namespace
+	 *     2. from the above, filter the ones that we care about (by name)
+	 *     3. see if any of the secrets has a single yaml/properties file
+	 *     4. gather all the names of the secrets + decoded data they hold
+	 * 
*/ - static Map dataFromSecret(V1Secret secret, String namespace) { - LOG.debug("reading secret with name : " + secret.getMetadata().getName() + " in namespace : " + namespace); - Map data = secret.getData(); + static MultipleSourcesContainer secretsDataByName(CoreV1Api client, String namespace, Set sourceNames, + Environment environment) { + List secrets = secretsSearch(client, namespace); + if (ConfigUtils.noSources(secrets, namespace)) { + return MultipleSourcesContainer.empty(); + } - Map result = new HashMap<>(CollectionUtils.newHashMap(data.size())); - data.forEach((k, v) -> { - String decodedValue = decoded(v); - result.put(k, decodedValue); - }); + List strippedSources = strippedSecrets(secrets); + return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, DECODE); + + } + + /** + *
+	 *     1. read all config maps in the provided namespace
+	 *     2. from the above, filter the ones that we care about (by name)
+	 *     3. see if any of the config maps has a single yaml/properties file
+	 *     4. gather all the names of the config maps + data they hold
+	 * 
+ */ + static MultipleSourcesContainer configMapsDataByName(CoreV1Api client, String namespace, Set sourceNames, + Environment environment) { + List configMaps = configMapsSearch(client, namespace); + if (ConfigUtils.noSources(configMaps, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedConfigMaps(configMaps); + return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, DECODE); + + } + + private static List secretsSearch(CoreV1Api client, String namespace) { + LOG.debug("Loading all secrets in namespace '" + namespace + "'"); + try { + return client.listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null) + .getItems(); + } + catch (ApiException apiException) { + throw new RuntimeException(apiException.getResponseBody(), apiException); + } + } + + private static List configMapsSearch(CoreV1Api client, String namespace) { + LOG.debug("Loading all config maps in namespace '" + namespace + "'"); + try { + return client.listNamespacedConfigMap(namespace, null, null, null, null, null, null, null, null, null, null) + .getItems(); + } + catch (ApiException apiException) { + throw new RuntimeException(apiException.getResponseBody(), apiException); + } + } + + private static List strippedSecrets(List secrets) { + return secrets.stream().map(secret -> new StrippedSourceContainer(secret.getMetadata().getLabels(), + secret.getMetadata().getName(), transform(secret.getData()))).collect(Collectors.toList()); + } - return result; + private static List strippedConfigMaps(List configMaps) { + return configMaps.stream().map(configMap -> new StrippedSourceContainer(configMap.getMetadata().getLabels(), + configMap.getMetadata().getName(), configMap.getData())).collect(Collectors.toList()); } - private static String decoded(byte[] value) { - return new String(Base64.getDecoder().decode(Base64.getEncoder().encodeToString(value))).trim(); + private static Map transform(Map in) { + return in.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, en -> new String(en.getValue()))); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySource.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySource.java index fa608c9603..642386f604 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySource.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySource.java @@ -52,11 +52,11 @@ private static SourceData getSourceData(KubernetesClientConfigContext context) { } private static KubernetesClientContextToSourceData namedSecret() { - return NamedSecretContextToSourceDataProvider.of(SecretsPropertySource::getSourceName).get(); + return new NamedSecretContextToSourceDataProvider().get(); } private static KubernetesClientContextToSourceData labeledSecret() { - return LabeledSecretContextToSourceDataProvider.of(SecretsPropertySource::getSourceName).get(); + return new LabeledSecretContextToSourceDataProvider().get(); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProvider.java new file mode 100644 index 0000000000..6d4381ffd2 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config; + +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; + +class LabeledConfigMapContextToSourceDataProvider implements Supplier { + + LabeledConfigMapContextToSourceDataProvider() { + } + + /* + * Computes a ContextSourceData (think content) for configmap(s) based on some labels. + * There could be many sources that are read based on incoming labels, for which we + * will be computing a single Map in the end. + * + * If there is no config maps found for the provided labels, we will return an "empty" + * SourceData. Its name is going to be the concatenated labels mapped to an empty Map. + * + * If we find config maps(s) for the provided labels, its name is going to be the + * concatenated names mapped to the data they hold as a Map. + */ + @Override + public KubernetesClientContextToSourceData get() { + + return context -> { + + LabeledConfigMapNormalizedSource source = (LabeledConfigMapNormalizedSource) context.normalizedSource(); + + return new LabeledSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Map labels, Set profiles) { + return KubernetesClientConfigUtils.configMapsDataByLabels(context.client(), context.namespace(), + labels, context.environment(), profiles); + } + + }.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); + }; + + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java index e5269d8ed4..9cf72cbc0b 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProvider.java @@ -16,25 +16,13 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.function.BiFunction; +import java.util.Set; import java.util.function.Supplier; -import java.util.stream.Collectors; - -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.openapi.models.V1Secret; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; - -import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.dataFromSecret; -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; -import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; +import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; /** * Provides an implementation of {@link KubernetesClientContextToSourceData} for a labeled @@ -44,16 +32,8 @@ */ final class LabeledSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - - private final BiFunction sourceNameMapper; + LabeledSecretContextToSourceDataProvider() { - private LabeledSecretContextToSourceDataProvider(BiFunction sourceNameFunction) { - this.sourceNameMapper = Objects.requireNonNull(sourceNameFunction); - } - - static LabeledSecretContextToSourceDataProvider of(BiFunction sourceNameFunction) { - return new LabeledSecretContextToSourceDataProvider(sourceNameFunction); } /* @@ -71,38 +51,18 @@ static LabeledSecretContextToSourceDataProvider of(BiFunction { - Map result = new HashMap<>(); LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource(); - Map labels = source.labels(); - String namespace = context.namespace(); - String sourceName = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet()); - - try { - LOG.info("Loading Secret with labels '" + labels + "' in namespace '" + namespace + "'"); - List secrets = context.client().listNamespacedSecret(namespace, null, null, null, null, - createLabelsSelector(labels), null, null, null, null, null).getItems(); - - if (!secrets.isEmpty()) { - sourceName = secrets.stream().map(V1Secret::getMetadata).map(V1ObjectMeta::getName) - .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); - - secrets.forEach(s -> result.putAll(dataFromSecret(s, namespace))); + return new LabeledSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Map labels, Set profiles) { + return KubernetesClientConfigUtils.secretsDataByLabels(context.client(), context.namespace(), + labels, context.environment(), profiles); } - } - catch (Exception e) { - String message = "Unable to read Secret with labels [" + labels + "] in namespace '" + namespace + "'"; - onException(source.failFast(), message, e); - } - - String propertySourceName = sourceNameMapper.apply(sourceName, namespace); - return new SourceData(propertySourceName, result); + }.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; } - private static String createLabelsSelector(Map labels) { - return labels.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining(",")); - } - } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java index 33722890f4..749e08a4d2 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProvider.java @@ -16,31 +16,12 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; -import io.kubernetes.client.openapi.ApiException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPrefixContext; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; - -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.getApplicationName; -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; -import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; +import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; /** * Provides an implementation of {@link KubernetesClientContextToSourceData} for a named @@ -50,28 +31,7 @@ */ final class NamedConfigMapContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedConfigMapContextToSourceDataProvider.class); - - private final BiFunction, Environment, Map> entriesProcessor; - - private final BiFunction sourceNameMapper; - - private final Function withPrefix; - - private NamedConfigMapContextToSourceDataProvider( - BiFunction, Environment, Map> entriesProcessor, - BiFunction sourceNameMapper, - Function withPrefix) { - this.entriesProcessor = Objects.requireNonNull(entriesProcessor); - this.sourceNameMapper = Objects.requireNonNull(sourceNameMapper); - this.withPrefix = Objects.requireNonNull(withPrefix); - } - - static NamedConfigMapContextToSourceDataProvider of( - BiFunction, Environment, Map> entriesProcessor, - BiFunction sourceNameMapper, - Function withPrefix) { - return new NamedConfigMapContextToSourceDataProvider(entriesProcessor, sourceNameMapper, withPrefix); + NamedConfigMapContextToSourceDataProvider() { } @Override @@ -80,65 +40,17 @@ public KubernetesClientContextToSourceData get() { return context -> { NamedConfigMapNormalizedSource source = (NamedConfigMapNormalizedSource) context.normalizedSource(); - // namespace has to be read from context, not from the normalized source - String namespace = context.namespace(); - Environment environment = context.environment(); - String configMapName = appName(environment, source).get(); - Set propertySourceNames = new LinkedHashSet<>(); - propertySourceNames.add(configMapName); - Map result = new HashMap<>(); - try { - Set names = new HashSet<>(); - names.add(configMapName); - if (environment != null && source.profileSpecificSources()) { - for (String activeProfile : environment.getActiveProfiles()) { - names.add(configMapName + "-" + activeProfile); - } + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return KubernetesClientConfigUtils.configMapsDataByName(context.client(), context.namespace(), + sourceNames, context.environment()); } - - /* - * 1. read all config maps in the namespace 2. filter the ones that match - * user supplied name + profile specific ones 3. get an Entry that - * contains the data from config map + its name 4. create a single map - * that contains all the data from all config maps; also create a unified - * name of the property source (combined config map names). - */ - context.client() - .listNamespacedConfigMap(namespace, null, null, null, null, null, null, null, null, null, null) - .getItems().stream().filter(cm -> names.contains(cm.getMetadata().getName())) - .map(cm -> new AbstractMap.SimpleEntry<>(entriesProcessor.apply(cm.getData(), environment), - cm.getMetadata().getName())) - .collect(Collectors.toList()).forEach(entry -> { - LOG.info("Loaded config map with name : '" + entry.getValue() + "' in namespace : '" - + namespace + "'"); - result.putAll(entry.getKey()); - propertySourceNames.add(entry.getValue()); - }); - - if (!"".equals(source.prefix())) { - ConfigMapPrefixContext prefixContext = new ConfigMapPrefixContext(result, source.prefix(), - namespace, propertySourceNames); - return withPrefix.apply(prefixContext); - } - - } - catch (ApiException e) { - // we could make the error tell exactly what config map we could not read, - // this would mean separate calls to kubeapi server via the client, - // though. - String message = "Unable to read ConfigMap(s) in namespace '" + namespace + "'"; - onException(source.failFast(), message, e); - } - - String propertySourceTokens = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames); - return new SourceData(sourceNameMapper.apply(propertySourceTokens, namespace), result); + }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; } - private Supplier appName(Environment environment, NormalizedSource normalizedSource) { - return () -> getApplicationName(environment, normalizedSource.name().orElse(null), normalizedSource.target()); - } - } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java index 3211b5ef7b..12469c997c 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java @@ -16,22 +16,12 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; +import java.util.Set; import java.util.function.Supplier; -import io.kubernetes.client.openapi.models.V1Secret; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; - -import static org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigUtils.dataFromSecret; -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; +import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; /** * Provides an implementation of {@link KubernetesClientContextToSourceData} for a named @@ -41,16 +31,7 @@ */ final class NamedSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedSecretContextToSourceDataProvider.class); - - private final BiFunction sourceNameMapper; - - private NamedSecretContextToSourceDataProvider(BiFunction sourceNameFunction) { - this.sourceNameMapper = Objects.requireNonNull(sourceNameFunction); - } - - static NamedSecretContextToSourceDataProvider of(BiFunction sourceNameFunction) { - return new NamedSecretContextToSourceDataProvider(sourceNameFunction); + NamedSecretContextToSourceDataProvider() { } @Override @@ -59,31 +40,14 @@ public KubernetesClientContextToSourceData get() { NamedSecretNormalizedSource source = (NamedSecretNormalizedSource) context.normalizedSource(); - Map result = new HashMap<>(); - String namespace = context.namespace(); - // error should never be thrown here, since we always expect a name - // explicit or implicit - String name = source.name().orElseThrow(); - - try { - - LOG.info("Loading Secret with name '" + name + "' in namespace '" + namespace + "'"); - Optional secret; - secret = context.client() - .listNamespacedSecret(namespace, null, null, null, null, null, null, null, null, null, null) - .getItems().stream().filter(s -> name.equals(s.getMetadata().getName())).findFirst(); - - secret.ifPresent(s -> result.putAll(dataFromSecret(s, namespace))); - - } - catch (Exception e) { - String message = "Unable to read Secret with name '" + name + "' in namespace '" + namespace + "'"; - onException(source.failFast(), message, e); - } - - String propertySourceName = sourceNameMapper.apply(name, namespace); - return new SourceData(propertySourceName, result); - + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(), + sourceNames, context.environment()); + } + }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientConfigReloadAutoConfiguration.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientConfigReloadAutoConfiguration.java index eb1f9e3398..896defdceb 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientConfigReloadAutoConfiguration.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientConfigReloadAutoConfiguration.java @@ -23,7 +23,6 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration; @@ -42,111 +41,106 @@ import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector; import org.springframework.cloud.kubernetes.commons.config.reload.PollingSecretsChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.condition.ConditionalOnKubernetesReloadEnabled; import org.springframework.cloud.kubernetes.commons.config.reload.condition.EventReloadDetectionMode; import org.springframework.cloud.kubernetes.commons.config.reload.condition.PollingReloadDetectionMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.AbstractEnvironment; +import org.springframework.scheduling.TaskScheduler; /** * @author Ryan Baxter */ @Configuration(proxyBeanMethods = false) @ConditionalOnKubernetesAndConfigEnabled -@ConditionalOnClass(EndpointAutoConfiguration.class) +@ConditionalOnKubernetesReloadEnabled +@ConditionalOnClass({ EndpointAutoConfiguration.class, RestartEndpoint.class, ContextRefresher.class }) @AutoConfigureAfter({ InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class, - RefreshAutoConfiguration.class, ConfigReloadAutoConfiguration.class }) + RefreshAutoConfiguration.class }) @EnableConfigurationProperties(ConfigReloadProperties.class) +@Import(ConfigReloadAutoConfiguration.class) public class KubernetesClientConfigReloadAutoConfiguration { /** - * Configuration reload must be enabled explicitly. + * Polling configMap ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param configMapPropertySourceLocator configMap property source locator + * @return a bean that listen to configuration changes and fire a reload. */ - @ConditionalOnProperty("spring.cloud.kubernetes.reload.enabled") - @ConditionalOnClass({ RestartEndpoint.class, ContextRefresher.class }) - protected static class ConfigReloadAutoConfigurationBeans { + @Bean + @ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class) + @Conditional(PollingReloadDetectionMode.class) + public ConfigurationChangeDetector configMapPropertyChangePollingWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator, + AbstractEnvironment environment, TaskSchedulerWrapper taskScheduler) { - /** - * Polling configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param configMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator, - AbstractEnvironment environment, TaskSchedulerWrapper taskScheduler) { - - return new PollingConfigMapChangeDetector(environment, properties, strategy, - KubernetesClientConfigMapPropertySource.class, configMapPropertySourceLocator, - taskScheduler.getTaskScheduler()); - } - - /** - * Polling secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param secretsPropertySourceLocator secrets property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator, - AbstractEnvironment environment, TaskSchedulerWrapper taskScheduler) { + return new PollingConfigMapChangeDetector(environment, properties, strategy, + KubernetesClientConfigMapPropertySource.class, configMapPropertySourceLocator, + taskScheduler.getTaskScheduler()); + } - return new PollingSecretsChangeDetector(environment, properties, strategy, - KubernetesClientSecretsPropertySource.class, secretsPropertySourceLocator, - taskScheduler.getTaskScheduler()); - } + /** + * Polling secrets ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param secretsPropertySourceLocator secrets property source locator + * @return a bean that listen to configuration changes and fire a reload. + */ + @Bean + @ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class) + @Conditional(PollingReloadDetectionMode.class) + public ConfigurationChangeDetector secretsPropertyChangePollingWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator, AbstractEnvironment environment, + TaskSchedulerWrapper taskScheduler) { - /** - * Event Based configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param configMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configMap change events and fire a reload. - */ - @Bean - @ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator, - AbstractEnvironment environment, CoreV1Api coreV1Api, - KubernetesNamespaceProvider kubernetesNamespaceProvider) { + return new PollingSecretsChangeDetector(environment, properties, strategy, + KubernetesClientSecretsPropertySource.class, secretsPropertySourceLocator, + taskScheduler.getTaskScheduler()); + } - return new KubernetesClientEventBasedConfigMapChangeDetector(coreV1Api, environment, properties, strategy, - configMapPropertySourceLocator, kubernetesNamespaceProvider); - } + /** + * Event Based configMap ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param configMapPropertySourceLocator configMap property source locator + * @return a bean that listen to configMap change events and fire a reload. + */ + @Bean + @ConditionalOnBean(KubernetesClientConfigMapPropertySourceLocator.class) + @Conditional(EventReloadDetectionMode.class) + public ConfigurationChangeDetector configMapPropertyChangeEventWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + KubernetesClientConfigMapPropertySourceLocator configMapPropertySourceLocator, + AbstractEnvironment environment, CoreV1Api coreV1Api, + KubernetesNamespaceProvider kubernetesNamespaceProvider) { - /** - * Event Based secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param secretsPropertySourceLocator secrets property source locator - * @return a bean that listen to secrets change events and fire a reload. - */ - @Bean - @ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator, - AbstractEnvironment environment, CoreV1Api coreV1Api, - KubernetesNamespaceProvider kubernetesNamespaceProvider) { + return new KubernetesClientEventBasedConfigMapChangeDetector(coreV1Api, environment, properties, strategy, + configMapPropertySourceLocator, kubernetesNamespaceProvider); + } - return new KubernetesClientEventBasedSecretsChangeDetector(coreV1Api, environment, properties, strategy, - secretsPropertySourceLocator, kubernetesNamespaceProvider); - } + /** + * Event Based secrets ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param secretsPropertySourceLocator secrets property source locator + * @return a bean that listen to secrets change events and fire a reload. + */ + @Bean + @ConditionalOnBean(KubernetesClientSecretsPropertySourceLocator.class) + @Conditional(EventReloadDetectionMode.class) + public ConfigurationChangeDetector secretsPropertyChangeEventWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + KubernetesClientSecretsPropertySourceLocator secretsPropertySourceLocator, AbstractEnvironment environment, + CoreV1Api coreV1Api, KubernetesNamespaceProvider kubernetesNamespaceProvider) { + return new KubernetesClientEventBasedSecretsChangeDetector(coreV1Api, environment, properties, strategy, + secretsPropertySourceLocator, kubernetesNamespaceProvider); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java index 82a8e811af..6df2af1d91 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetector.java @@ -25,12 +25,9 @@ import io.kubernetes.client.util.CallGeneratorParams; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator; -import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; @@ -44,17 +41,15 @@ */ public class KubernetesClientEventBasedConfigMapChangeDetector extends ConfigurationChangeDetector { - private static final Log LOG = LogFactory.getLog(KubernetesClientEventBasedConfigMapChangeDetector.class); - - private CoreV1Api coreV1Api = null; + private final CoreV1Api coreV1Api; private final KubernetesClientConfigMapPropertySourceLocator propertySourceLocator; private final SharedInformerFactory factory; - private KubernetesClientProperties kubernetesClientProperties; + private final String namespace; - private KubernetesNamespaceProvider kubernetesNamespaceProvider; + private final boolean monitorConfigMaps; public KubernetesClientEventBasedConfigMapChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment, ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy, @@ -71,38 +66,33 @@ public KubernetesClientEventBasedConfigMapChangeDetector(CoreV1Api coreV1Api, Co // certificate authorities for the cluster. This results in SSL errors. // See https://github.com/spring-cloud/spring-cloud-kubernetes/issues/885 this.factory = new SharedInformerFactory(createApiClientForInformerClient()); - this.kubernetesNamespaceProvider = kubernetesNamespaceProvider; - } - - private String getNamespace() { - return kubernetesNamespaceProvider != null ? kubernetesNamespaceProvider.getNamespace() - : kubernetesClientProperties.getNamespace(); + this.namespace = kubernetesNamespaceProvider.getNamespace(); + this.monitorConfigMaps = properties.isMonitoringConfigMaps(); } @PostConstruct public void watch() { - if (coreV1Api != null && this.properties.isMonitoringConfigMaps()) { + if (coreV1Api != null && monitorConfigMaps) { SharedIndexInformer configMapInformer = factory.sharedIndexInformerFor( - (CallGeneratorParams params) -> coreV1Api.listNamespacedConfigMapCall(getNamespace(), null, null, - null, null, null, null, params.resourceVersion, null, params.timeoutSeconds, params.watch, - null), + (CallGeneratorParams params) -> coreV1Api.listNamespacedConfigMapCall(namespace, null, null, null, + null, null, null, params.resourceVersion, null, params.timeoutSeconds, params.watch, null), V1ConfigMap.class, V1ConfigMapList.class); - configMapInformer.addEventHandler(new ResourceEventHandler() { + configMapInformer.addEventHandler(new ResourceEventHandler<>() { @Override public void onAdd(V1ConfigMap obj) { - LOG.info("CongifMap " + obj.getMetadata().getName() + " was added."); + log.info("ConfigMap " + obj.getMetadata().getName() + " was added."); onEvent(obj); } @Override public void onUpdate(V1ConfigMap oldObj, V1ConfigMap newObj) { - LOG.info("ConfigMap " + newObj.getMetadata().getName() + " was added."); + log.info("ConfigMap " + newObj.getMetadata().getName() + " was added."); onEvent(newObj); } @Override public void onDelete(V1ConfigMap obj, boolean deletedFinalStateUnknown) { - LOG.info("ConfigMap " + obj.getMetadata() + " was deleted."); + log.info("ConfigMap " + obj.getMetadata() + " was deleted."); onEvent(obj); } }); @@ -116,15 +106,15 @@ public void unwatch() { } private void onEvent(V1ConfigMap configMap) { - this.log.debug(String.format("onEvent configMap: %s", configMap.toString())); + log.debug("onEvent configMap: " + configMap.toString()); boolean changed = changed(locateMapPropertySources(this.propertySourceLocator, this.environment), findPropertySources(KubernetesClientConfigMapPropertySource.class)); if (changed) { - LOG.info("Configuration change detected, reloading properties."); + log.info("Configuration change detected, reloading properties."); reloadProperties(); } else { - LOG.warn("Configuration change was not detected."); + log.warn("Configuration change was not detected."); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java index 45b2490ed3..213660ad92 100644 --- a/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java +++ b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetector.java @@ -24,12 +24,9 @@ import io.kubernetes.client.openapi.models.V1SecretList; import io.kubernetes.client.util.CallGeneratorParams; import jakarta.annotation.PostConstruct; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator; -import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; @@ -43,17 +40,15 @@ */ public class KubernetesClientEventBasedSecretsChangeDetector extends ConfigurationChangeDetector { - private static final Log LOG = LogFactory.getLog(KubernetesClientEventBasedSecretsChangeDetector.class); - - private CoreV1Api coreV1Api; + private final CoreV1Api coreV1Api; private final KubernetesClientSecretsPropertySourceLocator propertySourceLocator; private final SharedInformerFactory factory; - private KubernetesClientProperties kubernetesClientProperties; + private final String namespace; - private KubernetesNamespaceProvider kubernetesNamespaceProvider; + private final boolean monitorSecrets; public KubernetesClientEventBasedSecretsChangeDetector(CoreV1Api coreV1Api, ConfigurableEnvironment environment, ConfigReloadProperties properties, ConfigurationUpdateStrategy strategy, @@ -70,37 +65,33 @@ public KubernetesClientEventBasedSecretsChangeDetector(CoreV1Api coreV1Api, Conf // See https://github.com/spring-cloud/spring-cloud-kubernetes/issues/885 this.factory = new SharedInformerFactory(createApiClientForInformerClient()); this.coreV1Api = coreV1Api; - this.kubernetesNamespaceProvider = kubernetesNamespaceProvider; - } - - private String getNamespace() { - return kubernetesNamespaceProvider != null ? kubernetesNamespaceProvider.getNamespace() - : kubernetesClientProperties.getNamespace(); + this.namespace = kubernetesNamespaceProvider.getNamespace(); + this.monitorSecrets = properties.isMonitoringSecrets(); } @PostConstruct public void watch() { - if (coreV1Api != null && this.properties.isMonitoringSecrets()) { + if (coreV1Api != null && monitorSecrets) { SharedIndexInformer configMapInformer = factory.sharedIndexInformerFor( - (CallGeneratorParams params) -> coreV1Api.listNamespacedSecretCall(getNamespace(), null, null, null, + (CallGeneratorParams params) -> coreV1Api.listNamespacedSecretCall(namespace, null, null, null, null, null, null, params.resourceVersion, null, params.timeoutSeconds, params.watch, null), V1Secret.class, V1SecretList.class); - configMapInformer.addEventHandler(new ResourceEventHandler() { + configMapInformer.addEventHandler(new ResourceEventHandler<>() { @Override public void onAdd(V1Secret obj) { - LOG.info("Secret " + obj.getMetadata().getName() + " was added."); + log.info("Secret " + obj.getMetadata().getName() + " was added."); onEvent(obj); } @Override public void onUpdate(V1Secret oldObj, V1Secret newObj) { - LOG.info("Secret " + newObj.getMetadata().getName() + " was added."); + log.info("Secret " + newObj.getMetadata().getName() + " was added."); onEvent(newObj); } @Override public void onDelete(V1Secret obj, boolean deletedFinalStateUnknown) { - LOG.info("Secret " + obj.getMetadata() + " was deleted."); + log.info("Secret " + obj.getMetadata() + " was deleted."); onEvent(obj); } }); @@ -109,11 +100,11 @@ public void onDelete(V1Secret obj, boolean deletedFinalStateUnknown) { } private void onEvent(V1Secret secret) { - this.log.debug(String.format("onEvent configMap: %s", secret.toString())); + log.debug("onEvent configMap: " + secret.toString()); boolean changed = changed(locateMapPropertySources(this.propertySourceLocator, this.environment), findPropertySources(KubernetesClientSecretsPropertySource.class)); if (changed) { - this.log.info("Detected change in secrets"); + log.info("Detected change in secrets"); reloadProperties(); } } diff --git a/spring-cloud-kubernetes-client-config/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-client-config/src/main/resources/META-INF/spring.factories index 2efd0550c7..107ff95efd 100644 --- a/spring-cloud-kubernetes-client-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-client-config/src/main/resources/META-INF/spring.factories @@ -3,3 +3,6 @@ org.springframework.cloud.kubernetes.client.config.reload.KubernetesClientConfig org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.kubernetes.client.config.KubernetesClientBootstrapConfiguration,\ org.springframework.cloud.kubernetes.client.config.KubernetesClientRetryBootstrapConfiguration +# ConfigData Location Resolvers +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigDataLocationResolver diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapIncludeProfileSpecificSourcesTests.java index a1fb50f13d..c8a43cb3ec 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapIncludeProfileSpecificSourcesTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapIncludeProfileSpecificSourcesTests.java @@ -21,14 +21,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.IncludeProfileSpecificSourcesApp; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** @@ -36,14 +30,7 @@ * * @author wind57 */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = IncludeProfileSpecificSourcesApp.class, - properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources", - "include.profile.specific.sources=true", "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@ActiveProfiles("dev") -class KubernetesClientConfigMapIncludeProfileSpecificSourcesTests { +abstract class KubernetesClientConfigMapIncludeProfileSpecificSourcesTests { @Autowired private WebTestClient webClient; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java index 4f636df074..9a045e30e5 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceLocatorTests.java @@ -183,7 +183,7 @@ public void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment())); assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap(s) in namespace 'default'"); + .hasMessage("Internal Server Error"); } @Test diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java index b7c42c8c4b..75a6c2a1d4 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapPropertySourceTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.mock.env.MockEnvironment; @@ -100,7 +101,7 @@ public void propertiesFile() { stubFor(get("/api/v1/namespaces/default/configmaps") .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST)))); - NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "", true); + NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context); @@ -121,7 +122,7 @@ public void yamlFile() { stubFor(get("/api/v1/namespaces/default/configmaps") .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(YAML_CONFIGMAP_LIST)))); - NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-641", "default", false, "", true); + NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-641", "default", false, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context); @@ -142,7 +143,8 @@ public void propertiesFileWithPrefix() { stubFor(get("/api/v1/namespaces/default/configmaps") .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(PROPERTIES_CONFIGMAP_LIST)))); - NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "prefix", true); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, prefix, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); KubernetesClientConfigMapPropertySource propertySource = new KubernetesClientConfigMapPropertySource(context); @@ -161,7 +163,8 @@ public void propertiesFileWithPrefix() { @Test void constructorWithNamespaceMustNotFail() { - NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, "prefix", true); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource("bootstrap-640", "default", false, prefix, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default", new MockEnvironment()); @@ -173,13 +176,13 @@ public void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { stubFor(get("/api/v1/namespaces/default/configmaps") .willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", true, "prefix", true); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", true, prefix, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default", new MockEnvironment()); assertThatThrownBy(() -> new KubernetesClientConfigMapPropertySource(context)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap(s) in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); verify(getRequestedFor(urlEqualTo("/api/v1/namespaces/default/configmaps"))); } @@ -188,7 +191,8 @@ public void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() stubFor(get("/api/v1/namespaces/default/configmaps") .willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", false, "prefix", true); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource("my-config", "default", false, prefix, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(new CoreV1Api(), source, "default", new MockEnvironment()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigReloadAutoConfigurationTest.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigReloadAutoConfigurationTest.java index 944fc1bdbb..a7ddd275bc 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigReloadAutoConfigurationTest.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigReloadAutoConfigurationTest.java @@ -29,7 +29,6 @@ import io.kubernetes.client.openapi.models.V1ConfigMapList; import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; import io.kubernetes.client.util.ClientBuilder; -import org.junit.ClassRule; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -68,8 +67,7 @@ public class KubernetesClientConfigReloadAutoConfigurationTest { private ConfigurableApplicationContext context; - @ClassRule - public static WireMockServer wireMockServer = new WireMockServer(options().dynamicPort()); + public static WireMockServer wireMockServer; protected void setup(String... env) { List envList = (env != null) ? new ArrayList<>(Arrays.asList(env)) : new ArrayList<>(); @@ -86,13 +84,14 @@ protected void setup(String... env) { } @BeforeAll - public static void startWireMockServer() { + static void startWireMockServer() { + wireMockServer = new WireMockServer(options().dynamicPort()); wireMockServer.start(); WireMock.configureFor(wireMockServer.port()); } @AfterEach - public void afterEach() { + void afterEach() { if (this.context != null) { this.context.close(); this.context = null; @@ -100,7 +99,7 @@ public void afterEach() { } @BeforeEach - public void beforeEach() { + void beforeEach() { V1ConfigMapList TEST_CONFIGMAP = new V1ConfigMapList().addItemsItem(new V1ConfigMapBuilder().withMetadata( new V1ObjectMetaBuilder().withName("test-cm").withNamespace("default").withResourceVersion("1").build()) .addToData("app.name", "test").build()); @@ -112,7 +111,7 @@ public void beforeEach() { // 1. watchers @Test - public void kubernetesWatchersWhenKubernetesDisabled() throws Exception { + void kubernetesWatchersWhenKubernetesDisabled() throws Exception { setup(); assertThat(context.containsBean("configMapPropertySourceLocator")).isFalse(); assertThat(context.containsBean("secretsPropertySourceLocator")).isFalse(); @@ -123,7 +122,7 @@ public void kubernetesWatchersWhenKubernetesDisabled() throws Exception { } @Test - public void kubernetesWatchersWhenConfigDisabled() throws Exception { + void kubernetesWatchersWhenConfigDisabled() throws Exception { setup("spring.cloud.kubernetes.config.enabled=false"); assertThat(context.containsBean("configMapPropertyChangePollingWatcher")).isFalse(); assertThat(context.containsBean("secretsPropertyChangePollingWatcher")).isFalse(); @@ -132,7 +131,7 @@ public void kubernetesWatchersWhenConfigDisabled() throws Exception { } @Test - public void kubernetesWatchersWhenReloadDisabled() throws Exception { + void kubernetesWatchersWhenReloadDisabled() throws Exception { setup("spring.cloud.kubernetes.reload.enabled=false"); assertThat(context.containsBean("configMapPropertyChangePollingWatcher")).isFalse(); assertThat(context.containsBean("secretsPropertyChangePollingWatcher")).isFalse(); @@ -141,7 +140,7 @@ public void kubernetesWatchersWhenReloadDisabled() throws Exception { } @Test - public void kubernetesReloadEnabledButSecretAndConfigDisabled() throws Exception { + void kubernetesReloadEnabledButSecretAndConfigDisabled() throws Exception { setup("spring.cloud.kubernetes.reload.enabled=true", "spring.cloud.kubernetes.config.enabled=false", "spring.cloud.kubernetes.secrets.enabled=false"); assertThat(context.containsBean("configMapPropertyChangePollingWatcher")).isFalse(); @@ -151,7 +150,7 @@ public void kubernetesReloadEnabledButSecretAndConfigDisabled() throws Exception } @Test - public void kubernetesReloadEnabledWithPolling() throws Exception { + void kubernetesReloadEnabledWithPolling() throws Exception { setup("spring.cloud.kubernetes.reload.enabled=true", "spring.cloud.kubernetes.reload.mode=polling", "spring.main.cloud-platform=KUBERNETES"); assertThat(context.containsBean("configMapPropertySourceLocator")).isTrue(); @@ -163,7 +162,7 @@ public void kubernetesReloadEnabledWithPolling() throws Exception { } @Test - public void kubernetesReloadEnabledWithEvent() throws Exception { + void kubernetesReloadEnabledWithEvent() throws Exception { setup("spring.cloud.kubernetes.reload.enabled=true", "spring.cloud.kubernetes.reload.mode=event", "spring.main.cloud-platform=KUBERNETES"); assertThat(context.containsBean("configMapPropertyChangePollingWatcher")).isFalse(); @@ -175,21 +174,21 @@ public void kubernetesReloadEnabledWithEvent() throws Exception { // 2. config and secrets property source locators @Test - public void kubernetesConfigAndSecretEnabledByDefault() throws Exception { + void kubernetesConfigAndSecretEnabledByDefault() throws Exception { setup("spring.main.cloud-platform=KUBERNETES"); assertThat(context.containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(context.containsBean("secretsPropertySourceLocator")).isTrue(); } @Test - public void kubernetesConfigEnabledButSecretDisabled() throws Exception { + void kubernetesConfigEnabledButSecretDisabled() throws Exception { setup("spring.cloud.kubernetes.secrets.enabled=false", "spring.main.cloud-platform=KUBERNETES"); assertThat(context.containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(context.containsBean("secretsPropertySourceLocator")).isFalse(); } @Test - public void kubernetesSecretsEnabledButConfigDisabled() throws Exception { + void kubernetesSecretsEnabledButConfigDisabled() throws Exception { setup("spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES"); assertThat(context.containsBean("configMapPropertySourceLocator")).isFalse(); assertThat(context.containsBean("secretsPropertySourceLocator")).isTrue(); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java index f0742969f0..b897f98743 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceLocatorTests.java @@ -80,7 +80,7 @@ class KubernetesClientSecretsPropertySourceLocatorTests { private static final MockEnvironment ENV = new MockEnvironment(); @BeforeAll - public static void setup() { + static void setup() { wireMockServer = new WireMockServer(options().dynamicPort()); wireMockServer.start(); @@ -92,12 +92,12 @@ public static void setup() { } @AfterAll - public static void after() { + static void after() { wireMockServer.stop(); } @AfterEach - public void afterEach() { + void afterEach() { WireMock.reset(); } @@ -160,7 +160,7 @@ void testLocateWithoutNamespaceConstructor() { } @Test - public void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { + void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { CoreV1Api api = new CoreV1Api(); stubFor(get(LIST_API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); @@ -174,11 +174,11 @@ public void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { new KubernetesNamespaceProvider(new MockEnvironment()), secretsConfigProperties); assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'db-secret' in namespace 'default'"); + .hasMessage("Internal Server Error"); } @Test - public void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { + void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { CoreV1Api api = new CoreV1Api(); stubFor(get(LIST_API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java index b0cbfd960c..2da5e54e51 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientSecretsPropertySourceTests.java @@ -64,7 +64,7 @@ class KubernetesClientSecretsPropertySourceTests { .withNamespace("default").build()) .addToData("password", "p455w0rd".getBytes()).addToData("username", "user".getBytes()).build()).build(); - private static final String LIST_API_WITH_LABEL = "/api/v1/namespaces/default/secrets?labelSelector=spring.cloud.kubernetes.secret%3Dtrue"; + private static final String LIST_API_WITH_LABEL = "/api/v1/namespaces/default/secrets"; private static final String LIST_BODY = "{\n" + "\t\"kind\": \"SecretList\",\n" + "\t\"apiVersion\": \"v1\",\n" + "\t\"metadata\": {\n" + "\t\t\"selfLink\": \"/api/v1/secrets\",\n" @@ -119,7 +119,7 @@ void secretsTest() { CoreV1Api api = new CoreV1Api(); stubFor(get(API).willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRET_LIST)))); - NormalizedSource source = new NamedSecretNormalizedSource("db-secret", "default", false); + NormalizedSource source = new NamedSecretNormalizedSource("db-secret", "default", false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); @@ -137,7 +137,7 @@ void secretLabelsTest() { Map labels = new HashMap<>(); labels.put("spring.cloud.kubernetes.secret", "true"); - NormalizedSource source = new LabeledSecretNormalizedSource("default", labels, false); + NormalizedSource source = new LabeledSecretNormalizedSource("default", labels, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); @@ -151,13 +151,12 @@ void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { CoreV1Api api = new CoreV1Api(); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - NormalizedSource source = new NamedSecretNormalizedSource("secret", "default", true); + NormalizedSource source = new NamedSecretNormalizedSource("secret", "default", true, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); assertThatThrownBy(() -> new KubernetesClientSecretsPropertySource(context)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'secret' in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); verify(getRequestedFor(urlEqualTo(API))); } @@ -166,7 +165,7 @@ void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { CoreV1Api api = new CoreV1Api(); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - NormalizedSource source = new NamedSecretNormalizedSource("secret", "db-secret", false); + NormalizedSource source = new NamedSecretNormalizedSource("secret", "db-secret", false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, "default", new MockEnvironment()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProviderTests.java new file mode 100644 index 0000000000..a8bea2e90c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProviderTests.java @@ -0,0 +1,453 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.SourceData; +import org.springframework.mock.env.MockEnvironment; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * @author wind57 + */ +class LabeledConfigMapContextToSourceDataProviderTests { + + private static final Map LABELS = new LinkedHashMap<>(); + + private static final Map RED_LABEL = Map.of("color", "red"); + + private static final Map BLUE_LABEL = Map.of("color", "blue"); + + private static final Map PINK_LABEL = Map.of("color", "pink"); + + private static final String NAMESPACE = "default"; + + static { + LABELS.put("label2", "value2"); + LABELS.put("label1", "value1"); + } + + @BeforeAll + static void setup() { + WireMockServer wireMockServer = new WireMockServer(options().dynamicPort()); + + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + + ApiClient client = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + client.setDebugging(true); + Configuration.setDefaultApiClient(client); + } + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + /** + * we have a single config map deployed. it has two labels and these match against our + * queries. + */ + @Test + void singleConfigMapMatchAgainstLabels() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("test-configmap") + .withLabels(LABELS).withNamespace(NAMESPACE).build()).addToData("name", "value").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, LABELS, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData()); + + } + + /** + * we have three configmaps deployed. two of them have labels that match (color=red), + * one does not (color=blue). + */ + @Test + void twoConfigMapsMatchAgainstLabels() { + + V1ConfigMap redOne = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("red-configmap") + .withLabels(RED_LABEL).withNamespace(NAMESPACE).build()).addToData("colorOne", "really-red").build(); + + V1ConfigMap redTwo = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName("red-configmap-again").withLabels(RED_LABEL).withNamespace(NAMESPACE).build()) + .addToData("colorTwo", "really-red-again").build(); + + V1ConfigMap blue = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("color", "blue").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(redOne).addItemsItem(redTwo) + .addItemsItem(blue); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, RED_LABEL, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.red-configmap.red-configmap-again.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("colorOne"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("colorTwo"), "really-red-again"); + + } + + /** + * one configmap deployed (pink), does not match our query (blue). + */ + @Test + void configMapNoMatch() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("pink-configmap") + .withLabels(PINK_LABEL).withNamespace(NAMESPACE).build()).addToData("color", "pink").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default"); + Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); + + } + + /** + * LabeledConfigMapContextToSourceDataProvider gets as input a Fabric8ConfigContext. + * This context has a namespace as well as a NormalizedSource, that has a namespace + * too. It is easy to get confused in code on which namespace to use. This test makes + * sure that we use the proper one. + */ + @Test + void namespaceMatch() { + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("test-configmap") + .withLabels(LABELS).withNamespace(NAMESPACE).build()).addToData("name", "value").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + String wrongNamespace = NAMESPACE + "nope"; + NormalizedSource source = new LabeledConfigMapNormalizedSource(wrongNamespace, LABELS, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData()); + } + + /** + * one configmap with name : "blue-configmap" and labels "color=blue" is deployed. we + * search it with the same labels, find it, and assert that name of the SourceData (it + * must use its name, not its labels) and values in the SourceData must be prefixed + * (since we have provided an explicit prefix). + */ + @Test + void testWithPrefix() { + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("what-color", "blue-color").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant"); + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, mePrefix, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.blue-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData()); + } + + /** + * two configmaps are deployed (name:blue-configmap, name:another-blue-configmap) and + * labels "color=blue" (on both). we search with the same labels, find them, and + * assert that name of the SourceData (it must use its name, not its labels) and + * values in the SourceData must be prefixed (since we have provided a delayed + * prefix). + * + * Also notice that the prefix is made up from both configmap names. + * + */ + @Test + void testTwoConfigmapsWithPrefix() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("blue-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("first", "blue").build(); + + V1ConfigMap two = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName("another-blue-configmap").withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()) + .addToData("second", "blue").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, + ConfigUtils.Prefix.DELAYED, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.another-blue-configmap.blue-configmap.default"); + + Map properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator keys = properties.keySet().iterator(); + String firstKey = keys.next(); + String secondKey = keys.next(); + + if (firstKey.contains("first")) { + Assertions.assertEquals(firstKey, "another-blue-configmap.blue-configmap.first"); + } + + Assertions.assertEquals(secondKey, "another-blue-configmap.blue-configmap.second"); + Assertions.assertEquals(properties.get(firstKey), "blue"); + Assertions.assertEquals(properties.get(secondKey), "blue"); + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "color-configmap-k8s" with no labels. We search by "{color:red}", do not find + * anything and thus have an empty SourceData. profile based sources are enabled, but + * it has no effect. + */ + @Test + void searchWithLabelsNoConfigmapsFound() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build(); + + V1ConfigMap two = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("color-config-k8s").withNamespace(NAMESPACE).build()) + .addToData("two", "2").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, RED_LABEL, true, + ConfigUtils.Prefix.DEFAULT, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertTrue(sourceData.sourceData().isEmpty()); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default"); + + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "shape-configmap" with label: "{shape:round}". We search by "{color:blue}" and find + * one configmap. profile based sources are enabled, but it has no effect. + */ + @Test + void searchWithLabelsOneConfigMapFound() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build(); + + V1ConfigMap two = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap").withNamespace(NAMESPACE).build()) + .addToData("two", "2").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, + ConfigUtils.Prefix.DEFAULT, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("one"), "1"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.default"); + + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "color-configmap-k8s" with label: "{color:red}". We search by "{color:blue}" and + * find one configmap. Since profiles are enabled, we will also be reading + * "color-configmap-k8s", even if its labels do not match provided ones. + */ + @Test + void searchWithLabelsOneConfigMapFoundAndOneFromProfileFound() { + + V1ConfigMap one = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("color-configmap") + .withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()).addToData("one", "1").build(); + + V1ConfigMap two = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName("color-configmap-k8s").withLabels(RED_LABEL).withNamespace(NAMESPACE).build()) + .addToData("two", "2").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one).addItemsItem(two); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, + ConfigUtils.Prefix.DELAYED, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.color-configmap-k8s.default"); + + } + + /** + *
+	 *     - configmap "color-configmap" with label "{color:blue}"
+	 *     - configmap "shape-configmap" with labels "{color:blue, shape:round}"
+	 *     - configmap "no-fit" with labels "{tag:no-fit}"
+	 *     - configmap "color-configmap-k8s" with label "{color:red}"
+	 *     - configmap "shape-configmap-k8s" with label "{shape:triangle}"
+	 * 
+ */ + @Test + void searchWithLabelsTwoConfigMapsFoundAndOneFromProfileFound() { + + V1ConfigMap colorConfigMap = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName("color-configmap").withLabels(BLUE_LABEL).withNamespace(NAMESPACE).build()) + .addToData("one", "1").build(); + + V1ConfigMap shapeConfigmap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap") + .withLabels(Map.of("color", "blue", "shape", "round")).withNamespace(NAMESPACE).build()) + .addToData("two", "2").build(); + + V1ConfigMap noFit = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("no-fit") + .withLabels(Map.of("tag", "no-fit")).withNamespace(NAMESPACE).build()).addToData("three", "3").build(); + + V1ConfigMap colorConfigmapK8s = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName("color-configmap-k8s").withLabels(RED_LABEL).withNamespace(NAMESPACE).build()) + .addToData("four", "4").build(); + + V1ConfigMap shapeConfigmapK8s = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("shape-configmap-k8s") + .withLabels(Map.of("shape", "triangle")).withNamespace(NAMESPACE).build()) + .addToData("five", "5").build(); + + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(colorConfigMap).addItemsItem(shapeConfigmap) + .addItemsItem(noFit).addItemsItem(colorConfigmapK8s).addItemsItem(shapeConfigmapK8s); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource source = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, + ConfigUtils.Prefix.DELAYED, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 4); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.four"), "4"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.five"), "5"); + + Assertions.assertEquals(sourceData.sourceName(), + "configmap.color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.default"); + + } + + private void stubCall(V1ConfigMapList list) { + stubFor(get("/api/v1/namespaces/default/configmaps") + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java index 6e83c1ac6a..fcf220777e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledSecretContextToSourceDataProviderTests.java @@ -18,6 +18,7 @@ import java.util.Base64; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -28,6 +29,7 @@ import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1SecretBuilder; import io.kubernetes.client.openapi.models.V1SecretList; import io.kubernetes.client.util.ClientBuilder; @@ -36,9 +38,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource; import org.springframework.cloud.kubernetes.commons.config.SourceData; import org.springframework.mock.env.MockEnvironment; @@ -86,26 +88,25 @@ void afterEach() { @Test void noMatch() { - V1SecretList secretList = new V1SecretList().addItemsItem(new V1SecretBuilder() + V1Secret red = new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withLabels(Collections.singletonMap("color", "red")) - .withNamespace(NAMESPACE).withName("red-secret").withResourceVersion("1").build()) - .addToData("color", Base64.getEncoder().encode("really-red".getBytes())).build()); + .withNamespace(NAMESPACE).withName("red-secret").build()) + .addToData("color", Base64.getEncoder().encode("really-red".getBytes())).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(red); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dred") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); // blue does not match red NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, - Collections.singletonMap("color", "blue"), false); + Collections.singletonMap("color", "blue"), false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = LabeledSecretContextToSourceDataProvider - .of(LabeledSecretContextToSourceDataProviderTests.Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.color.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color.default"); Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); } @@ -117,24 +118,22 @@ void noMatch() { @Test void singleSecretMatchAgainstLabels() { - V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("test-secret").build()) - .addToData("color", "really-red".getBytes()).build()); + V1Secret red = new V1SecretBuilder().withMetadata( + new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE).withName("test-secret").build()) + .addToData("color", "really-red".getBytes()).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(red); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=label2%3Dvalue2%2Clabel1%3Dvalue1") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST)))); - NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, false); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = LabeledSecretContextToSourceDataProvider - .of(LabeledSecretContextToSourceDataProviderTests.Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.test-secret.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.test-secret.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } @@ -145,30 +144,26 @@ void singleSecretMatchAgainstLabels() { @Test void twoSecretsMatchAgainstLabels() { - V1SecretList secretList = new V1SecretList(); - secretList.addItemsItem(new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("color-one").build()) - .addToData("colorOne", "really-red-one".getBytes()).build()); + V1Secret one = new V1SecretBuilder().withMetadata( + new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE).withName("color-one").build()) + .addToData("colorOne", "really-red-one".getBytes()).build(); - secretList.addItemsItem(new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("color-two").build()) - .addToData("colorTwo", "really-red-two".getBytes()).build()); + V1Secret two = new V1SecretBuilder().withMetadata( + new V1ObjectMetaBuilder().withLabels(RED_LABEL).withNamespace(NAMESPACE).withName("color-two").build()) + .addToData("colorTwo", "really-red-two".getBytes()).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(one).addItemsItem(two); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dred") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); - NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, false); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = LabeledSecretContextToSourceDataProvider - .of(LabeledSecretContextToSourceDataProviderTests.Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.color-one.color-two.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-one.color-two.default"); Assertions.assertEquals(sourceData.sourceData().size(), 2); Assertions.assertEquals(sourceData.sourceData().get("colorOne"), "really-red-one"); Assertions.assertEquals(sourceData.sourceData().get("colorTwo"), "really-red-two"); @@ -177,38 +172,276 @@ void twoSecretsMatchAgainstLabels() { @Test void namespaceMatch() { - V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("test-secret").build()) - .addToData("color", "really-red".getBytes()).build()); + V1Secret one = new V1SecretBuilder().withMetadata( + new V1ObjectMetaBuilder().withLabels(LABELS).withNamespace(NAMESPACE).withName("test-secret").build()) + .addToData("color", "really-red".getBytes()).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(one); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=label2%3Dvalue2%2Clabel1%3Dvalue1") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST)))); - NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, false); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = LabeledSecretContextToSourceDataProvider - .of(LabeledSecretContextToSourceDataProviderTests.Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.test-secret.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.test-secret.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } - // needed only to allow access to the super methods - private static final class Dummy extends SecretsPropertySource { + /** + * one secret with name : "blue-secret" and labels "color=blue" is deployed. we search + * it with the same labels, find it, and assert that name of the SourceData (it must + * use its name, not its labels) and values in the SourceData must be prefixed (since + * we have provided an explicit prefix). + */ + @Test + void testWithPrefix() { - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + V1Secret one = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")) + .withNamespace(NAMESPACE).withName("blue-secret").build()) + .addToData("what-color", "blue-color".getBytes()).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(one); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("me", false, false, null); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, prefix, + false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("secret.blue-secret.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData()); + } + + /** + * two secrets are deployed (name:blue-secret, name:another-blue-secret) and labels + * "color=blue" (on both). we search with the same labels, find them, and assert that + * name of the SourceData (it must use its name, not its labels) and values in the + * SourceData must be prefixed (since we have provided a delayed prefix). + * + * Also notice that the prefix is made up from both secret names. + * + */ + @Test + void testTwoSecretsWithPrefix() { + + V1Secret one = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")) + .withNamespace(NAMESPACE).withName("blue-secret").build()).addToData("first", "blue".getBytes()) + .build(); + + V1Secret two = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")) + .withNamespace(NAMESPACE).withName("another-blue-secret").build()) + .addToData("second", "blue".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(one).addItemsItem(two); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DELAYED, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); + // maps don't have a defined order, so assert components separately + Assertions.assertEquals(46, sourceData.sourceName().length()); + Assertions.assertTrue(sourceData.sourceName().contains("secret")); + Assertions.assertTrue(sourceData.sourceName().contains("blue-secret")); + Assertions.assertTrue(sourceData.sourceName().contains("another-blue-secret")); + Assertions.assertTrue(sourceData.sourceName().contains("default")); + + Map properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator keys = properties.keySet().iterator(); + String firstKey = keys.next(); + String secondKey = keys.next(); + + if (firstKey.contains("first")) { + Assertions.assertEquals(firstKey, "another-blue-secret.blue-secret.first"); } + Assertions.assertEquals(secondKey, "another-blue-secret.blue-secret.second"); + Assertions.assertEquals(properties.get(firstKey), "blue"); + Assertions.assertEquals(properties.get(secondKey), "blue"); + } + + /** + * two secrets are deployed: secret "color-secret" with label: "{color:blue}" and + * "shape-secret" with label: "{shape:round}". We search by "{color:blue}" and find + * one secret. profile based sources are enabled, but it has no effect. + */ + @Test + void searchWithLabelsOneSecretFound() { + + V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build()) + .addToData("one", "1".getBytes()).build(); + + V1Secret shapeSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("shape", "round")).withNamespace(NAMESPACE).withName("shape-secret").build()) + .addToData("two", "2".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DEFAULT, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("one"), "1"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default"); + + } + + /** + * two secrets are deployed: secret "color-secret" with label: "{color:blue}" and + * "color-secret-k8s" with label: "{color:red}". We search by "{color:blue}" and find + * one secret. Since profiles are enabled, we will also be reading "color-secret-k8s", + * even if its labels do not match provided ones. + */ + @Test + void searchWithLabelsOneSecretFoundAndOneFromProfileFound() { + + V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build()) + .addToData("one", "1".getBytes()).build(); + + V1Secret shapeSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "red")).withNamespace(NAMESPACE).withName("color-secret-k8s").build()) + .addToData("two", "2".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DELAYED, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.color-secret-k8s.default"); + + } + + /** + *
+	 *     - secret "color-secret" with label "{color:blue}"
+	 *     - secret "shape-secret" with labels "{color:blue, shape:round}"
+	 *     - secret "no-fit" with labels "{tag:no-fit}"
+	 *     - secret "color-secret-k8s" with label "{color:red}"
+	 *     - secret "shape-secret-k8s" with label "{shape:triangle}"
+	 * 
+ */ + @Test + void searchWithLabelsTwoSecretsFoundAndOneFromProfileFound() { + + V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build()) + .addToData("one", "1".getBytes()).build(); + + V1Secret shapeSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue", "shape", "round")) + .withNamespace(NAMESPACE).withName("shape-secret").build()) + .addToData("two", "2".getBytes()).build(); + + V1Secret noFit = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("tag", "no-fit")).withNamespace(NAMESPACE).withName("no-fit").build()) + .addToData("three", "3".getBytes()).build(); + + V1Secret colorSecretK8s = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "red")).withNamespace(NAMESPACE).withName("color-secret-k8s").build()) + .addToData("four", "4".getBytes()).build(); + + V1Secret shapeSecretK8s = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("shape", "triangle")).withNamespace(NAMESPACE).withName("shape-secret-k8s").build()) + .addToData("five", "5".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret).addItemsItem(shapeSecret) + .addItemsItem(noFit).addItemsItem(colorSecretK8s).addItemsItem(shapeSecretK8s); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DELAYED, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 4); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.one"), "1"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.two"), "2"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.four"), "4"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.five"), "5"); + + Assertions.assertEquals(sourceData.sourceName(), + "secret.color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.default"); + + } + + /** + * yaml/properties gets special treatment + */ + @Test + void testYaml() { + V1Secret colorSecret = new V1SecretBuilder().withMetadata(new V1ObjectMetaBuilder() + .withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE).withName("color-secret").build()) + .addToData("test.yaml", "color: blue".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(colorSecret); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DEFAULT, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("color"), "blue"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default"); + } + + private void stubCall(V1SecretList list) { + stubFor(get("/api/v1/namespaces/default/secrets") + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list)))); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java index a527c1d0b2..550e1762f2 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -25,6 +25,7 @@ import io.kubernetes.client.openapi.Configuration; import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; import io.kubernetes.client.openapi.models.V1ConfigMapList; import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; @@ -34,12 +35,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPrefixContext; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; import org.springframework.mock.env.MockEnvironment; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -56,8 +55,14 @@ class NamedConfigMapContextToSourceDataProviderTests { private static final String RED_CONFIG_MAP_NAME = "red"; + private static final String RED_WITH_PROFILE_CONFIG_MAP_NAME = RED_CONFIG_MAP_NAME + "-with-profile"; + private static final String BLUE_CONFIG_MAP_NAME = "blue"; + private static final Map COLOR_REALLY_RED = Map.of("color", "really-red"); + + private static final Map TASTE_MANGO = Map.of("taste", "mango"); + @BeforeAll static void setup() { WireMockServer wireMockServer = new WireMockServer(options().dynamicPort()); @@ -76,92 +81,90 @@ void afterEach() { } /** - * we have a single config map deployed. it does not match our query. + *
+	 *     one configmap deployed with name "red"
+	 *     we search by name, but for the "blue" one, as such not find it
+	 * 
*/ @Test void noMatch() { + V1ConfigMap redConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME) - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("color", "really-red").build()); - + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(redConfigMap); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(BLUE_CONFIG_MAP_NAME, NAMESPACE, true, "", false); + + NormalizedSource source = new NamedConfigMapNormalizedSource(BLUE_CONFIG_MAP_NAME, NAMESPACE, true, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.blue.default"); - Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); + Assertions.assertEquals(sourceData.sourceData(), Map.of()); } /** - * we have a single config map deployed. it matches our query. + *
+	 *     one configmap deployed with name "red"
+	 *     we search by name, for the "red" one, as such we find it
+	 * 
*/ @Test void match() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME) - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("color", "really-red").build()); + V1ConfigMap configMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, "", false); + + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); - Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red")); + Assertions.assertEquals(sourceData.sourceData(), COLOR_REALLY_RED); } /** - * we have two config maps deployed. one matches the query name. the other matches the - * active profile + name, thus is taken also. + *
+	 *     - two configmaps deployed : "red" and "red-with-profile".
+	 *     - "red" is matched directly, "red-with-profile" is matched because we have an active profile
+	 *       "active-profile"
+	 * 
*/ @Test void matchIncludeSingleProfile() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE) - .withResourceVersion("1").build()) - .addToData("color", "really-red").build()) - .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-profile") - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("taste", "mango").build()); + V1ConfigMap red = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); + + V1ConfigMap redWithProfile = new V1ConfigMapBuilder().withMetadata( + new V1ObjectMetaBuilder().withName(RED_WITH_PROFILE_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(TASTE_MANGO).build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithProfile); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, "", true); + + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, true); MockEnvironment environment = new MockEnvironment(); environment.setActiveProfiles("with-profile"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -172,37 +175,37 @@ void matchIncludeSingleProfile() { } /** - * we have two config maps deployed. one matches the query name. the other matches the - * active profile + name, thus is taken also. This takes into consideration the - * prefix, that we explicitly specify. Notice that prefix works for profile based - * config maps as well. + *
+	 *     - two configmaps deployed : "red" and "red-with-profile".
+	 *     - "red" is matched directly, "red-with-profile" is matched because we have an active profile
+	 *       "active-profile"
+	 *     -  This takes into consideration the prefix, that we explicitly specify.
+	 *        Notice that prefix works for profile based config maps as well.
+	 * 
*/ @Test void matchIncludeSingleProfileWithPrefix() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE) - .withResourceVersion("1").build()) - .addToData("color", "really-red").build()) - .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-profile") - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("taste", "mango").build()); + V1ConfigMap red = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); + + V1ConfigMap redWithTaste = new V1ConfigMapBuilder().withMetadata( + new V1ObjectMetaBuilder().withName(RED_WITH_PROFILE_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(TASTE_MANGO).build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithTaste); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, "some", + + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix, true); MockEnvironment environment = new MockEnvironment(); environment.setActiveProfiles("with-profile"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -213,46 +216,46 @@ void matchIncludeSingleProfileWithPrefix() { } /** - * we have three config maps deployed. one matches the query name. the other two match - * the active profile + name, thus are taken also. This takes into consideration the - * prefix, that we explicitly specify. Notice that prefix works for profile based - * config maps as well. + *
+	 *     - three configmaps deployed : "red", "red-with-taste" and "red-with-shape"
+	 *     - "red" is matched directly, the other two are matched because of active profiles
+	 *     -  This takes into consideration the prefix, that we explicitly specify.
+	 *        Notice that prefix works for profile based config maps as well.
+	 * 
*/ @Test void matchIncludeTwoProfilesWithPrefix() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE) - .withResourceVersion("1").build()) - .addToData("color", "really-red").build()) - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-taste") - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("taste", "mango").build()) - .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-shape") - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("shape", "round").build()); + V1ConfigMap red = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); + + V1ConfigMap redWithTaste = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME + "-with-taste") + .withNamespace(NAMESPACE).withResourceVersion("1").build()) + .addToData(TASTE_MANGO).build(); + + V1ConfigMap redWithShape = new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder() + .withName(RED_CONFIG_MAP_NAME + "-with-shape").withNamespace(NAMESPACE).build()) + .addToData("shape", "round").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red).addItemsItem(redWithTaste) + .addItemsItem(redWithShape); + + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, "some", + + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix, true); MockEnvironment environment = new MockEnvironment(); environment.setActiveProfiles("with-taste", "with-shape"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-taste.red-with-shape.default"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-shape.red-with-taste.default"); Assertions.assertEquals(sourceData.sourceData().size(), 3); Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); @@ -260,24 +263,29 @@ void matchIncludeTwoProfilesWithPrefix() { } - // this test makes sure that even if NormalizedSource has no name (which is a valid - // case for config maps), - // it will default to "application" and such a config map will be read. + /** + *
+	 * 		proves that an implicit configmap is going to be generated and read, even if
+	 * 	    we did not provide one
+	 * 
+ */ @Test - void matchWithoutName() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem(new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("application") - .withNamespace(NAMESPACE).withResourceVersion("1").build()).addToData("color", "red").build()); + void matchWithName() { + + V1ConfigMap red = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("application").withNamespace(NAMESPACE).build()) + .addToData("color", "red").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(red); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(null, NAMESPACE, true, "some", false); + + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null); + NormalizedSource source = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, prefix, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.application.default"); @@ -285,56 +293,95 @@ void matchWithoutName() { } /** - * NamedSecretContextToSourceDataProvider gets as input a - * KubernetesClientConfigContext. This context has a namespace as well as a - * NormalizedSource, that has a namespace too. It is easy to get confused in code on - * which namespace to use. This test makes sure that we use the proper one. + *
+	 *     - NamedSecretContextToSourceDataProvider gets as input a KubernetesClientConfigContext.
+	 *     - This context has a namespace as well as a NormalizedSource, that has a namespace too.
+	 *     - This test makes sure that we use the proper one.
+	 * 
*/ @Test void namespaceMatch() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem( - new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME) - .withNamespace(NAMESPACE).withResourceVersion("1").build()) - .addToData("color", "really-red").build()); + V1ConfigMap configMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData(COLOR_REALLY_RED).build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(configMap); + stubCall(configMapList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/configmaps") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); - NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE + "nope", true, "", - false); + + String wrongNamespace = NAMESPACE + "nope"; + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, wrongNamespace, true, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); - Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red")); + Assertions.assertEquals(sourceData.sourceData(), COLOR_REALLY_RED); } - // needed only to allow access to the super methods - private static final class Dummy extends ConfigMapPropertySource { + /** + *
+	 *     - proves that single yaml file gets special treatment
+	 * 
+ */ + @Test + void testSingleYaml() { + V1ConfigMap singleYaml = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName(RED_CONFIG_MAP_NAME).withNamespace(NAMESPACE).build()) + .addToData("single.yaml", "key: value").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(singleYaml); - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); - } + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); - private static Map processEntries(Map map, Environment environment) { - return processAllEntries(map, environment); - } + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); - private static SourceData prefix(ConfigMapPrefixContext context) { - return withPrefix(context); - } + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); + Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value")); + } + /** + *
+	 *     - one configmap is deployed with name "one"
+	 *     - profile is enabled with name "k8s"
+	 *
+	 *     we assert that the name of the source is "one" and does not contain "one-dev"
+	 * 
+ */ + @Test + void testCorrectNameWithProfile() { + V1ConfigMap one = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("one").withNamespace(NAMESPACE).build()) + .addToData("key", "value").build(); + V1ConfigMapList configMapList = new V1ConfigMapList().addItemsItem(one); + + stubCall(configMapList); + CoreV1Api api = new CoreV1Api(); + + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource source = new NamedConfigMapNormalizedSource("one", NAMESPACE, true, true); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.one.default"); + Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value")); + } + + private void stubCall(V1ConfigMapList list) { + stubFor(get("/api/v1/namespaces/default/configmaps") + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list)))); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java index 8efc74fee9..2907eff028 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProviderTests.java @@ -26,17 +26,19 @@ import io.kubernetes.client.openapi.JSON; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1SecretBuilder; import io.kubernetes.client.openapi.models.V1SecretList; +import io.kubernetes.client.openapi.models.V1SecretListBuilder; import io.kubernetes.client.util.ClientBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource; import org.springframework.cloud.kubernetes.commons.config.SourceData; import org.springframework.mock.env.MockEnvironment; @@ -47,8 +49,12 @@ class NamedSecretContextToSourceDataProviderTests { + private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("some", false, false, "irrelevant"); + private static final String NAMESPACE = "default"; + private static final Map COLOR_REALLY_RED = Map.of("color", "really-red".getBytes()); + @BeforeAll static void setup() { WireMockServer wireMockServer = new WireMockServer(options().dynamicPort()); @@ -67,32 +73,27 @@ void afterEach() { } /** - * - * /** we have a single secret deployed. it matched the name in our queries + * we have a single secret deployed. it matched the name in our queries */ @Test void singleSecretMatchAgainstLabels() { - V1SecretList secretList = new V1SecretList() - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()); - + V1Secret red = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(red); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); // blue does not match red - NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false); + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } @@ -104,36 +105,32 @@ void singleSecretMatchAgainstLabels() { @Test void twoSecretMatchAgainstLabels() { - V1SecretList secretList = new V1SecretList() - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()) - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("blue") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()) - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("pink") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()); + V1Secret red = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + + V1Secret blue = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("blue").build()) + .addToData(COLOR_REALLY_RED).build(); + V1Secret pink = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("pink").build()) + .addToData(COLOR_REALLY_RED).build(); + + V1SecretList secretList = new V1SecretListBuilder().addToItems(red).addToItems(blue).addToItems(pink).build(); + + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); - // blue does not match red - NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false); + // blue does not match red, nor pink + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData().size(), 1); Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); @@ -145,66 +142,200 @@ void twoSecretMatchAgainstLabels() { @Test void testSecretNoMatch() { - V1SecretList secretList = new V1SecretList() - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()); + V1Secret secret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(secret); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); // blue does not match red - NormalizedSource source = new NamedSecretNormalizedSource("blue", NAMESPACE, false); + NormalizedSource source = new NamedSecretNormalizedSource("blue", NAMESPACE, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.blue.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.blue.default"); Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); } + /** + *
+	 *     - LabeledSecretContextToSourceDataProvider gets as input a KubernetesClientConfigContext.
+	 *     - This context has a namespace as well as a NormalizedSource, that has a namespace too.
+	 *     - This test makes sure that we use the proper one.
+	 * 
+ */ @Test void namespaceMatch() { - V1SecretList secretList = new V1SecretList() - .addItemsItem( - new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red") - .withResourceVersion("1").build()) - .addToData("color", "really-red".getBytes()).build()); + V1Secret secret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(secret); + stubCall(secretList); CoreV1Api api = new CoreV1Api(); - stubFor(get("/api/v1/namespaces/default/secrets") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(secretList)))); - // blue does not match red - NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", false); + String wrongNamespace = NAMESPACE + "nope"; + NormalizedSource source = new NamedSecretNormalizedSource("red", wrongNamespace, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } - // needed only to allow access to the super methods - private static final class Dummy extends SecretsPropertySource { + /** + * we have two secrets deployed. one matches the query name. the other matches the + * active profile + name, thus is taken also. + */ + @Test + void matchIncludeSingleProfile() { + + V1Secret red = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + + V1Secret mango = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-profile").build()) + .addToData("taste", "mango".getBytes()).build(); - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, false, true); + MockEnvironment environment = new MockEnvironment(); + environment.addActiveProfile("with-profile"); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango"); + + } + + /** + * we have two secrets deployed. one matches the query name. the other matches the + * active profile + name, thus is taken also. This takes into consideration the + * prefix, that we explicitly specify. Notice that prefix works for profile based + * secrets as well. + */ + @Test + void matchIncludeSingleProfileWithPrefix() { + + V1Secret red = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + + V1Secret mango = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-taste").build()) + .addToData("taste", "mango".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); - } + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true); + MockEnvironment environment = new MockEnvironment(); + environment.addActiveProfile("with-taste"); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-taste.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); + + } + + /** + * we have three secrets deployed. one matches the query name. the other two match the + * active profile + name, thus are taken also. This takes into consideration the + * prefix, that we explicitly specify. Notice that prefix works for profile based + * config maps as well. + */ + @Test + void matchIncludeTwoProfilesWithPrefix() { + + V1Secret red = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red").build()) + .addToData(COLOR_REALLY_RED).build(); + + V1Secret mango = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-taste").build()) + .addToData("taste", "mango".getBytes()).build(); + + V1Secret shape = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withNamespace(NAMESPACE).withName("red-with-shape").build()) + .addToData("shape", "round".getBytes()).build(); + + V1SecretList secretList = new V1SecretList().addItemsItem(red).addItemsItem(mango).addItemsItem(shape); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("with-taste", "with-shape"); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); + + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-shape.red-with-taste.default"); + + Assertions.assertEquals(sourceData.sourceData().size(), 3); + Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); + Assertions.assertEquals(sourceData.sourceData().get("some.shape"), "round"); + + } + + /** + *
+	 *     - proves that single yaml file gets special treatment
+	 * 
+ */ + @Test + void testSingleYaml() { + V1Secret singleYaml = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("single-yaml").withNamespace(NAMESPACE).build()) + .addToData("single.yaml", "key: value".getBytes()).build(); + V1SecretList secretList = new V1SecretList().addItemsItem(singleYaml); + + stubCall(secretList); + CoreV1Api api = new CoreV1Api(); + + NormalizedSource source = new NamedSecretNormalizedSource("single-yaml", NAMESPACE, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + + KubernetesClientContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.single-yaml.default"); + Assertions.assertEquals(sourceData.sourceData(), Map.of("key", "value")); + } + + private void stubCall(V1SecretList list) { + stubFor(get("/api/v1/namespaces/default/secrets") + .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(list)))); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java new file mode 100644 index 0000000000..d80949f597 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class, Four.class }) +public class LabeledConfigMapWithPrefixApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledConfigMapWithPrefixApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..7db7b3b40c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-prefix", + "labeled.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledConfigMapWithPrefixBootstrapTests extends LabeledConfigMapWithPrefixTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..27302005d6 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub.stubData; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithPrefixApp.class, + properties = { "spring.cloud.application.name=labeled-configmap-with-prefix", + "labeled.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-configmap-with-prefix.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledConfigMapWithPrefixConfigDataTests extends LabeledConfigMapWithPrefixTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java new file mode 100644 index 0000000000..647e3cb6c3 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Stud data is in + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub} + * + * @author wind57 + */ +abstract class LabeledConfigMapWithPrefixTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.configmap.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.configmap.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-configmap/prefix/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-configmap/prefix/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[2].labels=letter:c'
+	 * 	 ("property", "three")
+	 *
+	 *   We find the configmap by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "configmap-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-configmap/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[3].labels=letter:d'
+	 * 	 ("property", "four")
+	 *
+	 *   We find the configmap by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "configmap-four")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-configmap/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java new file mode 100644 index 0000000000..5e16b04b38 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledConfigMapWithPrefixController { + + private final One one; + + private final Two two; + + private final Three three; + + private final Four four; + + public LabeledConfigMapWithPrefixController(One one, Two two, Three three, Four four) { + this.one = one; + this.two = two; + this.three = three; + this.four = four; + } + + @GetMapping("/labeled-configmap/prefix/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/three") + public String three() { + return three.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/four") + public String four() { + return four.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Four.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Four.java new file mode 100644 index 0000000000..9e87586053 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Four.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "configmap-four") +public class Four { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/One.java similarity index 94% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/One.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/One.java index 1e59b034ed..23bbec8696 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/One.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/One.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Three.java new file mode 100644 index 0000000000..03ab598e73 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "configmap-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Two.java similarity index 94% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Two.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Two.java index 4dde04d3a1..3f0c26823e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Two.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Two.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java new file mode 100644 index 0000000000..6135608b5d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Green; + +/** + * @author wind57 + */ +@SpringBootApplication +@EnableConfigurationProperties({ Blue.class, Green.class }) +public class LabeledConfigMapWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledConfigMapWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java new file mode 100644 index 0000000000..770f618d15 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-profile", + "labeled.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledConfigMapWithProfileBootstrapTests extends LabeledConfigMapWithProfileTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java new file mode 100644 index 0000000000..8f29fcfefa --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub.stubData; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithProfileApp.class, + properties = { "spring.cloud.application.name=labeled-configmap-with-profile", + "labeled.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-configmap-with-profile.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledConfigMapWithProfileConfigDataTests extends LabeledConfigMapWithProfileTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java new file mode 100644 index 0000000000..3f625741fa --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * Stud data is in + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub} + * + * @author wind57 + */ +abstract class LabeledConfigMapWithProfileTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *     this one is taken from : "blue.one". We find "color-configmap" by labels, and
+	 *     "color-configmap-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
+	 *     Since "explicitPrefix=blue", we take "blue.one"
+	 * 
+ */ + @Test + void testBlue() { + this.webClient.get().uri("/labeled-configmap/profile/blue").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("1")); + } + + /** + *
+	 *   this one is taken from : "green-configmap.green-configmap-k8s.green-configmap-prod".
+	 *   We find "green-configmap" by labels, also "green-configmap-k8s" and "green-configmap-prod" exists,
+	 *   because "includeProfileSpecificSources=true" is set.
+	 * 
+ */ + @Test + void testGreen() { + this.webClient.get().uri("/labeled-configmap/profile/green").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java new file mode 100644 index 0000000000..33cc1d11bf --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties.Green; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledConfigMapWithProfileController { + + private final Blue blue; + + private final Green green; + + public LabeledConfigMapWithProfileController(Blue blue, Green green) { + this.blue = blue; + this.green = green; + } + + @GetMapping("/labeled-configmap/profile/blue") + public String blue() { + return blue.getOne(); + } + + @GetMapping("/labeled-configmap/profile/green") + public String green() { + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Blue.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Blue.java new file mode 100644 index 0000000000..e1cad9515e --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Blue.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("blue") +public class Blue { + + private String one; + + public String getOne() { + return one; + } + + public void setOne(String one) { + this.one = one; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java new file mode 100644 index 0000000000..59bc828928 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod") +public class Green { + + private String two; + + private String six; + + private String seven; + + public String getTwo() { + return two; + } + + public void setTwo(String two) { + this.two = two; + } + + public String getSix() { + return six; + } + + public void setSix(String six) { + this.six = six; + } + + public String getSeven() { + return seven; + } + + public void setSeven(String seven) { + this.seven = seven; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java new file mode 100644 index 0000000000..28484bcc14 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class, Four.class }) +public class LabeledSecretWithPrefixApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledSecretWithPrefixApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..0987907d92 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-secret-with-prefix", "labeled.secret.with.prefix.stub=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledSecretWithPrefixBootstrapTests extends LabeledSecretWithPrefixTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..af37c9acca --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithPrefixConfigurationStub.stubData; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class, + properties = { "spring.cloud.application.name=labeled-secret-with-prefix", + "labeled.secret.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-secret-with-prefix.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledSecretWithPrefixConfigDataTests extends LabeledSecretWithPrefixTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java new file mode 100644 index 0000000000..6300f24f2d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithPrefixConfigurationStub; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * The stub data for this test is in : {@link LabeledSecretWithPrefixConfigurationStub} + * + * @author wind57 + */ +abstract class LabeledSecretWithPrefixTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].labels=letter:c'
+	 * 	 ("property", "three")
+	 *
+	 *   We find the secret by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[3].labels=letter:d'
+	 * 	 ("property", "four")
+	 *
+	 *   We find the secret by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-four")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java new file mode 100644 index 0000000000..b6c6066bfb --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledSecretWithPrefixController { + + private final One one; + + private final Two two; + + private final Three three; + + private final Four four; + + public LabeledSecretWithPrefixController(One one, Two two, Three three, Four four) { + this.one = one; + this.two = two; + this.three = three; + this.four = four; + } + + @GetMapping("/labeled-secret/prefix/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/three") + public String three() { + return three.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/four") + public String four() { + return four.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java new file mode 100644 index 0000000000..aefd225d21 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Four.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-four") +public class Four { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java new file mode 100644 index 0000000000..6ad8b4346d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java new file mode 100644 index 0000000000..ca661c7c77 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java new file mode 100644 index 0000000000..265b17e5be --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_prefix/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileApp.java new file mode 100644 index 0000000000..eae8cd695a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileApp.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Green; + +@SpringBootApplication +@EnableConfigurationProperties({ Blue.class, Green.class }) +public class LabeledSecretWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledSecretWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java new file mode 100644 index 0000000000..76ac55925a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-secret-with-profile", + "labeled.secret.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class LabeledSecretWithProfileBootstrapTests extends LabeledSecretWithProfileTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java new file mode 100644 index 0000000000..f4d3a0c827 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub.stubData; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class, + properties = { "spring.application.name=labeled-secret-with-profile", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-secret-with-profile.yaml", + "spring.cloud.kubernetes.config.enabled=false" }) +class LabeledSecretWithProfileConfigDataTests extends LabeledSecretWithProfileTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java new file mode 100644 index 0000000000..6cf9ae37fb --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/* + *
+ *   - secret with name "color-secret", with labels: "{color: blue}" and "explicitPrefix: blue"
+ *   - secret with name "green-secret", with labels: "{color: green}" and "explicitPrefix: blue-again"
+ *   - secret with name "red-secret", with labels "{color: not-red}" and "useNameAsPrefix: true"
+ *   - secret with name "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true
+ *   - secret with name "color-secret-k8s", with labels : "{color: not-blue}"
+ *   - secret with name "green-secret-k8s", with labels : "{color: green-k8s}"
+ *   - secret with name "green-secret-prod", with labels : "{color: green-prod}"
+ * 
+ */ + +/** + * Stubs for this test are in + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub} + * + * @author wind57 + */ +abstract class LabeledSecretWithProfileTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + public void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *     this one is taken from : "blue.one". We find "color-secret" by labels, and
+	 *     "color-secrets-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
+	 *     Since "explicitPrefix=blue", we take "blue.one"
+	 * 
+ */ + @Test + void testBlue() { + this.webClient.get().uri("/labeled-secret/profile/blue").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("1")); + } + + /** + *
+	 *   this one is taken from : "green-secret.green-secret-k8s.green-secret-prod".
+	 *   We find "green-secret" by labels, also "green-secrets-k8s" and "green-secrets-prod" exists,
+	 *   because "includeProfileSpecificSources=true" is set.
+	 * 
+ */ + @Test + void testGreen() { + this.webClient.get().uri("/labeled-secret/profile/green").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java new file mode 100644 index 0000000000..f342746dd7 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties.Green; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledSecretWithProfileController { + + private final Blue blue; + + private final Green green; + + public LabeledSecretWithProfileController(Blue blue, Green green) { + this.blue = blue; + this.green = green; + } + + @GetMapping("/labeled-secret/profile/blue") + public String blue() { + return blue.getOne(); + } + + @GetMapping("/labeled-secret/profile/green") + public String green() { + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Blue.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Blue.java new file mode 100644 index 0000000000..7231da17ac --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Blue.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("blue") +public class Blue { + + private String one; + + public String getOne() { + return one; + } + + public void setOne(String one) { + this.one = one; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java new file mode 100644 index 0000000000..04c7e5c585 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.labeled_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("green-secret.green-secret-k8s.green-secret-prod") +public class Green { + + private String two; + + private String six; + + private String seven; + + public String getTwo() { + return two; + } + + public void setTwo(String two) { + this.two = two; + } + + public String getSix() { + return six; + } + + public void setSix(String six) { + this.six = six; + } + + public String getSeven() { + return seven; + } + + public void setSeven(String seven) { + this.seven = seven; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java similarity index 78% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java index d719c4220e..d6457a5bc8 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources; +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.Two; @SpringBootApplication @EnableConfigurationProperties({ One.class, Two.class, Three.class }) -public class IncludeProfileSpecificSourcesApp { +public class NamedConfigMapWithPrefixApp { public static void main(String[] args) { - SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args); + SpringApplication.run(NamedConfigMapWithPrefixApp.class, args); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..a7600c8e39 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=named-configmap-with-prefix", + "named.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class NamedConfigMapWithPrefixBootstrapTests extends NamedConfigMapWithPrefixTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..725ff856e8 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithPrefixConfigurationStub.stubData; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class, + properties = { "spring.cloud.application.name=named-configmap-with-prefix", + "named.config.map.with.prefix.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-configmap-with-prefix.yaml" }) +class NamedConfigMapWithPrefixConfigDataTests extends NamedConfigMapWithPrefixTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapNameAsPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java similarity index 55% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapNameAsPrefixTests.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java index 77ffd97f86..881b628416 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapNameAsPrefixTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2020 the original author or authors. + * Copyright 2013-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,33 +14,24 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config; +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix; import com.github.tomakehurst.wiremock.client.WireMock; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.WithPrefixApp; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** - * The stub data for this test is in : ConfigMapNameAsPrefixConfigurationStub + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithPrefixConfigurationStub} * * @author wind57 */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WithPrefixApp.class, - properties = { "spring.cloud.bootstrap.name=config-map-name-as-prefix", "config.map.name.as.prefix.stub=true", - "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -public class KubernetesClientConfigMapNameAsPrefixTests { +abstract class NamedConfigMapWithPrefixTests { @Autowired private WebTestClient webClient; @@ -51,7 +42,7 @@ public void afterEach() { } @AfterAll - public static void afterAll() { + static void afterAll() { WireMock.shutdownServer(); } @@ -65,9 +56,9 @@ public static void afterAll() { * */ @Test - public void testOne() { - this.webClient.get().uri("/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("one")); + void testOne() { + this.webClient.get().uri("/named-configmap/prefix/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one")); } /** @@ -80,9 +71,9 @@ public void testOne() { * */ @Test - public void testTwo() { - this.webClient.get().uri("/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("two")); + void testTwo() { + this.webClient.get().uri("/named-configmap/prefix/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); } /** @@ -95,9 +86,9 @@ public void testTwo() { * */ @Test - public void testThree() { - this.webClient.get().uri("/prefix/three").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("three")); + void testThree() { + this.webClient.get().uri("/named-configmap/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/controller/NamedConfigWithPrefixController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/controller/NamedConfigWithPrefixController.java new file mode 100644 index 0000000000..6702619367 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/controller/NamedConfigWithPrefixController.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class NamedConfigWithPrefixController { + + private final One one; + + private final Two two; + + private final Three three; + + public NamedConfigWithPrefixController(One one, Two two, Three three) { + this.one = one; + this.two = two; + this.three = three; + } + + @GetMapping("/named-configmap/prefix/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/named-configmap/prefix/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/named-configmap/prefix/three") + public String three() { + return three.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/One.java new file mode 100644 index 0000000000..82c2a7f42c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Three.java similarity index 95% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Three.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Three.java index ae83c9a944..30c592243c 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Three.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Three.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties; +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Two.java new file mode 100644 index 0000000000..0f45b685c2 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileApp.java new file mode 100644 index 0000000000..81db4b8077 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileApp.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Two; + +/** + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub} + * + * @author wind57 + */ +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class }) +class NamedConfigMapWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(NamedConfigMapWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java new file mode 100644 index 0000000000..9e569f5c70 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = NamedConfigMapWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=named-configmap-with-profile", + "named.config.map.with.profile.stub=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true", "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +@ActiveProfiles("k8s") +class NamedConfigMapWithProfileBootstrapTests extends NamedConfigMapWithProfileTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java new file mode 100644 index 0000000000..28fa10afa1 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub.stubData; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = NamedConfigMapWithProfileApp.class, + properties = { "spring.application.name=named-config-map-with-profile", "include.profile.specific.sources=true", + "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-configmap-with-profile.yaml" }) +@ActiveProfiles("k8s") +class NamedConfigMapWithProfileConfigDataTests extends NamedConfigMapWithProfileTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + public static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileTests.java new file mode 100644 index 0000000000..a997b4e037 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub} + * + * @author wind57 + */ +abstract class NamedConfigMapWithProfileTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + public void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[0].useNameAsPrefix=false'
+	 *   'spring.cloud.kubernetes.config.sources[0].includeProfileSpecificSources=true'
+	 * 	 ("one.property", "one-from-k8s")
+	 *
+	 * 	 As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
+	 * 	 the profile based source.
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-configmap/profile/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one-from-k8s")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
+	 *   'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=false'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two").
+	 *
+	 * 	 Even if there is a profile based source, we disabled reading it.
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-configmap/profile/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[2].name=configmap-three'
+	 *   'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=true'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "config-three"), value is overridden by the one that we read from
+	 * 	 the profile based source
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-configmap/profile/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three-from-k8s")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java similarity index 73% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java index fd8e4db04d..3a1bd2b1d2 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.controller; +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.controller; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.One; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Three; -import org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties.Two; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties.Two; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController -public class IncludeProfileSpecificSourcesController { +public class NamedConfigMapWithProfileController { private final One one; @@ -31,23 +31,23 @@ public class IncludeProfileSpecificSourcesController { private final Three three; - public IncludeProfileSpecificSourcesController(One one, Two two, Three three) { + public NamedConfigMapWithProfileController(One one, Two two, Three three) { this.one = one; this.two = two; this.three = three; } - @GetMapping("/profile-specific/one") + @GetMapping("/named-configmap/profile/one") public String one() { return one.getProperty(); } - @GetMapping("/profile-specific/two") + @GetMapping("/named-configmap/profile/two") public String two() { return two.getProperty(); } - @GetMapping("/profile-specific/three") + @GetMapping("/named-configmap/profile/three") public String three() { return three.getProperty(); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/One.java new file mode 100644 index 0000000000..ac8e271586 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Three.java similarity index 90% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Three.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Three.java index e96da1ebd6..77d6656dc8 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/include_profile_specific_sources/properties/Three.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Three.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("three") +@ConfigurationProperties("configmap-three") public class Three { private String property; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Two.java new file mode 100644 index 0000000000..3d5bbb80d0 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/WithPrefixApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixApp.java similarity index 81% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/WithPrefixApp.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixApp.java index 0bdbb15a00..036776a6ac 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/WithPrefixApp.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixApp.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix; +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.One; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Three; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Two; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.Two; @SpringBootApplication @EnableConfigurationProperties({ One.class, Two.class, Three.class }) -public class WithPrefixApp { +public class NamedSecretWithPrefixApp { public static void main(String[] args) { - SpringApplication.run(WithPrefixApp.class, args); + SpringApplication.run(NamedSecretWithPrefixApp.class, args); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..9c11d631b2 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=named-secret-with-prefix", "named.secret.with.prefix.stub=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class NamedSecretWithPrefixBootstrapTests extends NamedSecretWithPrefixTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..7022d4141d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub.stubData; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithPrefixApp.class, + properties = { "spring.cloud.application.name=named-secret-with-prefix", "named.secret.with.prefix.stub=true", + "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-secret-with-prefix.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class NamedSecretWithPrefixConfigDataTests extends NamedSecretWithPrefixTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixTests.java new file mode 100644 index 0000000000..58a6545d2a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/NamedSecretWithPrefixTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub} + * + * @author wind57 + */ +abstract class NamedSecretWithPrefixTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].name=config-map-three'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "config-map-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-secret/prefix/three").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("three")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/controller/Controller.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java similarity index 75% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/controller/Controller.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java index e4d655780c..facc299a6e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/controller/Controller.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.controller; +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.controller; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.One; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Three; -import org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties.Two; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties.Two; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController -public class Controller { +public class NamedSecretWithPrefixController { private final One one; @@ -31,23 +31,23 @@ public class Controller { private final Three three; - public Controller(One one, Two two, Three three) { + public NamedSecretWithPrefixController(One one, Two two, Three three) { this.one = one; this.two = two; this.three = three; } - @GetMapping("/prefix/one") + @GetMapping("/named-secret/prefix/one") public String one() { return one.getProperty(); } - @GetMapping("/prefix/two") + @GetMapping("/named-secret/prefix/two") public String two() { return two.getProperty(); } - @GetMapping("/prefix/three") + @GetMapping("/named-secret/prefix/three") public String three() { return three.getProperty(); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/One.java similarity index 95% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/One.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/One.java index 4a9eb5e436..80127c1bc7 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/One.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/One.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties; +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Three.java new file mode 100644 index 0000000000..658e23d903 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Two.java similarity index 95% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Two.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Two.java index 7a3d9d8dc7..a28aca72c4 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/config_map_name_as_prefix/properties/Two.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_prefix/properties/Two.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config.applications.config_map_name_as_prefix.properties; +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithLabelApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithLabelApp.java new file mode 100644 index 0000000000..64c7643cae --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithLabelApp.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class }) +public class NamedSecretWithLabelApp { + + public static void main(String[] args) { + SpringApplication.run(NamedSecretWithLabelApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java new file mode 100644 index 0000000000..dc6b22b741 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithLabelApp.class, + properties = { "spring.cloud.bootstrap.name=named-secret-with-profile", "named.secret.with.profile.stub=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class NamedSecretWithProfileBootstrapTests extends NamedSecretWithProfileTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java new file mode 100644 index 0000000000..b0f0439d66 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub.stubData; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithLabelApp.class, + properties = { "spring.cloud.application.name=named-secret-with-profile", "named.secret.with.profile.stub=true", + "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-secret-with-profile.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class NamedSecretWithProfileConfigDataTests extends NamedSecretWithProfileTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileTests.java new file mode 100644 index 0000000000..20093f5dc5 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * The stub data for this test is in : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub} + * + * @author wind57 + */ +abstract class NamedSecretWithProfileTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].includeProfileSpecificSources=true'
+	 * 	 ("one.property", "one-from-k8s")
+	 *
+	 * 	 As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
+	 * 	 the profile based source.
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-secret/profile/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one-from-k8s")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=false'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two").
+	 *
+	 * 	 Even if there is a profile based source, we disabled reading it.
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-secret/profile/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].name=secret-three'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=true'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-three"), value is overridden by the one that we read from
+	 * 	 * 	 the profile based source
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-secret/profile/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three-from-k8s")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/controller/NamedSecretWithProfileController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/controller/NamedSecretWithProfileController.java new file mode 100644 index 0000000000..93758cc0bf --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/controller/NamedSecretWithProfileController.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.One; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class NamedSecretWithProfileController { + + private final One one; + + private final Two two; + + private final Three three; + + public NamedSecretWithProfileController(One one, Two two, Three three) { + this.one = one; + this.two = two; + this.three = three; + } + + @GetMapping("/named-secret/profile/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/named-secret/profile/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/named-secret/profile/three") + public String three() { + return three.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/One.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/One.java new file mode 100644 index 0000000000..f2d558ef98 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Three.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Three.java new file mode 100644 index 0000000000..d4418e3278 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Two.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Two.java new file mode 100644 index 0000000000..64a8f67ecc --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesApp.java new file mode 100644 index 0000000000..17f7d3c768 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Color; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Name; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Shape; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Type; + +@SpringBootApplication +@EnableConfigurationProperties({ Name.class, Shape.class, Color.class, Type.class }) +public class SingleSourceMultipleFilesApp { + + public static void main(String[] args) { + SpringApplication.run(SingleSourceMultipleFilesApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java new file mode 100644 index 0000000000..c5f3fb1a54 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("color") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = SingleSourceMultipleFilesApp.class, + properties = { "spring.cloud.bootstrap.name=single-source-multiple-files", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.client.namespace=spring-k8s", "single.source.multiple.files.stub=true" }) +class SingleSourceMultipleFilesBootstrapTests extends SingleSourceMultipleFilesTests { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java new file mode 100644 index 0000000000..dd9caf954a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SingleSourceMultipleFilesConfigurationStub.stubData; + +/** + * @author wind57 + */ +@ActiveProfiles("color") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = SingleSourceMultipleFilesApp.class, + properties = { "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./single-source-multiple-files.yaml", + "spring.cloud.kubernetes.client.namespace=spring-k8s" }) +class SingleSourceMultipleFilesConfigDataTests extends SingleSourceMultipleFilesTests { + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(server.baseUrl()).build()); + stubData(); + } + + @AfterAll + static void teardown() { + clientUtilsMock.close(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesTests.java new file mode 100644 index 0000000000..6ba97caee4 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/SingleSourceMultipleFilesTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + * + * Stub for this test is here : + * {@link org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SingleSourceMultipleFilesConfigurationStub} + * + * issue: https://github.com/spring-cloud/spring-cloud-kubernetes/issues/640 + * + */ +abstract class SingleSourceMultipleFilesTests { + + @Autowired + private WebTestClient webClient; + + @AfterEach + void afterEach() { + WireMock.reset(); + } + + @AfterAll + static void afterAll() { + WireMock.shutdownServer(); + } + + /** + *
+	 *   "fruit-color.properties" is taken since "spring.application.name=fruit" and
+	 *   "color" is an active profile
+	 * 
+ */ + @Test + void color() { + this.webClient.get().uri("/single_source-multiple-files/color").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("raw:green###ripe:yellow")); + } + + /** + *
+	 *   "fruit.properties" is read, since it matches "spring.application.name"
+	 * 
+ */ + @Test + void name() { + this.webClient.get().uri("/single_source-multiple-files/name").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("banana")); + } + + /** + *
+	 *   shape profile is not active, thus property "fruit-shape.properties" is skipped
+	 *   and as such, a null comes here.
+	 * 
+ */ + @Test + void shape() { + this.webClient.get().uri("/single_source-multiple-files/shape").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.nullValue()); + } + + /** + *
+	 *   this is a non-file property in the configmap
+	 * 
+ */ + @Test + void type() { + this.webClient.get().uri("/single_source-multiple-files/type").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("yummy")); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java new file mode 100644 index 0000000000..8fb655618c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.controller; + +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Color; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Name; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Shape; +import org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties.Type; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SingleSourceMultipleFilesController { + + private final Name name; + + private final Shape shape; + + private final Color color; + + private final Type type; + + public SingleSourceMultipleFilesController(Name name, Shape shape, Color color, Type type) { + this.name = name; + this.shape = shape; + this.color = color; + this.type = type; + } + + @GetMapping("/single_source-multiple-files/type") + public String type() { + return type.getType(); + } + + @GetMapping("/single_source-multiple-files/shape") + public String shape() { + return shape.getRaw(); + } + + @GetMapping("/single_source-multiple-files/color") + public String color() { + return "raw:" + color.getRaw() + "###" + "ripe:" + color.getRipe(); + } + + @GetMapping("/single_source-multiple-files/name") + public String name() { + return name.getName(); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Color.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Color.java new file mode 100644 index 0000000000..8d5cb2d44d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Color.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "color.when") +public class Color { + + private String raw; + + private String ripe; + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getRipe() { + return ripe; + } + + public void setRipe(String ripe) { + this.ripe = ripe; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Name.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Name.java new file mode 100644 index 0000000000..21df50111a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Name.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("cool") +public class Name { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Shape.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Shape.java new file mode 100644 index 0000000000..3a041536d8 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Shape.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("shape.when") +public class Shape { + + private String raw; + + private String ripe; + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getRipe() { + return ripe; + } + + public void setRipe(String ripe) { + this.ripe = ripe; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Type.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Type.java new file mode 100644 index 0000000000..a8e4338b74 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/single_source_multiple_files/properties/Type.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.applications.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("fruit") +public class Type { + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithPrefixConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithPrefixConfigurationStub.java new file mode 100644 index 0000000000..c55e19164a --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithPrefixConfigurationStub.java @@ -0,0 +1,100 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Collections; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("labeled.config.map.with.prefix.stub") +public class LabeledConfigMapWithPrefixConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + apiClient.setDebugging(true); + stubData(); + return apiClient; + } + + public static void stubData() { + + V1ConfigMap one = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-one").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "a")).build()) + .addToData(Collections.singletonMap("one.property", "one")).build(); + + V1ConfigMap two = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-two").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "b")).build()) + .addToData(Collections.singletonMap("property", "two")).build(); + + V1ConfigMap three = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-three").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "c")).build()) + .addToData(Collections.singletonMap("property", "three")).build(); + + V1ConfigMap four = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-four").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "d")).build()) + .addToData(Collections.singletonMap("property", "four")).build(); + + // the actual stub for CoreV1Api calls + V1ConfigMapList configMapList = new V1ConfigMapList(); + configMapList.addItemsItem(one); + configMapList.addItemsItem(two); + configMapList.addItemsItem(three); + configMapList.addItemsItem(four); + + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMapList)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java new file mode 100644 index 0000000000..6feb584856 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java @@ -0,0 +1,135 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Collections; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("labeled.config.map.with.profile.stub") +public class LabeledConfigMapWithProfileConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + apiClient.setDebugging(true); + stubData(); + return apiClient; + } + + /* + *
 - configmap with name "color-configmap", with labels: "{color: blue}" and
+	 * "explicitPrefix: blue" - configmap with name "green-configmap", with labels:
+	 * "{color: green}" and "explicitPrefix: blue-again" - configmap with name
+	 * "red-configmap", with labels "{color: not-red}" and "useNameAsPrefix: true" -
+	 * configmap with name "yellow-configmap" with labels "{color: not-yellow}" and
+	 * useNameAsPrefix: true - configmap with name "color-configmap-k8s", with labels :
+	 * "{color: not-blue}" - configmap with name "green-configmap-k8s", with labels :
+	 * "{color: green-k8s}" - configmap with name "green-configmap-prod", with labels :
+	 * "{color: green-prod}" 
+ */ + public static void stubData() { + + // is found by labels + V1ConfigMap colorConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("color-configmap").withNamespace("spring-k8s") + .withLabels(Map.of("color", "blue")).build()) + .addToData(Collections.singletonMap("one", "1")).build(); + + // is not taken, since "profileSpecificSources=false" for the above + V1ConfigMap colorConfigMapK8s = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("color-configmap-k8s").withNamespace("spring-k8s") + .withLabels(Map.of("color", "not-blue")).build()) + .addToData(Collections.singletonMap("five", "5")).build(); + + // is found by labels + V1ConfigMap greenConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-configmap").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green")).build()) + .addToData(Collections.singletonMap("two", "2")).build(); + + V1ConfigMap greenConfigMapK8s = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-configmap-k8s").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green-k8s")).build()) + .addToData(Collections.singletonMap("six", "6")).build(); + + // is taken because prod profile is active and "profileSpecificSources=true" + V1ConfigMap greenConfigMapProd = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-configmap-prod").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green-prod")).build()) + .addToData(Collections.singletonMap("seven", "7")).build(); + + // not taken + V1ConfigMap redConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("red-configmap").withNamespace("spring-k8s") + .withLabels(Map.of("color", "red")).build()) + .addToData(Collections.singletonMap("three", "3")).build(); + + // not taken + V1ConfigMap yellowConfigMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("yellow-configmap").withNamespace("spring-k8s") + .withLabels(Map.of("color", "yellow")).build()) + .addToData(Collections.singletonMap("four", "4")).build(); + + // the actual stub for CoreV1Api calls + V1ConfigMapList configMaps = new V1ConfigMapList(); + configMaps.addItemsItem(colorConfigMap); + configMaps.addItemsItem(colorConfigMapK8s); + configMaps.addItemsItem(greenConfigMap); + configMaps.addItemsItem(greenConfigMapK8s); + configMaps.addItemsItem(greenConfigMapProd); + configMaps.addItemsItem(redConfigMap); + configMaps.addItemsItem(yellowConfigMap); + + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(configMaps)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java new file mode 100644 index 0000000000..ea30c86b88 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithPrefixConfigurationStub.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Collections; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import io.kubernetes.client.openapi.models.V1SecretList; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("labeled.secret.with.prefix.stub") +public class LabeledSecretWithPrefixConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + apiClient.setDebugging(true); + stubData(); + return apiClient; + } + + public static void stubData() { + V1Secret one = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "a")).withResourceVersion("1").build()) + .addToData(Collections.singletonMap("one.property", "one".getBytes())).build(); + + V1Secret two = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-two").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "b")).withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "two".getBytes())).build(); + + V1Secret three = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-three").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "c")).withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "three".getBytes())).build(); + + V1Secret four = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-four").withNamespace("spring-k8s") + .withLabels(Map.of("letter", "d")).withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "four".getBytes())).build(); + + // the actual stub for CoreV1Api calls + V1SecretList secrets = new V1SecretList(); + secrets.addItemsItem(one); + secrets.addItemsItem(two); + secrets.addItemsItem(three); + secrets.addItemsItem(four); + + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secrets)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java new file mode 100644 index 0000000000..8a7b98a7bc --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java @@ -0,0 +1,134 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Collections; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import io.kubernetes.client.openapi.models.V1SecretList; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("labeled.secret.with.profile.stub") +public class LabeledSecretWithProfileConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + apiClient.setDebugging(true); + stubData(); + return apiClient; + } + + /* + *
 - secret with name "color-secret", with labels: "{color: blue}" and
+	 * "explicitPrefix: blue" - secret with name "green-secret", with labels:
+	 * "{color: green}" and "explicitPrefix: blue-again" - secret with name "red-secret",
+	 * with labels "{color: not-red}" and "useNameAsPrefix: true" - secret with name
+	 * "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true -
+	 * secret with name "color-secret-k8s", with labels : "{color: not-blue}" - secret
+	 * with name "green-secret-k8s", with labels : "{color: green-k8s}" - secret with name
+	 * "green-secret-prod", with labels : "{color: green-prod}" 
+ */ + public static void stubData() { + + // is found by labels + V1Secret colorSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("color-secret").withNamespace("spring-k8s") + .withLabels(Map.of("color", "blue")).build()) + .addToData(Collections.singletonMap("one", "1".getBytes())).build(); + + // is not taken, since "profileSpecificSources=false" for the above + V1Secret colorSecretK8s = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("color-secret-k8s").withNamespace("spring-k8s") + .withLabels(Map.of("color", "not-blue")).build()) + .addToData(Collections.singletonMap("five", "5".getBytes())).build(); + + // is found by labels + V1Secret greenSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-secret").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green")).build()) + .addToData(Collections.singletonMap("two", "2".getBytes())).build(); + + V1Secret greenSecretK8s = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-secret-k8s").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green-k8s")).build()) + .addToData(Collections.singletonMap("six", "6".getBytes())).build(); + + // is taken because prod profile is active and "profileSpecificSources=true" + V1Secret shapeSecretProd = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("green-secret-prod").withNamespace("spring-k8s") + .withLabels(Map.of("color", "green-prod")).build()) + .addToData(Collections.singletonMap("seven", "7".getBytes())).build(); + + // not taken + V1Secret redSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("red-secret").withNamespace("spring-k8s") + .withLabels(Map.of("color", "not-red")).build()) + .addToData(Collections.singletonMap("three", "3".getBytes())).build(); + + // not taken + V1Secret yellowSecret = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("yellow-secret").withNamespace("spring-k8s") + .withLabels(Map.of("color", "not-yellow")).build()) + .addToData(Collections.singletonMap("four", "4".getBytes())).build(); + + // the actual stub for CoreV1Api calls + V1SecretList secrets = new V1SecretList(); + secrets.addItemsItem(colorSecret); + secrets.addItemsItem(colorSecretK8s); + secrets.addItemsItem(greenSecret); + secrets.addItemsItem(greenSecretK8s); + secrets.addItemsItem(shapeSecretProd); + secrets.addItemsItem(redSecret); + secrets.addItemsItem(yellowSecret); + + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(secrets)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/ConfigMapNameAsPrefixConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithPrefixConfigurationStub.java similarity index 95% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/ConfigMapNameAsPrefixConfigurationStub.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithPrefixConfigurationStub.java index a639d71378..c021349cd1 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/ConfigMapNameAsPrefixConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithPrefixConfigurationStub.java @@ -44,8 +44,8 @@ */ @Order(0) @Configuration -@ConditionalOnProperty("config.map.name.as.prefix.stub") -public class ConfigMapNameAsPrefixConfigurationStub { +@ConditionalOnProperty("named.config.map.with.prefix.stub") +public class NamedConfigMapWithPrefixConfigurationStub { @Bean public WireMockServer wireMock() { @@ -64,7 +64,7 @@ public ApiClient apiClient(WireMockServer wireMockServer) { return apiClient; } - private void stubData() { + public static void stubData() { V1ConfigMap one = new V1ConfigMapBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("config-map-one").withNamespace("spring-k8s") .withResourceVersion("1").build()) diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/IncludeProfileSpecificSourcesConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java similarity index 65% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/IncludeProfileSpecificSourcesConfigurationStub.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java index ee37d971c0..ea83ae13e1 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/IncludeProfileSpecificSourcesConfigurationStub.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java @@ -44,8 +44,8 @@ */ @Order(0) @Configuration -@ConditionalOnProperty("include.profile.specific.sources") -class IncludeProfileSpecificSourcesConfigurationStub { +@ConditionalOnProperty("named.config.map.with.profile.stub") +public class NamedConfigMapWithProfileConfigurationStub { @Bean public WireMockServer wireMock() { @@ -64,34 +64,37 @@ public ApiClient apiClient(WireMockServer wireMockServer) { return apiClient; } - private void stubData() { + public static void stubData() { + V1ConfigMap one = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-one-dev").withNamespace("spring-k8s") - .withResourceVersion("1").build()) + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-one").withNamespace("spring-k8s").build()) .addToData(Collections.singletonMap("one.property", "one")).build(); + V1ConfigMap oneFromK8s = new V1ConfigMapBuilder() + .withMetadata( + new V1ObjectMetaBuilder().withName("configmap-one-k8s").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("one.property", "one-from-k8s")).build(); + V1ConfigMap two = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-two").withNamespace("spring-k8s") - .withResourceVersion("1").build()) - .addToData(Collections.singletonMap("two.property", "two")).build(); + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-two").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("property", "two")).build(); - V1ConfigMap twoDev = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-two-dev").withNamespace("spring-k8s") - .withResourceVersion("1").build()) - .addToData(Collections.singletonMap("two.property", "twoDev")).build(); + V1ConfigMap twoFromK8s = new V1ConfigMapBuilder() + .withMetadata( + new V1ObjectMetaBuilder().withName("configmap-two-k8s").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("property", "two-from-k8s")).build(); V1ConfigMap three = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-three").withNamespace("spring-k8s") - .withResourceVersion("1").build()) - .addToData(Collections.singletonMap("three.property", "three")).build(); + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-three").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("property", "three")).build(); - V1ConfigMap threeDev = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-three-dev").withNamespace("spring-k8s") - .withResourceVersion("1").build()) - .addToData(Collections.singletonMap("three.property", "threeDev")).build(); + V1ConfigMap threeFromK8s = new V1ConfigMapBuilder() + .withMetadata( + new V1ObjectMetaBuilder().withName("configmap-three-k8s").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("property", "three-from-k8s")).build(); V1ConfigMapList allConfigMaps = new V1ConfigMapList(); - allConfigMaps.setItems(Arrays.asList(one, two, twoDev, three, threeDev)); + allConfigMaps.setItems(Arrays.asList(one, oneFromK8s, two, twoFromK8s, three, threeFromK8s)); // the actual stub for CoreV1Api calls WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps") diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithPrefixConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithPrefixConfigurationStub.java new file mode 100644 index 0000000000..dc8ab3dfd5 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithPrefixConfigurationStub.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Arrays; +import java.util.Collections; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import io.kubernetes.client.openapi.models.V1SecretList; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("named.secret.with.prefix.stub") +public class NamedSecretWithPrefixConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + stubData(); + return apiClient; + } + + public static void stubData() { + V1Secret one = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("one.property", "one".getBytes())).build(); + + V1Secret two = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-two").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "two".getBytes())).build(); + + V1Secret three = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-three").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "three".getBytes())).build(); + + V1SecretList allSecrets = new V1SecretList(); + allSecrets.setItems(Arrays.asList(one, two, three)); + + // the actual stub for CoreV1Api calls + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allSecrets)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java new file mode 100644 index 0000000000..6031358a1c --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.Arrays; +import java.util.Collections; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretBuilder; +import io.kubernetes.client.openapi.models.V1SecretList; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("named.secret.with.profile.stub") +public class NamedSecretWithProfileConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + stubData(); + return apiClient; + } + + public static void stubData() { + V1Secret one = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-one").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("one.property", "one".getBytes())).build(); + + V1Secret oneWithProfile = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-one-k8s").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("one.property", "one-from-k8s".getBytes())).build(); + + V1Secret two = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-two").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "two".getBytes())).build(); + + V1Secret twoWithProfile = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-two-k8s").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "two-from-k8s".getBytes())).build(); + + V1Secret three = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-three").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "three".getBytes())).build(); + + V1Secret threeFromProfile = new V1SecretBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("secret-three-k8s").withNamespace("spring-k8s") + .withResourceVersion("1").build()) + .addToData(Collections.singletonMap("property", "three-from-k8s".getBytes())).build(); + + V1SecretList allSecrets = new V1SecretList(); + allSecrets.setItems(Arrays.asList(one, oneWithProfile, two, twoWithProfile, three, threeFromProfile)); + + // the actual stub for CoreV1Api calls + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allSecrets)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SingleSourceMultipleFilesConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SingleSourceMultipleFilesConfigurationStub.java new file mode 100644 index 0000000000..e92948d747 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/SingleSourceMultipleFilesConfigurationStub.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.boostrap.stubs; + +import java.util.HashMap; +import java.util.Map; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import io.kubernetes.client.openapi.models.V1ObjectMetaBuilder; +import io.kubernetes.client.util.ClientBuilder; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +/** + * A test bootstrap that takes care to initialize ApiClient _before_ our main bootstrap + * context; with some stub data already present. + * + * @author wind57 + */ +@Order(0) +@Configuration +@ConditionalOnProperty("single.source.multiple.files.stub") +public class SingleSourceMultipleFilesConfigurationStub { + + @Bean + public WireMockServer wireMock() { + WireMockServer server = new WireMockServer(options().dynamicPort()); + server.start(); + WireMock.configureFor("localhost", server.port()); + return server; + } + + @Bean + public ApiClient apiClient(WireMockServer wireMockServer) { + ApiClient apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockServer.port()).build(); + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(apiClient); + stubData(); + return apiClient; + } + + public static void stubData() { + + Map one = new HashMap<>(); + one.put("fruit.type", "yummy"); + one.put("fruit.properties", "cool.name=banana"); + one.put("fruit-color.properties", "color.when.raw=green\ncolor.when.ripe=yellow"); + + // this is not taken, since "shape" is not an active profile + one.put("fruit-shape.properties", "shape.when.raw=small-sphere\nshape.when.ripe=bigger-sphere"); + + V1ConfigMap configMap = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("my-configmap").withNamespace("spring-k8s").build()) + .addToData(one).build(); + + V1ConfigMapList allConfigMaps = new V1ConfigMapList(); + allConfigMaps.addItemsItem(configMap); + + // the actual stub for CoreV1Api calls + WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/configmaps") + .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(allConfigMaps)))); + } + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesClientBootstrapConfigurationInsideK8s.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesClientBootstrapConfigurationInsideK8s.java index eaa61f0ffc..898d664fa9 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesClientBootstrapConfigurationInsideK8s.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesClientBootstrapConfigurationInsideK8s.java @@ -31,7 +31,8 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=abc" }) + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=abc", + "spring.cloud.bootstrap.enabled=true" }) class KubernetesClientBootstrapConfigurationInsideK8s { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesDisabled.java index c930a7bca6..bfe295c45d 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesDisabled.java @@ -31,8 +31,7 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.enabled=false", "kubernetes.informer.enabled=false", - "kubernetes.manifests.enabled=false" }) + properties = { "kubernetes.informer.enabled=false", "kubernetes.manifests.enabled=false" }) class KubernetesDisabled { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabled.java index 0006a5b642..fc43330fe9 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabled.java @@ -28,7 +28,8 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) class KubernetesEnabled { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledConfigDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledConfigDisabled.java index 36dd39a96d..e8ee40b554 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledConfigDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledConfigDisabled.java @@ -31,7 +31,8 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) + properties = { "spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) class KubernetesEnabledConfigDisabled { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledOnPurpose.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledOnPurpose.java index bae93bdb6b..821d15e2c1 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledOnPurpose.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledOnPurpose.java @@ -32,7 +32,8 @@ */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = { "spring.cloud.kubernetes.secrets.enabled=true", - "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) class KubernetesEnabledOnPurpose { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledSecretsDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledSecretsDisabled.java index 5e7e8470bd..db268a9683 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledSecretsDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap_configuration/KubernetesEnabledSecretsDisabled.java @@ -32,7 +32,8 @@ */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = { "spring.cloud.kubernetes.secrets.enabled=false", - "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) class KubernetesEnabledSecretsDisabled { @Autowired diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastDisabled.java new file mode 100644 index 0000000000..f94ff5ecf0 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastDisabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }, + classes = App.class) +class BootstrapConfigFailFastDisabled extends ConfigFailFastDisabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..6ce5113f44 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }, + classes = App.class) +class BootstrapConfigFailFastEnabledButRetryDisabled extends ConfigFailFastEnabledButRetryDisabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryDisabledButSecretsRetryEnabled.java new file mode 100644 index 0000000000..68ecd78781 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryDisabledButSecretsRetryEnabled.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }, + classes = App.class) +class BootstrapConfigRetryDisabledButSecretsRetryEnabled extends ConfigRetryDisabledButSecretsRetryEnabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryEnabled.java new file mode 100644 index 0000000000..e59631cdbb --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/BootstrapConfigRetryEnabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }, + classes = App.class) +class BootstrapConfigRetryEnabled extends ConfigRetryEnabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastDisabled.java new file mode 100644 index 0000000000..33051f0239 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastDisabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, + classes = App.class) +class ConfigDataConfigFailFastDisabled extends ConfigFailFastDisabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..5fc1b2d38d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, + classes = App.class) +class ConfigDataConfigFailFastEnabledButRetryDisabled extends ConfigFailFastEnabledButRetryDisabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java new file mode 100644 index 0000000000..60ced494e4 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, + classes = App.class) +class ConfigDataConfigRetryDisabledButSecretsRetryEnabled extends ConfigRetryDisabledButSecretsRetryEnabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryEnabled.java new file mode 100644 index 0000000000..db2a2c96ec --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigDataConfigRetryEnabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.config.configmap_retry; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, + classes = App.class) +class ConfigDataConfigRetryEnabled extends ConfigRetryEnabled { + +} diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastDisabled.java index 7bdab621e2..ddb6ecef7b 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastDisabled.java @@ -28,8 +28,7 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator; import org.springframework.mock.env.MockEnvironment; @@ -40,16 +39,14 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }, - classes = App.class) -class ConfigFailFastDisabled { +abstract class ConfigFailFastDisabled { private static final String API = "/api/v1/namespaces/default/configmaps"; @@ -86,12 +83,12 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean + @Autowired private KubernetesClientConfigMapPropertySourceLocator propertySourceLocator; @Test void locateShouldNotRetry() { - + propertySourceLocator = spy(propertySourceLocator); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); Assertions.assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java index e40d2c965d..78c255d7c2 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java @@ -28,8 +28,6 @@ import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator; import org.springframework.context.ApplicationContext; @@ -43,18 +41,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", - "spring.main.cloud-platform=KUBERNETES" }, - classes = App.class) -class ConfigFailFastEnabledButRetryDisabled { +abstract class ConfigFailFastEnabledButRetryDisabled { private static final String API = "/api/v1/namespaces/default/configmaps"; @@ -91,7 +85,7 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean + @Autowired private KubernetesClientConfigMapPropertySourceLocator propertySourceLocator; @Autowired @@ -99,13 +93,12 @@ void afterEach() { @Test void locateShouldFailWithoutRetrying() { - + propertySourceLocator = spy(propertySourceLocator); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isFalse(); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap(s) in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); // verify that propertySourceLocator.locate is called only once verify(propertySourceLocator, times(1)).locate(any()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryDisabledButSecretsRetryEnabled.java index 497e3f8691..d71c7a04dd 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryDisabledButSecretsRetryEnabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryDisabledButSecretsRetryEnabled.java @@ -29,8 +29,6 @@ import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator; import org.springframework.context.ApplicationContext; @@ -40,22 +38,17 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", - "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES" }, - classes = App.class) -class ConfigRetryDisabledButSecretsRetryEnabled { +abstract class ConfigRetryDisabledButSecretsRetryEnabled { private static final String API = "/api/v1/namespaces/default/configmaps"; @@ -96,7 +89,7 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean + @Autowired private KubernetesClientConfigMapPropertySourceLocator propertySourceLocator; @Autowired @@ -104,7 +97,7 @@ void afterEach() { @Test void locateShouldFailWithoutRetrying() { - + propertySourceLocator = spy(propertySourceLocator); /* * Enabling secrets retry causes Spring Retry to be enabled and a * RetryOperationsInterceptor bean with NeverRetryPolicy for config maps to be @@ -114,10 +107,10 @@ void locateShouldFailWithoutRetrying() { stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isTrue(); + // TODO not in bootstrap + // assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isTrue(); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap(s) in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); // verify that propertySourceLocator.locate is called only once verify(propertySourceLocator, times(1)).locate(any()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryEnabled.java index 7bfa4e3147..17c2f0f52b 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryEnabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigRetryEnabled.java @@ -33,34 +33,28 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; -import org.springframework.cloud.kubernetes.client.config.RetryableKubernetesClientConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.spy; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", - "spring.main.cloud-platform=KUBERNETES" }, - classes = App.class) -class ConfigRetryEnabled { +abstract class ConfigRetryEnabled { private static final String API = "/api/v1/namespaces/default/configmaps"; @@ -97,12 +91,12 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean - private RetryableKubernetesClientConfigMapPropertySourceLocator propertySourceLocator; + @Autowired + private ConfigMapPropertySourceLocator retryablePl; @Test void locateShouldNotRetryWhenThereIsNoFailure() { - + ConfigMapPropertySourceLocator propertySourceLocator = spy(retryablePl); Map data = new HashMap<>(); data.put("some.prop", "theValue"); data.put("some.number", "0"); @@ -116,7 +110,7 @@ void locateShouldNotRetryWhenThereIsNoFailure() { .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); // verify locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + WireMock.verify(1, getRequestedFor(urlEqualTo(API))); // validate the contents of the property source assertThat(propertySource.getProperty("some.prop")).isEqualTo("theValue"); @@ -125,6 +119,7 @@ void locateShouldNotRetryWhenThereIsNoFailure() { @Test void locateShouldRetryAndRecover() { + ConfigMapPropertySourceLocator propertySourceLocator = spy(retryablePl); Map data = new HashMap<>(); data.put("some.prop", "theValue"); data.put("some.number", "0"); @@ -149,8 +144,8 @@ void locateShouldRetryAndRecover() { PropertySource propertySource = Assertions .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); - // verify retried 4 times - verify(propertySourceLocator, times(4)).locate(any()); + // verify the request was retried 4 times, 5 total request + WireMock.verify(5, getRequestedFor(urlEqualTo(API))); // validate the contents of the property source assertThat(propertySource.getProperty("some.prop")).isEqualTo("theValue"); @@ -159,15 +154,15 @@ void locateShouldRetryAndRecover() { @Test void locateShouldRetryAndFail() { + ConfigMapPropertySourceLocator propertySourceLocator = spy(retryablePl); // fail all the 5 requests stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap(s) in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); - // verify retried 5 times until failure - verify(propertySourceLocator, times(5)).locate(any()); + // verify the request was retried 5 times + WireMock.verify(5, getRequestedFor(urlEqualTo(API))); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetectorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetectorTests.java index 88b0a44b31..1cb8f8359a 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetectorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedConfigMapChangeDetectorTests.java @@ -20,8 +20,8 @@ import java.lang.reflect.Modifier; import java.time.Duration; import java.time.OffsetDateTime; -import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -47,7 +47,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySource; import org.springframework.cloud.kubernetes.client.config.KubernetesClientConfigMapPropertySourceLocator; @@ -64,9 +63,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -110,7 +107,7 @@ void watch() { V1ConfigMap applicationConfig = new V1ConfigMap().kind("ConfigMap") .metadata(new V1ObjectMeta().namespace("default").name("bar1")).data(data); V1ConfigMapList configMapList = new V1ConfigMapList().metadata(new V1ListMeta().resourceVersion("0")) - .items(Arrays.asList(applicationConfig)); + .items(List.of(applicationConfig)); stubFor(get(urlMatching("^/api/v1/namespaces/default/configmaps.*")).inScenario("watch") .whenScenarioStateIs(STARTED).withQueryParam("watch", equalTo("false")) .willReturn(aResponse().withStatus(200).withBody(gson.toJson(configMapList))).willSetStateTo("update")); @@ -147,8 +144,13 @@ void watch() { OkHttpClient httpClient = apiClient.getHttpClient().newBuilder().readTimeout(0, TimeUnit.SECONDS).build(); apiClient.setHttpClient(httpClient); CoreV1Api coreV1Api = new CoreV1Api(apiClient); - ConfigurationUpdateStrategy strategy = mock(ConfigurationUpdateStrategy.class); - when(strategy.getName()).thenReturn("strategy"); + + int[] howMany = new int[1]; + Runnable run = () -> { + ++howMany[0]; + }; + ConfigurationUpdateStrategy strategy = new ConfigurationUpdateStrategy("strategy", run); + KubernetesMockEnvironment environment = new KubernetesMockEnvironment( mock(KubernetesClientConfigMapPropertySource.class)).withProperty("debug", "true"); KubernetesClientConfigMapPropertySourceLocator locator = mock( @@ -162,15 +164,14 @@ void watch() { Thread controllerThread = new Thread(changeDetector::watch); controllerThread.setDaemon(true); controllerThread.start(); - await().timeout(Duration.ofSeconds(5)) - .until(() -> Mockito.mockingDetails(strategy).getInvocations().size() > 4); - verify(strategy, atLeast(3)).reload(); + + await().timeout(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(2)).until(() -> howMany[0] >= 4); } // This is needed when using JDK17 because GSON uses reflection to construct an // OffsetDateTime but that constructor // is protected. - public class GsonOffsetDateTimeAdapter extends TypeAdapter { + public final static class GsonOffsetDateTimeAdapter extends TypeAdapter { @Override public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IOException { @@ -178,7 +179,7 @@ public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IO } @Override - public OffsetDateTime read(JsonReader jsonReader) throws IOException { + public OffsetDateTime read(JsonReader jsonReader) { return OffsetDateTime.now(); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetectorTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetectorTests.java index 4c438e89a7..a61d38499a 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetectorTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/reload/KubernetesClientEventBasedSecretsChangeDetectorTests.java @@ -20,8 +20,8 @@ import java.lang.reflect.Modifier; import java.time.Duration; import java.time.OffsetDateTime; -import java.util.Arrays; import java.util.Base64; +import java.util.List; import java.util.concurrent.TimeUnit; import com.github.tomakehurst.wiremock.WireMockServer; @@ -46,7 +46,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySource; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator; @@ -63,9 +62,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -107,7 +104,7 @@ void watch() { .putStringDataItem("password", Base64.getEncoder().encodeToString("p455w0rd2".getBytes())) .putStringDataItem("username", Base64.getEncoder().encodeToString("user".getBytes())); V1SecretList secretList = new V1SecretList().kind("SecretList").metadata(new V1ListMeta().resourceVersion("0")) - .items(Arrays.asList(dbPassword)); + .items(List.of(dbPassword)); stubFor(get(urlMatching("^/api/v1/namespaces/default/secrets.*")).inScenario("watch") .whenScenarioStateIs(STARTED).withQueryParam("watch", equalTo("false")) @@ -142,8 +139,13 @@ void watch() { OkHttpClient httpClient = apiClient.getHttpClient().newBuilder().readTimeout(0, TimeUnit.SECONDS).build(); apiClient.setHttpClient(httpClient); CoreV1Api coreV1Api = new CoreV1Api(apiClient); - ConfigurationUpdateStrategy strategy = mock(ConfigurationUpdateStrategy.class); - when(strategy.getName()).thenReturn("strategy"); + + int[] howMany = new int[1]; + Runnable run = () -> { + ++howMany[0]; + }; + ConfigurationUpdateStrategy strategy = new ConfigurationUpdateStrategy("strategy", run); + KubernetesMockEnvironment environment = new KubernetesMockEnvironment( mock(KubernetesClientSecretsPropertySource.class)).withProperty("db-password", "p455w0rd"); KubernetesClientSecretsPropertySourceLocator locator = mock(KubernetesClientSecretsPropertySourceLocator.class); @@ -160,15 +162,13 @@ void watch() { controllerThread.setDaemon(true); controllerThread.start(); - await().timeout(Duration.ofSeconds(300)) - .until(() -> Mockito.mockingDetails(strategy).getInvocations().size() > 4); - verify(strategy, atLeast(3)).reload(); + await().timeout(Duration.ofSeconds(10)).pollInterval(Duration.ofSeconds(2)).until(() -> howMany[0] >= 4); } // This is needed when using JDK17 because GSON uses reflection to construct an // OffsetDateTime but that constructor // is protected. - public class GsonOffsetDateTimeAdapter extends TypeAdapter { + public final static class GsonOffsetDateTimeAdapter extends TypeAdapter { @Override public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IOException { @@ -176,7 +176,7 @@ public void write(JsonWriter jsonWriter, OffsetDateTime localDateTime) throws IO } @Override - public OffsetDateTime read(JsonReader jsonReader) throws IOException { + public OffsetDateTime read(JsonReader jsonReader) { return OffsetDateTime.now(); } diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastDisabled.java index 24e9e20ae4..e83623b8c6 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastDisabled.java @@ -28,8 +28,8 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator; import org.springframework.mock.env.MockEnvironment; @@ -40,6 +40,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -49,7 +50,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", - "spring.main.cloud-platform=KUBERNETES" }, + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, classes = Application.class) class SecretsFailFastDisabled { @@ -88,12 +89,12 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean - private KubernetesClientSecretsPropertySourceLocator propertySourceLocator; + @Autowired + private KubernetesClientSecretsPropertySourceLocator psl; @Test void locateShouldNotRetry() { - + KubernetesClientSecretsPropertySourceLocator propertySourceLocator = spy(psl); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); Assertions.assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java index 929a4ea3f5..20e0c2b0e8 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java @@ -30,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator; import org.springframework.context.ApplicationContext; @@ -44,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -54,7 +54,7 @@ properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", - "spring.main.cloud-platform=KUBERNETES" }, + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, classes = Application.class) class SecretsFailFastEnabledButRetryDisabled { @@ -97,21 +97,20 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean - private KubernetesClientSecretsPropertySourceLocator propertySourceLocator; + @Autowired + private KubernetesClientSecretsPropertySourceLocator psl; @Autowired private ApplicationContext context; @Test void locateShouldFailWithoutRetrying() { - + KubernetesClientSecretsPropertySourceLocator propertySourceLocator = spy(psl); stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isFalse(); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); // verify that propertySourceLocator.locate is called only once verify(propertySourceLocator, times(1)).locate(any()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryDisabledButConfigRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryDisabledButConfigRetryEnabled.java index d1bafcbe40..6a04e30c7e 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryDisabledButConfigRetryEnabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryDisabledButConfigRetryEnabled.java @@ -30,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; import org.springframework.cloud.kubernetes.client.config.KubernetesClientSecretsPropertySourceLocator; import org.springframework.context.ApplicationContext; @@ -40,10 +39,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -54,7 +53,8 @@ properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.secrets.name=my-secret", - "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES" }, + "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, classes = Application.class) class SecretsRetryDisabledButConfigRetryEnabled { @@ -97,15 +97,15 @@ void afterEach() { stubConfigMapAndSecretsDefaults(); } - @SpyBean - private KubernetesClientSecretsPropertySourceLocator propertySourceLocator; + @Autowired + private KubernetesClientSecretsPropertySourceLocator psl; @Autowired private ApplicationContext context; @Test void locateShouldFailWithoutRetrying() { - + KubernetesClientSecretsPropertySourceLocator propertySourceLocator = spy(psl); /* * Enabling config retry causes Spring Retry to be enabled and a * RetryOperationsInterceptor bean with NeverRetryPolicy for secrets to be @@ -115,10 +115,10 @@ void locateShouldFailWithoutRetrying() { stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); - assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isTrue(); + // TODO not in bootstrap + // assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isTrue(); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); // verify that propertySourceLocator.locate is called only once verify(propertySourceLocator, times(1)).locate(any()); diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryEnabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryEnabled.java index 3431ff1eba..9d6e9ac80b 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryEnabled.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsRetryEnabled.java @@ -27,38 +27,40 @@ import io.kubernetes.client.openapi.models.V1SecretList; import io.kubernetes.client.util.ClientBuilder; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; -import org.springframework.cloud.kubernetes.client.config.RetryableKubernetesClientSecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.spy; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { - "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.fail-fast=true", - "spring.cloud.kubernetes.secrets.retry.max-attempts=5", "spring.cloud.kubernetes.secrets.name=my-secret", - "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES" }, +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", + "spring.cloud.kubernetes.secrets.retry.max-attempts=5", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, classes = Application.class) class SecretsRetryEnabled { @@ -91,18 +93,18 @@ static void teardown() { clientUtilsMock.close(); } - @AfterEach + @Autowired + private SecretsPropertySourceLocator psl; + + @BeforeEach void afterEach() { WireMock.reset(); stubConfigMapAndSecretsDefaults(); } - @SpyBean - private RetryableKubernetesClientSecretsPropertySourceLocator propertySourceLocator; - @Test void locateShouldNotRetryWhenThereIsNoFailure() { - + SecretsPropertySourceLocator propertySourceLocator = spy(psl); Map data = new HashMap<>(); data.put("some.sensitive.prop", "theSensitiveValue".getBytes()); data.put("some.sensitive.number", "1".getBytes()); @@ -116,7 +118,7 @@ void locateShouldNotRetryWhenThereIsNoFailure() { .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); // verify locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + WireMock.verify(1, getRequestedFor(urlEqualTo(API))); // validate the contents of the property source assertThat(propertySource.getProperty("some.sensitive.prop")).isEqualTo("theSensitiveValue"); @@ -125,6 +127,7 @@ void locateShouldNotRetryWhenThereIsNoFailure() { @Test void locateShouldRetryAndRecover() { + SecretsPropertySourceLocator propertySourceLocator = spy(psl); Map data = new HashMap<>(); data.put("some.sensitive.prop", "theSensitiveValue".getBytes()); data.put("some.sensitive.number", "1".getBytes()); @@ -150,7 +153,7 @@ void locateShouldRetryAndRecover() { .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); // verify retried 4 times - verify(propertySourceLocator, times(4)).locate(any()); + WireMock.verify(4, getRequestedFor(urlEqualTo(API))); // validate the contents of the property source assertThat(propertySource.getProperty("some.sensitive.prop")).isEqualTo("theSensitiveValue"); @@ -159,15 +162,15 @@ void locateShouldRetryAndRecover() { @Test void locateShouldRetryAndFail() { + SecretsPropertySourceLocator propertySourceLocator = spy(psl); // fail all the 5 requests stubFor(get(API).willReturn(aResponse().withStatus(500).withBody("Internal Server Error"))); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .isInstanceOf(IllegalStateException.class).hasMessage("Internal Server Error"); // verify retried 5 times until failure - verify(propertySourceLocator, times(5)).locate(any()); + WireMock.verify(5, getRequestedFor(urlEqualTo(API))); } } diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories index 6332427dc5..8460c48bee 100644 --- a/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-client-config/src/test/resources/META-INF/spring.factories @@ -1,4 +1,11 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \ -org.springframework.cloud.kubernetes.client.config.boostrap.stubs.ConfigMapNameAsPrefixConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithPrefixConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithPrefixConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedSecretWithProfileConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithPrefixConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledSecretWithProfileConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithPrefixConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.LabeledConfigMapWithProfileConfigurationStub, \ +org.springframework.cloud.kubernetes.client.config.boostrap.stubs.SingleSourceMultipleFilesConfigurationStub, \ org.springframework.cloud.kubernetes.client.config.EnableRetryBootstrapConfiguration diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-prefix.yaml new file mode 100644 index 0000000000..40bea776b8 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-prefix.yaml @@ -0,0 +1,21 @@ +spring: + application: + name: labeled-configmap-with-prefix + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-profile.yaml new file mode 100644 index 0000000000..8aa88d2deb --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-profile.yaml @@ -0,0 +1,22 @@ +spring: + application: + name: labeled-configmap-with-profile + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - labels: + color: blue + explicitPrefix: blue + includeProfileSpecificSources: false + - labels: + color: green + - labels: + color: red + - labels: + color: yellow + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml new file mode 100644 index 0000000000..fa6cb72d0f --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-prefix.yaml @@ -0,0 +1,21 @@ +spring: + application: + name: labeled-secret-with-prefix + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-profile.yaml new file mode 100644 index 0000000000..89255f0810 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-profile.yaml @@ -0,0 +1,22 @@ +spring: + application: + name: labeled-secret-with-profile + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - labels: + color: blue + explicitPrefix: blue + includeProfileSpecificSources: false + - labels: + color: green + - labels: + color: red + - labels: + color: yellow + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/config-map-name-as-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-prefix.yaml similarity index 88% rename from spring-cloud-kubernetes-client-config/src/test/resources/config-map-name-as-prefix.yaml rename to spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-prefix.yaml index b52095e4d5..cb7bd5af86 100644 --- a/spring-cloud-kubernetes-client-config/src/test/resources/config-map-name-as-prefix.yaml +++ b/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-prefix.yaml @@ -1,6 +1,6 @@ spring: application: - name: with-prefix + name: named-config-map-with-prefix cloud: kubernetes: config: diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-profile.yaml new file mode 100644 index 0000000000..da49651040 --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-profile.yaml @@ -0,0 +1,17 @@ +spring: + application: + name: named-configmap-with-profile + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - name: configmap-one + useNameAsPrefix: false + - name: configmap-two + explicitPrefix: two + includeProfileSpecificSources: false + - name: configmap-three diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-prefix.yaml new file mode 100644 index 0000000000..8bfda9beaa --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-prefix.yaml @@ -0,0 +1,16 @@ +spring: + application: + name: named-secret-with-prefix + cloud: + kubernetes: + client: + namespace: spring-k8s + secrets: + enableApi: true + useNameAsPrefix: true + sources: + - name: secret-one + useNameAsPrefix: false + - name: secret-two + explicitPrefix: two + - name: secret-three diff --git a/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-profile.yaml new file mode 100644 index 0000000000..6d52b01f1d --- /dev/null +++ b/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-profile.yaml @@ -0,0 +1,18 @@ +spring: + application: + name: named-secret-with-prefix + cloud: + kubernetes: + client: + namespace: spring-k8s + secrets: + enableApi: true + useNameAsPrefix: true + includeProfileSpecificSources: true + sources: + - name: secret-one + useNameAsPrefix: false + - name: secret-two + explicitPrefix: two + includeProfileSpecificSources: false + - name: secret-three diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml b/spring-cloud-kubernetes-client-config/src/test/resources/single-source-multiple-files.yaml similarity index 60% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml rename to spring-cloud-kubernetes-client-config/src/test/resources/single-source-multiple-files.yaml index 7a0c1eaeb6..571e90a02d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml +++ b/spring-cloud-kubernetes-client-config/src/test/resources/single-source-multiple-files.yaml @@ -1,7 +1,9 @@ spring: + application: + name: fruit cloud: kubernetes: config: + namespace: spring-k8s sources: - name: my-configmap - namespace: default diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesClientProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesClientProperties.java index b31a7c1dec..ee62df6d23 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesClientProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/KubernetesClientProperties.java @@ -20,14 +20,21 @@ import org.springframework.boot.context.properties.ConfigurationProperties; +import static org.springframework.cloud.kubernetes.commons.KubernetesClientProperties.PREFIX; + /** * Kubernetes client properties. * * @author Ioannis Canellos */ -@ConfigurationProperties("spring.cloud.kubernetes.client") +@ConfigurationProperties(PREFIX) public class KubernetesClientProperties { + /** + * Configuration properties prefix. + */ + public static final String PREFIX = "spring.cloud.kubernetes.client"; + /** * Default user-agent for kubernetes client. */ diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/AbstractConfigProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/AbstractConfigProperties.java index 82e2bf5923..45df92e73c 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/AbstractConfigProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/AbstractConfigProperties.java @@ -30,7 +30,7 @@ public abstract class AbstractConfigProperties { protected String namespace; - // use config map name to prefix properties + // use config map or secret name to prefix properties protected boolean useNameAsPrefix; // use profile name to append config map name @@ -121,6 +121,8 @@ public static class RetryProperties { */ private int maxAttempts = 6; + private boolean enabled = true; + public long getInitialInterval() { return this.initialInterval; } @@ -153,6 +155,14 @@ public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; } + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableConfigMapPropertySourceLocator.java new file mode 100644 index 0000000000..84cb4f931f --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableConfigMapPropertySourceLocator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.commons.config; + +import java.util.Collection; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.retry.support.RetryTemplate; + +/** + * ConfigMapPropertySourceLocator for when retry is enabled. + * + * @author Ryan Baxter + */ +public class ConfigDataRetryableConfigMapPropertySourceLocator extends ConfigMapPropertySourceLocator { + + private final RetryTemplate retryTemplate; + + private ConfigMapPropertySourceLocator configMapPropertySourceLocator; + + public ConfigDataRetryableConfigMapPropertySourceLocator( + ConfigMapPropertySourceLocator configMapPropertySourceLocator, ConfigMapConfigProperties properties) { + super(properties); + this.configMapPropertySourceLocator = configMapPropertySourceLocator; + this.retryTemplate = RetryTemplate.builder().maxAttempts(properties.getRetry().getMaxAttempts()) + .exponentialBackoff(properties.getRetry().getInitialInterval(), properties.getRetry().getMultiplier(), + properties.getRetry().getMaxInterval()) + .build(); + } + + @Override + protected MapPropertySource getMapPropertySource(NormalizedSource normalizedSource, + ConfigurableEnvironment environment) { + return configMapPropertySourceLocator.getMapPropertySource(normalizedSource, environment); + } + + @Override + public PropertySource locate(Environment environment) { + return retryTemplate.execute(retryContext -> configMapPropertySourceLocator.locate(environment)); + } + + @Override + public Collection> locateCollection(Environment environment) { + return retryTemplate.execute(retryContext -> configMapPropertySourceLocator.locateCollection(environment)); + } + + public void setConfigMapPropertySourceLocator(ConfigMapPropertySourceLocator configMapPropertySourceLocator) { + this.configMapPropertySourceLocator = configMapPropertySourceLocator; + } + + public ConfigMapPropertySourceLocator getConfigMapPropertySourceLocator() { + return configMapPropertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableSecretsPropertySourceLocator.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableSecretsPropertySourceLocator.java new file mode 100644 index 0000000000..2f78f3064d --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigDataRetryableSecretsPropertySourceLocator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.commons.config; + +import java.util.Collection; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.retry.support.RetryTemplate; + +/** + * SecretsPropertySourceLocator for when retry is enabled. + * + * @author Ryan Baxter + */ +public class ConfigDataRetryableSecretsPropertySourceLocator extends SecretsPropertySourceLocator { + + private RetryTemplate retryTemplate; + + private SecretsPropertySourceLocator secretsPropertySourceLocator; + + public ConfigDataRetryableSecretsPropertySourceLocator(SecretsPropertySourceLocator propertySourceLocator, + SecretsConfigProperties secretsConfigProperties) { + super(secretsConfigProperties); + this.secretsPropertySourceLocator = propertySourceLocator; + this.retryTemplate = RetryTemplate.builder().maxAttempts(properties.getRetry().getMaxAttempts()) + .exponentialBackoff(properties.getRetry().getInitialInterval(), properties.getRetry().getMultiplier(), + properties.getRetry().getMaxInterval()) + .build(); + } + + @Override + public PropertySource locate(Environment environment) { + return retryTemplate.execute(retryContext -> secretsPropertySourceLocator.locate(environment)); + } + + @Override + public Collection> locateCollection(Environment environment) { + return retryTemplate.execute(retryContext -> secretsPropertySourceLocator.locateCollection(environment)); + } + + @Override + protected MapPropertySource getPropertySource(ConfigurableEnvironment environment, + NormalizedSource normalizedSource) { + return this.secretsPropertySourceLocator.getPropertySource(environment, normalizedSource); + } + + public SecretsPropertySourceLocator getSecretsPropertySourceLocator() { + return secretsPropertySourceLocator; + } + + public void setSecretsPropertySourceLocator(SecretsPropertySourceLocator secretsPropertySourceLocator) { + this.secretsPropertySourceLocator = secretsPropertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java index 021d737912..bcf0406ac9 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigProperties.java @@ -16,17 +16,20 @@ package org.springframework.cloud.kubernetes.commons.config; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.stream.Stream; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; +import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.getApplicationName; + /** * Config map configuration properties. * @@ -37,18 +40,18 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { /** - * Prefix for Kubernetes secrets configuration properties. + * Prefix for Kubernetes config maps configuration properties. */ public static final String PREFIX = "spring.cloud.kubernetes.config"; - private static final Log LOG = LogFactory.getLog(ConfigMapConfigProperties.class); - private boolean enableApi = true; private List paths = Collections.emptyList(); private List sources = Collections.emptyList(); + private Map labels = Collections.emptyMap(); + public boolean isEnableApi() { return this.enableApi; } @@ -73,27 +76,34 @@ public void setSources(List sources) { this.sources = sources; } + public Map getLabels() { + return labels; + } + + public void setLabels(Map labels) { + this.labels = labels; + } + /** - * @return A list of Source to use. If the user has not specified any Source - * properties, then a single Source is constructed based on the supplied name and - * namespace. - * - * These are the actual name/namespace pairs that are used to create a - * ConfigMapPropertySource. + * @return A list of config map source(s) to use. */ - public List determineSources() { + public List determineSources(Environment environment) { if (this.sources.isEmpty()) { - if (useNameAsPrefix) { - LOG.warn( - "'spring.cloud.kubernetes.config.useNameAsPrefix' is set to 'true', but 'spring.cloud.kubernetes.config.sources'" - + " is empty; as such will default 'useNameAsPrefix' to 'false'"); + List result = new ArrayList<>(2); + String name = getApplicationName(environment, this.name, "ConfigMap"); + result.add(new NamedConfigMapNormalizedSource(name, this.namespace, this.failFast, + this.includeProfileSpecificSources)); + + if (!labels.isEmpty()) { + result.add(new LabeledConfigMapNormalizedSource(this.namespace, this.labels, this.failFast, + ConfigUtils.Prefix.DEFAULT, false)); } - return Collections.singletonList( - new NamedConfigMapNormalizedSource(name, namespace, failFast, "", includeProfileSpecificSources)); + return result; } - return sources.stream() - .map(s -> s.normalize(name, namespace, useNameAsPrefix, includeProfileSpecificSources, failFast)) + return this.sources + .stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels, + this.includeProfileSpecificSources, this.failFast, this.useNameAsPrefix, environment)) .collect(Collectors.toList()); } @@ -112,6 +122,16 @@ public static class Source { */ private String namespace; + /** + * labels of the config map to look for against. + */ + private Map labels = Collections.emptyMap(); + + /** + * An explicit prefix to be used for properties. + */ + private String explicitPrefix; + /** * Use config map name as prefix for properties. Can't be a primitive, we need to * know if it was explicitly set or not @@ -124,11 +144,6 @@ public static class Source { */ protected Boolean includeProfileSpecificSources; - /** - * An explicit prefix to be used for properties. - */ - private String explicitPrefix; - public Source() { } @@ -153,6 +168,10 @@ public Boolean isUseNameAsPrefix() { return useNameAsPrefix; } + public Boolean getUseNameAsPrefix() { + return useNameAsPrefix; + } + public void setUseNameAsPrefix(Boolean useNameAsPrefix) { this.useNameAsPrefix = useNameAsPrefix; } @@ -173,20 +192,47 @@ public void setIncludeProfileSpecificSources(Boolean includeProfileSpecificSourc this.includeProfileSpecificSources = includeProfileSpecificSources; } + public Map getLabels() { + return labels; + } + + public void setLabels(Map labels) { + this.labels = labels; + } + public boolean isEmpty() { return !StringUtils.hasLength(this.name) && !StringUtils.hasLength(this.namespace); } - private NormalizedSource normalize(String defaultName, String defaultNamespace, boolean defaultUseNameAsPrefix, - boolean defaultIncludeProfileSpecificSources, boolean failFast) { + private Stream normalize(String defaultName, String defaultNamespace, + Map defaultLabels, boolean defaultIncludeProfileSpecificSources, boolean failFast, + boolean defaultUseNameAsPrefix, Environment environment) { + + Stream.Builder normalizedSources = Stream.builder(); + String normalizedName = StringUtils.hasLength(this.name) ? this.name : defaultName; String normalizedNamespace = StringUtils.hasLength(this.namespace) ? this.namespace : defaultNamespace; - String prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix, defaultUseNameAsPrefix, - normalizedName); + Map normalizedLabels = this.labels.isEmpty() ? defaultLabels : this.labels; + + String configMapName = getApplicationName(environment, normalizedName, "Config Map"); + + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix, + defaultUseNameAsPrefix, normalizedName); + boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources( defaultIncludeProfileSpecificSources, this.includeProfileSpecificSources); - return new NamedConfigMapNormalizedSource(normalizedName, normalizedNamespace, failFast, prefix, - includeProfileSpecificSources); + NormalizedSource namedBasedSource = new NamedConfigMapNormalizedSource(configMapName, normalizedNamespace, + failFast, prefix, includeProfileSpecificSources); + normalizedSources.add(namedBasedSource); + + if (!normalizedLabels.isEmpty()) { + NormalizedSource labeledBasedSource = new LabeledConfigMapNormalizedSource(normalizedNamespace, labels, + failFast, prefix, includeProfileSpecificSources); + normalizedSources.add(labeledBasedSource); + } + + return normalizedSources.build(); + } @Override diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySourceLocator.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySourceLocator.java index 92db6b4dd2..b9b8cb4086 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySourceLocator.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySourceLocator.java @@ -67,9 +67,9 @@ public PropertySource locate(Environment environment) { CompositePropertySource composite = new CompositePropertySource("composite-configmap"); if (this.properties.isEnableApi()) { - Set sources = new LinkedHashSet<>(this.properties.determineSources()); + Set sources = new LinkedHashSet<>(this.properties.determineSources(environment)); LOG.debug("Config Map normalized sources : " + sources); - sources.forEach(s -> composite.addFirstPropertySource(getMapPropertySourceForSingleConfigMap(env, s))); + sources.forEach(s -> composite.addFirstPropertySource(getMapPropertySource(s, env))); } addPropertySourcesFromPaths(environment, composite); @@ -84,12 +84,6 @@ public Collection> locateCollection(Environment environment) { return PropertySourceLocator.super.locateCollection(environment); } - private MapPropertySource getMapPropertySourceForSingleConfigMap(ConfigurableEnvironment environment, - NormalizedSource normalizedSource) { - - return getMapPropertySource(normalizedSource, environment); - } - private void addPropertySourcesFromPaths(Environment environment, CompositePropertySource composite) { Set uniquePaths = new LinkedHashSet<>(properties.getPaths()); uniquePaths.stream().map(Paths::get).filter(p -> { diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java index 2ea6469805..3bf82e2025 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtils.java @@ -16,13 +16,26 @@ package org.springframework.cloud.kubernetes.commons.config; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import static org.springframework.cloud.kubernetes.commons.config.Constants.FALLBACK_APPLICATION_NAME; +import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; import static org.springframework.cloud.kubernetes.commons.config.Constants.SPRING_APPLICATION_NAME; /** @@ -49,39 +62,57 @@ public static String getApplicationName(Environment env, String configName, Stri /** * @param explicitPrefix value of - * 'spring.cloud.kubernetes.config.sources.explicitPrefix' + * 'spring.cloud.kubernetes.config|secrets.sources.explicitPrefix' * @param useNameAsPrefix value of - * 'spring.cloud.kubernetes.config.sources.useNameAsPrefix' + * 'spring.cloud.kubernetes.config|secrets.sources.useNameAsPrefix' * @param defaultUseNameAsPrefix value of - * 'spring.cloud.kubernetes.config.defaultUseNameAsPrefix' + * 'spring.cloud.kubernetes.config|secrets.defaultUseNameAsPrefix' * @param normalizedName either the name of - * 'spring.cloud.kubernetes.config.sources.name' or - * 'spring.cloud.kubernetes.config.name' - * @return prefix to use in normalized sources, never null + * 'spring.cloud.kubernetes.config|secrets.sources.name' or + * 'spring.cloud.kubernetes.config|secrets.name' + * @return prefix to use in normalized sources */ - public static String findPrefix(String explicitPrefix, Boolean useNameAsPrefix, boolean defaultUseNameAsPrefix, + public static Prefix findPrefix(String explicitPrefix, Boolean useNameAsPrefix, boolean defaultUseNameAsPrefix, String normalizedName) { // if explicitPrefix is set, it takes priority over useNameAsPrefix - // (either the one from 'spring.cloud.kubernetes.config' or - // 'spring.cloud.kubernetes.config.sources') + // (either the one from 'spring.cloud.kubernetes.config|secrets' or + // 'spring.cloud.kubernetes.config|secrets.sources') if (StringUtils.hasText(explicitPrefix)) { - return explicitPrefix; + Prefix.computeKnown(() -> explicitPrefix); + return Prefix.KNOWN; } // useNameAsPrefix is a java.lang.Boolean and if it's != null, users have // specified it explicitly if (useNameAsPrefix != null) { if (useNameAsPrefix) { - return normalizedName; + // this is the case when the source is searched by labels, + // but prefix support is enabled. + // in such cases, the name is not known yet. + if (normalizedName == null) { + return Prefix.DELAYED; + } + + Prefix.computeKnown(() -> normalizedName); + return Prefix.KNOWN; } - return ""; + return Prefix.DEFAULT; } if (defaultUseNameAsPrefix) { - return normalizedName; + + // this is the case when the source is searched by labels, + // but prefix support is enabled.ConfigUtilsTests + // in such cases, the name is not known yet. + if (normalizedName == null) { + return Prefix.DELAYED; + } + + Prefix.computeKnown(() -> normalizedName); + return Prefix.KNOWN; } - return ""; + return Prefix.DEFAULT; } /** @@ -102,11 +133,164 @@ public static boolean includeProfileSpecificSources(boolean defaultIncludeProfil /** * action to take when an Exception happens when dealing with a source. */ - public static void onException(boolean failFast, String message, Exception e) { + public static void onException(boolean failFast, Exception e) { if (failFast) { - throw new IllegalStateException(message, e); + throw new IllegalStateException(e.getMessage(), e); } - LOG.warn(message + ". Ignoring.", e); + LOG.warn(e.getMessage() + ". Ignoring.", e); + } + + /* + * this method will return a SourceData that has a name in the form : + * "configmap.my-configmap.my-configmap-2.namespace" and the "data" from the context + * is appended with prefix. So if incoming is "a=b", the result will be : "prefix.a=b" + */ + public static SourceData withPrefix(String target, PrefixContext context) { + Map withPrefix = CollectionUtils.newHashMap(context.data().size()); + context.data().forEach((key, value) -> withPrefix.put(context.prefix() + "." + key, value)); + + String propertySourceTokens = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, + context.propertySourceNames().stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new))); + return new SourceData(sourceName(target, propertySourceTokens, context.namespace()), withPrefix); + } + + public static String sourceName(String target, String applicationName, String namespace) { + return target + PROPERTY_SOURCE_NAME_SEPARATOR + applicationName + PROPERTY_SOURCE_NAME_SEPARATOR + namespace; + } + + /** + * transforms raw data from one or multiple sources into an entry of source names and + * flattened data that they all hold (potentially overriding entries without any + * defined order). + */ + public static MultipleSourcesContainer processNamedData(List strippedSources, + Environment environment, Set sourceNames, String namespace, boolean decode) { + + Set foundSourceNames = new HashSet<>(); + Map data = new HashMap<>(); + + strippedSources.stream().filter(source -> sourceNames.contains(source.name())).collect(Collectors.toList()) + .forEach(foundSource -> { + String sourceName = foundSource.name(); + LOG.debug("Found source with name : '" + sourceName + " in namespace: '" + namespace + "'"); + foundSourceNames.add(sourceName); + // see if data is a single yaml/properties file and if it needs + // decoding + Map rawData = foundSource.data(); + if (decode) { + rawData = decodeData(rawData); + } + data.putAll(SourceDataEntriesProcessor.processAllEntries(rawData == null ? Map.of() : rawData, + environment)); + }); + + return new MultipleSourcesContainer(foundSourceNames, data); + } + + /** + * transforms raw data from one or multiple sources into an entry of source names and + * flattened data that they all hold (potentially overriding entries without any + * defined order). This method first searches by labels, find the sources, then uses + * these names to find any profile based sources. + */ + + public static MultipleSourcesContainer processLabeledData(List containers, + Environment environment, Map labels, String namespace, Set profiles, + boolean decode) { + + // find sources by provided labels + List sourcesByLabels = containers.stream().filter(one -> { + Map sourceLabels = one.labels(); + Map labelsToSearchAgainst = sourceLabels == null ? Map.of() : sourceLabels; + return labelsToSearchAgainst.entrySet().containsAll((labels.entrySet())); + }).collect(Collectors.toList()); + + // compute profile based sources (based on the ones we found by labels) + List sourceNamesByLabelsWithProfile = new ArrayList<>(); + if (profiles != null && !profiles.isEmpty()) { + for (StrippedSourceContainer one : sourcesByLabels) { + for (String profile : profiles) { + String name = one.name() + "-" + profile; + sourceNamesByLabelsWithProfile.add(name); + } + } + } + + // once we know sources by labels (and thus their names), we can find out + // profiles based sources from the above. This would get all sources + // we are interested in. + List sourcesToTake = containers.stream() + .filter(one -> sourceNamesByLabelsWithProfile.contains(one.name())) + .collect(Collectors.toCollection(ArrayList::new)); + sourcesToTake.addAll(sourcesByLabels); + + Set sourceNames = new HashSet<>(); + Map result = new HashMap<>(); + + sourcesToTake.forEach(source -> { + String foundSourceName = source.name(); + LOG.debug("Loaded source with name : '" + foundSourceName + " in namespace: '" + namespace + "'"); + sourceNames.add(foundSourceName); + + Map rawData = source.data(); + if (decode) { + rawData = decodeData(rawData); + } + result.putAll(SourceDataEntriesProcessor.processAllEntries(rawData, environment)); + }); + + return new MultipleSourcesContainer(sourceNames, result); + } + + public static boolean noSources(List sources, String namespace) { + if (sources == null || sources.isEmpty()) { + LOG.debug("No sources in namespace '" + namespace + "'"); + return true; + } + return false; + } + + private static Map decodeData(Map data) { + Map result = new HashMap<>(CollectionUtils.newHashMap(data.size())); + data.forEach((key, value) -> result.put(key, new String(Base64.getDecoder().decode(value)).trim())); + return result; + } + + public static final class Prefix { + + /** + * prefix has not been provided. + */ + public static final Prefix DEFAULT = new Prefix(() -> ""); + + /** + * prefix has been enabled, but the actual value will be known later; the value + * for the prefix will be the name of the source. (this is the case for a + * prefix-enabled labeled source for example) + */ + public static final Prefix DELAYED = new Prefix(() -> { + throw new IllegalArgumentException("prefix is delayed, needs to be taken elsewhere"); + }); + + /** + * prefix is known at the callsite. + */ + public static Prefix KNOWN; + + public Supplier prefixProvider() { + return prefixProvider; + } + + private final Supplier prefixProvider; + + private Prefix(Supplier prefixProvider) { + this.prefixProvider = prefixProvider; + } + + private static void computeKnown(Supplier supplier) { + KNOWN = new Prefix(supplier); + } + } } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/Constants.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/Constants.java index 84c4b513ba..e8be954fb8 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/Constants.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/Constants.java @@ -59,9 +59,9 @@ public final class Constants { public static final String APPLICATION_PROPERTIES = "application.properties"; /** - * prefix of the configMap. + * reload mode spring property. */ - public static final String PREFIX = "configmap"; + public static final String RELOAD_MODE = "spring.cloud.kubernetes.reload.mode"; private Constants() { } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesBootstrapConfiguration.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesBootstrapConfiguration.java index 477562fd76..9c06059b73 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesBootstrapConfiguration.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesBootstrapConfiguration.java @@ -46,9 +46,9 @@ public class KubernetesBootstrapConfiguration { @Configuration(proxyBeanMethods = false) @EnableRetry(proxyTargetClass = true) @Import(AopAutoConfiguration.class) - static class RetryConfiguration { + public static class RetryConfiguration { - private static RetryOperationsInterceptor retryOperationsInterceptor( + public static RetryOperationsInterceptor retryOperationsInterceptor( AbstractConfigProperties.RetryProperties retryProperties) { return RetryInterceptorBuilder.stateless().backOffOptions(retryProperties.getInitialInterval(), retryProperties.getMultiplier(), retryProperties.getMaxInterval()) diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesConfigDataLoader.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesConfigDataLoader.java new file mode 100644 index 0000000000..624dbdaec3 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/KubernetesConfigDataLoader.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.commons.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.ConfigData; +import org.springframework.boot.context.config.ConfigData.Option; +import org.springframework.boot.context.config.ConfigDataLoader; +import org.springframework.boot.context.config.ConfigDataLoaderContext; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; +import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; + +/** + * @author Ryan Baxter + */ +public class KubernetesConfigDataLoader implements ConfigDataLoader, Ordered { + + @Override + public ConfigData load(ConfigDataLoaderContext context, KubernetesConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + + List> propertySources = new ArrayList<>(2); + ConfigurableBootstrapContext bootstrapContext = context.getBootstrapContext(); + Environment env = resource.getEnvironment(); + + if (bootstrapContext.isRegistered(ConfigMapPropertySourceLocator.class)) { + propertySources.add(bootstrapContext.get(ConfigMapPropertySourceLocator.class).locate(env)); + } + if (bootstrapContext.isRegistered(SecretsPropertySourceLocator.class)) { + propertySources.add(bootstrapContext.get(SecretsPropertySourceLocator.class).locate(env)); + } + + // boot 2.4.5+ + return new ConfigData(propertySources, propertySource -> { + String propertySourceName = propertySource.getName(); + List
diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java index 19dc35b65a..66e4894868 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/main/java/org/springframework/cloud/kubernetes/configserver/KubernetesConfigServerAutoConfiguration.java @@ -77,7 +77,7 @@ public KubernetesPropertySourceSupplier configMapPropertySourceSupplier( namespaces.forEach(space -> { NamedConfigMapNormalizedSource source = new NamedConfigMapNormalizedSource(applicationName, space, - false, "", true); + false, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space, springEnv); @@ -96,7 +96,7 @@ public KubernetesPropertySourceSupplier secretsPropertySourceSupplier(Kubernetes List propertySources = new ArrayList<>(); namespaces.forEach(space -> { - NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false); + NormalizedSource source = new NamedSecretNormalizedSource(applicationName, space, false, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, space, springEnv); propertySources.add(new KubernetesClientSecretsPropertySource(context)); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/BootstrapConfigServerIntegrationTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/BootstrapConfigServerIntegrationTest.java new file mode 100644 index 0000000000..76ddb5cf7c --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/BootstrapConfigServerIntegrationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configserver; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=default", + "spring.profiles.include=kubernetes", "spring.cloud.kubernetes.secrets.enableApi=true", "debug=true", + "spring.cloud.bootstrap.enabled=true" }, + classes = { KubernetesConfigServerApplication.class }) +public class BootstrapConfigServerIntegrationTest extends ConfigServerIntegrationTest { + +} diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigDataConfigServerIntegrationTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigDataConfigServerIntegrationTest.java new file mode 100644 index 0000000000..943b26b12e --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigDataConfigServerIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.configserver; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import io.kubernetes.client.util.ClientBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.MockedStatic; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.mockito.Mockito.mockStatic; + +/** + * @author Ryan Baxter + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=default", + "spring.profiles.include=kubernetes", "spring.cloud.kubernetes.secrets.enableApi=true", "debug=true", + "spring.config.import=kubernetes:" }, + classes = { KubernetesConfigServerApplication.class }) +public class ConfigDataConfigServerIntegrationTest extends ConfigServerIntegrationTest { + + private static WireMockServer wireMockServer; + + private static MockedStatic clientUtilsMock; + + @BeforeAll + static void setup() { + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + WireMock.configureFor(wireMockServer.port()); + + clientUtilsMock = mockStatic(KubernetesClientUtils.class); + clientUtilsMock.when(KubernetesClientUtils::kubernetesApiClient) + .thenReturn(new ClientBuilder().setBasePath(wireMockServer.baseUrl()).build()); + } + + @AfterAll + static void teardown() { + wireMockServer.stop(); + clientUtilsMock.close(); + } + + @AfterEach + void afterEach() { + WireMock.reset(); + } + +} diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesDisabled.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesDisabled.java index 9bb21e55f5..1661e6f995 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesDisabled.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesDisabled.java @@ -28,8 +28,7 @@ * @author Ryan Baxter */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { "spring.profiles.include=kubernetes,kubernetesdisabled", "spring.cloud.kubernetes.enabled=false", - "debug=true" }, + properties = { "spring.profiles.include=kubernetes,kubernetesdisabled", "debug=true" }, classes = { KubernetesConfigServerApplication.class, MockConfig.class }) class ConfigServerAutoConfigurationKubernetesDisabled { diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesProfileMissing.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesProfileMissing.java index 7bedf8686c..9ff3f98dc4 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesProfileMissing.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerAutoConfigurationKubernetesProfileMissing.java @@ -29,8 +29,7 @@ */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { KubernetesConfigServerApplication.class, MockConfig.class }, - properties = { "spring.cloud.kubernetes.enabled=false", "spring.profiles.include=kubernetes,kubernetesdisabled", - "debug=true" }) + properties = { "spring.profiles.include=kubernetes,kubernetesdisabled", "debug=true" }) class ConfigServerAutoConfigurationKubernetesProfileMissing { @Autowired diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java index 1e3386cf98..141af25611 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/ConfigServerIntegrationTest.java @@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.config.environment.Environment; @@ -41,11 +40,7 @@ /** * @author Ryan Baxter */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=default", - "spring.profiles.include=kubernetes", "spring.cloud.kubernetes.secrets.enableApi=true", "debug=true" }, - classes = { KubernetesConfigServerApplication.class }) -public class ConfigServerIntegrationTest { +abstract class ConfigServerIntegrationTest { @Autowired private TestRestTemplate testRestTemplate; @@ -77,7 +72,7 @@ public void enabled() { assertThat(env.getPropertySources().size()).isEqualTo(2); assertThat(env.getPropertySources().get(0).getName().equals("configmap.test-cm.default")).isTrue(); assertThat(env.getPropertySources().get(0).getSource().get("app.name")).isEqualTo("test"); - assertThat(env.getPropertySources().get(1).getName().equals("secrets.test-cm.default")).isTrue(); + assertThat(env.getPropertySources().get(1).getName().equals("secret.test-cm.default")).isTrue(); assertThat(env.getPropertySources().get(1).getSource().get("password")).isEqualTo("p455w0rd"); assertThat(env.getPropertySources().get(1).getSource().get("username")).isEqualTo("user"); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java index 76246aa986..bd3ce126f7 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java @@ -50,24 +50,23 @@ */ class KubernetesEnvironmentRepositoryTests { - private static List kubernetesPropertySourceSuppliers = new ArrayList<>(); + private static final List KUBERNETES_PROPERTY_SOURCE_SUPPLIER = new ArrayList<>(); + + private static final String VALUE = "dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n"; + + private static final String DEFAULT_NAMESPACE = "default"; private static final V1ConfigMapList CONFIGMAP_DEFAULT_LIST = new V1ConfigMapList() .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("application").withNamespace("default") - .withResourceVersion("1").build()) - .addToData("application.yaml", - "dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n") - .build()) + .withMetadata( + new V1ObjectMetaBuilder().withName("application").withNamespace(DEFAULT_NAMESPACE).build()) + .addToData("application.yaml", VALUE).build()) .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("stores").withNamespace("default") - .withResourceVersion("1").build()) - .addToData("application.yaml", - "dummy:\n property:\n string2: \"a\"\n int2: 1\n bool2: true\n") - .build()) + .withMetadata(new V1ObjectMetaBuilder().withName("stores").withNamespace(DEFAULT_NAMESPACE).build()) + .addToData("application.yaml", VALUE).build()) .addItemsItem(new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("stores-dev").withNamespace("default") - .withResourceVersion("1").build()) + .withMetadata( + new V1ObjectMetaBuilder().withName("stores-dev").withNamespace(DEFAULT_NAMESPACE).build()) .addToData("application.yaml", "dummy:\n property:\n string1: \"a\"\n string2: \"b\"\n int2: 2\n bool2: false\n") .build()); @@ -88,25 +87,26 @@ class KubernetesEnvironmentRepositoryTests { .addToItems(new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("stores").withResourceVersion("0") .withNamespace("default").build()) - .addToData("password", "p455w0rd".getBytes()).addToData("username", "stores".getBytes()).build()) + .addToData("password", "password-from-stores".getBytes()).addToData("username", "stores".getBytes()) + .build()) .addToItems(new V1SecretBuilder() .withMetadata(new V1ObjectMetaBuilder().withName("stores-dev").withResourceVersion("0") .withNamespace("default").build()) - .addToData("password", "p455w0rd".getBytes()).addToData("username", "stores-dev".getBytes()) - .build()) + .addToData("password", "password-from-stores-dev".getBytes()) + .addToData("username", "stores-dev".getBytes()).build()) .build(); @BeforeAll public static void before() { - kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> { + KUBERNETES_PROPERTY_SOURCE_SUPPLIER.add((coreApi, applicationName, namespace, springEnv) -> { List propertySources = new ArrayList<>(); - NormalizedSource defaultSource = new NamedConfigMapNormalizedSource(applicationName, "default", false, "", + NormalizedSource defaultSource = new NamedConfigMapNormalizedSource(applicationName, "default", false, true); KubernetesClientConfigContext defaultContext = new KubernetesClientConfigContext(coreApi, defaultSource, "default", springEnv); - NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, "", true); + NormalizedSource devSource = new NamedConfigMapNormalizedSource(applicationName, "dev", false, true); KubernetesClientConfigContext devContext = new KubernetesClientConfigContext(coreApi, devSource, "dev", springEnv); @@ -114,10 +114,10 @@ public static void before() { propertySources.add(new KubernetesClientConfigMapPropertySource(devContext)); return propertySources; }); - kubernetesPropertySourceSuppliers.add((coreApi, applicationName, namespace, springEnv) -> { + KUBERNETES_PROPERTY_SOURCE_SUPPLIER.add((coreApi, applicationName, namespace, springEnv) -> { List propertySources = new ArrayList<>(); - NormalizedSource source = new NamedSecretNormalizedSource(applicationName, "default", false); + NormalizedSource source = new NamedSecretNormalizedSource(applicationName, "default", false, true); KubernetesClientConfigContext context = new KubernetesClientConfigContext(coreApi, source, "default", springEnv); @@ -136,12 +136,12 @@ public void testApplicationCase() throws ApiException { when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST); KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, - kubernetesPropertySourceSuppliers, "default"); + KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default"); Environment environment = environmentRepository.findOne("application", "", ""); assertThat(environment.getPropertySources().size()).isEqualTo(2); environment.getPropertySources().forEach(propertySource -> { assertThat(propertySource.getName().equals("configmap.application.default") - || propertySource.getName().equals("secrets.application.default")).isTrue(); + || propertySource.getName().equals("secret.application.default")).isTrue(); if (propertySource.getName().equals("configmap.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(3); assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); @@ -166,22 +166,22 @@ public void testStoresCase() throws ApiException { when(coreApi.listNamespacedSecret(eq("default"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null))).thenReturn(SECRET_LIST); KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, - kubernetesPropertySourceSuppliers, "default"); + KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default"); Environment environment = environmentRepository.findOne("stores", "", ""); assertThat(environment.getPropertySources().size()).isEqualTo(5); environment.getPropertySources().forEach(propertySource -> { assertThat(propertySource.getName().equals("configmap.application.default") - || propertySource.getName().equals("secrets.application.default") + || propertySource.getName().equals("secret.application.default") || propertySource.getName().equals("configmap.stores.default") || propertySource.getName().equals("configmap.stores.dev") - || propertySource.getName().equals("secrets.stores.default")).isTrue(); + || propertySource.getName().equals("secret.stores.default")).isTrue(); if (propertySource.getName().equals("configmap.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(3); assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a"); } - if (propertySource.getName().equals("secrets.application.default")) { + if (propertySource.getName().equals("secret.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); assertThat(propertySource.getSource().get("username")).isEqualTo("user"); assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd"); @@ -198,10 +198,10 @@ public void testStoresCase() throws ApiException { assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev"); } - if (propertySource.getName().equals("secrets.stores.default")) { + if (propertySource.getName().equals("secret.stores.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); assertThat(propertySource.getSource().get("username")).isEqualTo("stores"); - assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd"); + assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores"); } }); } @@ -216,22 +216,22 @@ public void testStoresProfileCase() throws ApiException { when(coreApi.listNamespacedConfigMap(eq("dev"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null))).thenReturn(CONFIGMAP_DEV_LIST); KubernetesEnvironmentRepository environmentRepository = new KubernetesEnvironmentRepository(coreApi, - kubernetesPropertySourceSuppliers, "default"); + KUBERNETES_PROPERTY_SOURCE_SUPPLIER, "default"); Environment environment = environmentRepository.findOne("stores", "dev", ""); assertThat(environment.getPropertySources().size()).isEqualTo(5); environment.getPropertySources().forEach(propertySource -> { assertThat(propertySource.getName().equals("configmap.application.default") - || propertySource.getName().equals("secrets.application.default") + || propertySource.getName().equals("secret.application.default") || propertySource.getName().equals("configmap.stores.stores-dev.default") || propertySource.getName().equals("configmap.stores.dev") - || propertySource.getName().equals("secrets.stores.default")).isTrue(); + || propertySource.getName().equals("secret.stores.stores-dev.default")).isTrue(); if (propertySource.getName().equals("configmap.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(3); assertThat(propertySource.getSource().get("dummy.property.int2")).isEqualTo(1); assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("a"); } - else if (propertySource.getName().equals("secrets.application.default")) { + else if (propertySource.getName().equals("secret.application.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); assertThat(propertySource.getSource().get("username")).isEqualTo("user"); assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd"); @@ -249,13 +249,10 @@ else if (propertySource.getName().equals("configmap.stores.dev")) { assertThat(propertySource.getSource().get("dummy.property.bool2")).isEqualTo(true); assertThat(propertySource.getSource().get("dummy.property.string2")).isEqualTo("dev"); } - // Currently KubernetesClientSecretsPropertySource does not take into account - // profiles, so that plays no role at the moment - // See https://github.com/spring-cloud/spring-cloud-kubernetes/issues/880 - else if (propertySource.getName().equals("secrets.stores.default")) { + else if (propertySource.getName().equals("secret.stores.stores-dev.default")) { assertThat(propertySource.getSource().size()).isEqualTo(2); - assertThat(propertySource.getSource().get("username")).isEqualTo("stores"); - assertThat(propertySource.getSource().get("password")).isEqualTo("p455w0rd"); + assertThat(propertySource.getSource().get("username")).isEqualTo("stores-dev"); + assertThat(propertySource.getSource().get("password")).isEqualTo("password-from-stores-dev"); } else { Assertions.fail("no match in property source names"); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/pom.xml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/pom.xml index aa33464a83..ce0ebd2ae5 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/pom.xml +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/pom.xml @@ -3,9 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - spring-cloud-kubernetes-controllers org.springframework.cloud -3.0.0-SNAPSHOT + spring-cloud-kubernetes-controllers + 3.0.0-SNAPSHOT 4.0.0 diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java index 4bc967ef94..a92f56eeba 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetector.java @@ -21,6 +21,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.bus.BusProperties; +import org.springframework.cloud.bus.event.PathDestinationFactory; import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; @@ -39,7 +40,7 @@ public class BusEventBasedConfigMapWatcherChangeDetector extends ConfigMapWatche private ApplicationEventPublisher applicationEventPublisher; - private BusProperties busProperties; + private final BusProperties busProperties; public BusEventBasedConfigMapWatcherChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, @@ -54,8 +55,8 @@ public BusEventBasedConfigMapWatcherChangeDetector(AbstractEnvironment environme @Override protected Mono triggerRefresh(ConfigMap configMap) { - this.applicationEventPublisher.publishEvent( - new RefreshRemoteApplicationEvent(configMap, busProperties.getId(), configMap.getMetadata().getName())); + this.applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(configMap, busProperties.getId(), + new PathDestinationFactory().getDestination(configMap.getMetadata().getName()))); return Mono.empty(); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java index 446770321a..5fa9c94d8f 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetector.java @@ -21,6 +21,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.bus.BusProperties; +import org.springframework.cloud.bus.event.PathDestinationFactory; import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; @@ -39,7 +40,7 @@ public class BusEventBasedSecretsWatcherChangeDetector extends SecretsWatcherCha private ApplicationEventPublisher applicationEventPublisher; - private BusProperties busProperties; + private final BusProperties busProperties; public BusEventBasedSecretsWatcherChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, @@ -54,8 +55,8 @@ public BusEventBasedSecretsWatcherChangeDetector(AbstractEnvironment environment @Override protected Mono triggerRefresh(Secret secret) { - this.applicationEventPublisher.publishEvent( - new RefreshRemoteApplicationEvent(secret, busProperties.getId(), secret.getMetadata().getName())); + this.applicationEventPublisher.publishEvent(new RefreshRemoteApplicationEvent(secret, busProperties.getId(), + new PathDestinationFactory().getDestination(secret.getMetadata().getName()))); return Mono.empty(); } diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java index 02d6ffb080..b7cc010c4a 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigMapWatcherChangeDetector.java @@ -29,7 +29,7 @@ import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; -import org.springframework.cloud.kubernetes.fabric8.config.reload.EventBasedConfigMapChangeDetector; +import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedConfigMapChangeDetector; import org.springframework.core.env.AbstractEnvironment; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @@ -37,11 +37,11 @@ * @author Ryan Baxter * @author Kris Iyer */ -public abstract class ConfigMapWatcherChangeDetector extends EventBasedConfigMapChangeDetector { +public abstract class ConfigMapWatcherChangeDetector extends Fabric8EventBasedConfigMapChangeDetector { protected Log log = LogFactory.getLog(getClass()); - private ScheduledExecutorService executorService; + private final ScheduledExecutorService executorService; protected ConfigurationWatcherConfigurationProperties k8SConfigurationProperties; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java index e81adba52e..b4fd9f9817 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/SecretsWatcherChangeDetector.java @@ -29,7 +29,7 @@ import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; -import org.springframework.cloud.kubernetes.fabric8.config.reload.EventBasedSecretsChangeDetector; +import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedSecretsChangeDetector; import org.springframework.core.env.AbstractEnvironment; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @@ -37,11 +37,11 @@ * @author Ryan Baxter * @author Kris Iyer */ -public abstract class SecretsWatcherChangeDetector extends EventBasedSecretsChangeDetector { +public abstract class SecretsWatcherChangeDetector extends Fabric8EventBasedSecretsChangeDetector { protected Log log = LogFactory.getLog(getClass()); - private ScheduledExecutorService executorService; + private final ScheduledExecutorService executorService; protected ConfigurationWatcherConfigurationProperties k8SConfigurationProperties; diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml new file mode 100644 index 0000000000..f8da385044 --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml @@ -0,0 +1,11 @@ +spring: + cloud: + bus: + enabled: true + stream: + default-binder: rabbit +management: + health: + rabbit: + enabled: true + diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-kafka.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-kafka.yaml new file mode 100644 index 0000000000..76e033c061 --- /dev/null +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-kafka.yaml @@ -0,0 +1,6 @@ +spring: + cloud: + bus: + enabled: true + stream: + default-binder: kafka diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application.yml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application.yml index db2a799ad3..ad35cc314b 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application.yml +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + config: + import: "kubernetes:" application: name: spring-cloud-kubernetes-configuration-watcher cloud: @@ -13,27 +15,9 @@ management: health: rabbit: enabled: false - probes: - enabled: true + endpoint: + health: + probes: + enabled: true server: port: 8888 ---- -spring: - profiles: bus-amqp - cloud: - bus: - enabled: true - stream: - default-binder: rabbit -management: - health: - rabbit: - enabled: true ---- -spring: - profiles: bus-kafka - cloud: - bus: - enabled: true - stream: - default-binder: kafka \ No newline at end of file diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java index ea7effb194..52d82a6b10 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedConfigMapWatcherChangeDetectorTests.java @@ -19,12 +19,12 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; @@ -42,14 +42,16 @@ * @author Ryan Baxter * @author Kris Iyer */ -@RunWith(MockitoJUnitRunner.class) -public class BusEventBasedConfigMapWatcherChangeDetectorTests { +@ExtendWith(MockitoExtension.class) +class BusEventBasedConfigMapWatcherChangeDetectorTests { - @Mock - private KubernetesClient client; + private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy", + () -> { + + }); @Mock - private ConfigurationUpdateStrategy updateStrategy; + private KubernetesClient client; @Mock private Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator; @@ -62,24 +64,22 @@ public class BusEventBasedConfigMapWatcherChangeDetectorTests { private BusEventBasedConfigMapWatcherChangeDetector changeDetector; - private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties; - private BusProperties busProperties; - @Before - public void setup() { + @BeforeEach + void setup() { MockEnvironment mockEnvironment = new MockEnvironment(); ConfigReloadProperties configReloadProperties = new ConfigReloadProperties(); - configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties(); + ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties(); busProperties = new BusProperties(); changeDetector = new BusEventBasedConfigMapWatcherChangeDetector(mockEnvironment, configReloadProperties, - client, updateStrategy, fabric8ConfigMapPropertySourceLocator, busProperties, + client, UPDATE_STRATEGY, fabric8ConfigMapPropertySourceLocator, busProperties, configurationWatcherConfigurationProperties, threadPoolTaskExecutor); changeDetector.setApplicationEventPublisher(applicationEventPublisher); } @Test - public void triggerRefreshWithConfigMap() { + void triggerRefreshWithConfigMap() { ObjectMeta objectMeta = new ObjectMeta(); objectMeta.setName("foo"); ConfigMap configMap = new ConfigMap(); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java index 109ecb3094..c7a6adb0bc 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/BusEventBasedSecretsWatcherChangeDetectorTests.java @@ -19,12 +19,12 @@ import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.KubernetesClient; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.cloud.bus.BusProperties; import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; @@ -42,14 +42,16 @@ * @author Ryan Baxter * @author Kris Iyer */ -@RunWith(MockitoJUnitRunner.class) -public class BusEventBasedSecretsWatcherChangeDetectorTests { +@ExtendWith(MockitoExtension.class) +class BusEventBasedSecretsWatcherChangeDetectorTests { - @Mock - private KubernetesClient client; + private static final ConfigurationUpdateStrategy UPDATE_STRATEGY = new ConfigurationUpdateStrategy("strategy", + () -> { + + }); @Mock - private ConfigurationUpdateStrategy updateStrategy; + private KubernetesClient client; @Mock private Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator; @@ -62,24 +64,22 @@ public class BusEventBasedSecretsWatcherChangeDetectorTests { private BusEventBasedSecretsWatcherChangeDetector changeDetector; - private ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties; - private BusProperties busProperties; - @Before - public void setup() { + @BeforeEach + void setup() { MockEnvironment mockEnvironment = new MockEnvironment(); ConfigReloadProperties configReloadProperties = new ConfigReloadProperties(); - configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties(); + ConfigurationWatcherConfigurationProperties configurationWatcherConfigurationProperties = new ConfigurationWatcherConfigurationProperties(); busProperties = new BusProperties(); changeDetector = new BusEventBasedSecretsWatcherChangeDetector(mockEnvironment, configReloadProperties, client, - updateStrategy, fabric8SecretsPropertySourceLocator, busProperties, + UPDATE_STRATEGY, fabric8SecretsPropertySourceLocator, busProperties, configurationWatcherConfigurationProperties, threadPoolTaskExecutor); changeDetector.setApplicationEventPublisher(applicationEventPublisher); } @Test - public void triggerRefreshWithSecret() { + void triggerRefreshWithSecret() { ObjectMeta objectMeta = new ObjectMeta(); objectMeta.setName("foo"); Secret secret = new Secret(); diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationPropertiesTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationPropertiesTests.java index 9c08860cb6..04ae6c32fe 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationPropertiesTests.java +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigurationWatcherConfigurationPropertiesTests.java @@ -16,17 +16,17 @@ package org.springframework.cloud.kubernetes.configuration.watcher; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * @author Ryan Baxter */ -public class ConfigurationWatcherConfigurationPropertiesTests { +class ConfigurationWatcherConfigurationPropertiesTests { @Test - public void setActuatorPath() { + void setActuatorPath() { ConfigurationWatcherConfigurationProperties properties = new ConfigurationWatcherConfigurationProperties(); properties.setActuatorPath("foo"); assertThat(properties.getActuatorPath()).isEqualTo("/foo"); diff --git a/spring-cloud-kubernetes-dependencies/pom.xml b/spring-cloud-kubernetes-dependencies/pom.xml index 207b16b6d4..f512074556 100644 --- a/spring-cloud-kubernetes-dependencies/pom.xml +++ b/spring-cloud-kubernetes-dependencies/pom.xml @@ -35,7 +35,7 @@ 1.6.0.Final 1.15.2 0.13.0 - 5.9.0 + 5.12.1 13.0.2 1.7.7.1 2.26.3 diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/DiscoveryServerUrlInvalidException.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/DiscoveryServerUrlInvalidException.java index ed173af393..e1629d5218 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/DiscoveryServerUrlInvalidException.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/DiscoveryServerUrlInvalidException.java @@ -22,7 +22,7 @@ public class DiscoveryServerUrlInvalidException extends RuntimeException { public DiscoveryServerUrlInvalidException() { - super("spring.cloud.kubernetes.discovery-server-url must be specified and a valid URL."); + super("spring.cloud.kubernetes.discovery.discovery-server-url must be specified and a valid URL."); } } diff --git a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java index acab803eed..ecc47b7fc3 100644 --- a/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-kubernetes-discovery/src/main/java/org/springframework/cloud/kubernetes/discovery/KubernetesDiscoveryClientAutoConfiguration.java @@ -57,6 +57,7 @@ public static class Servlet { @Bean @ConditionalOnMissingClass("org.springframework.web.reactive.function.client.WebClient") + @ConditionalOnMissingBean(RestTemplate.class) public RestTemplate restTemplate() { return new RestTemplateBuilder().build(); } diff --git a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml index 968fe26379..c3d0ebeaf5 100644 --- a/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml +++ b/spring-cloud-kubernetes-examples/kubernetes-leader-election-example/leader-role.yml @@ -10,6 +10,16 @@ rules: - "" resources: - pods + verbs: + - watch + - get +- apiGroups: + - "" + resources: - configmaps verbs: - - '*' + - watch + - get + - update + # resourceNames: + # - diff --git a/spring-cloud-kubernetes-fabric8-config/pom.xml b/spring-cloud-kubernetes-fabric8-config/pom.xml index da17958fe0..e9907e8dc9 100644 --- a/spring-cloud-kubernetes-fabric8-config/pom.xml +++ b/spring-cloud-kubernetes-fabric8-config/pom.xml @@ -55,10 +55,6 @@ spring-cloud-starter - - org.springframework.cloud - spring-cloud-starter-bootstrap - org.springframework.cloud spring-cloud-commons diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java index 6cb0a698f0..a0c72da351 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8BootstrapConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.cloud.kubernetes.commons.config.KubernetesBootstrapConfiguration; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.cloud.kubernetes.fabric8.Fabric8AutoConfiguration; +import org.springframework.cloud.util.ConditionalOnBootstrapEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -49,6 +50,7 @@ @ConditionalOnClass({ ConfigMap.class, Secret.class }) @AutoConfigureAfter(KubernetesBootstrapConfiguration.class) @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) +@ConditionalOnBootstrapEnabled public class Fabric8BootstrapConfiguration { @Bean diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigDataLocationResolver.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigDataLocationResolver.java new file mode 100644 index 0000000000..0861ddd41f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigDataLocationResolver.java @@ -0,0 +1,141 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; + +import org.springframework.boot.BootstrapRegistry; +import org.springframework.boot.BootstrapRegistry.InstanceSupplier; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.Profiles; +import org.springframework.boot.logging.DeferredLogFactory; +import org.springframework.cloud.kubernetes.commons.KubernetesClientProperties; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableSecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.KubernetesConfigDataLocationResolver; +import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.fabric8.Fabric8AutoConfiguration; +import org.springframework.core.env.Environment; + +/** + * @author Ryan Baxter + */ +public class Fabric8ConfigDataLocationResolver extends KubernetesConfigDataLocationResolver { + + public Fabric8ConfigDataLocationResolver(DeferredLogFactory factory) { + super(factory); + } + + @Override + protected void registerBeans(ConfigDataLocationResolverContext resolverContext, ConfigDataLocation location, + Profiles profiles, KubernetesConfigDataLocationResolver.PropertyHolder propertyHolder, + KubernetesNamespaceProvider namespaceProvider) { + KubernetesClientProperties properties = propertyHolder.kubernetesClientProperties(); + ConfigMapConfigProperties configMapProperties = propertyHolder.configMapConfigProperties(); + SecretsConfigProperties secretsProperties = propertyHolder.secretsProperties(); + + ConfigurableBootstrapContext bootstrapContext = resolverContext.getBootstrapContext(); + Config config = config(properties); + bootstrapContext.registerIfAbsent(Config.class, InstanceSupplier.of(config)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("fabric8Config", event.getBootstrapContext().get(Config.class))); + + KubernetesClient kubernetesClient = kubernetesClient(config); + bootstrapContext.registerIfAbsent(KubernetesClient.class, + BootstrapRegistry.InstanceSupplier.of(kubernetesClient)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configKubernetesClient", event.getBootstrapContext().get(KubernetesClient.class))); + + if (isRetryEnabled(configMapProperties, secretsProperties)) { + registerRetryBeans(configMapProperties, secretsProperties, bootstrapContext, kubernetesClient, + namespaceProvider); + } + else { + if (configMapProperties.isEnabled()) { + Fabric8ConfigMapPropertySourceLocator configMapPropertySourceLocator = new Fabric8ConfigMapPropertySourceLocator( + kubernetesClient, configMapProperties, namespaceProvider); + bootstrapContext.registerIfAbsent(ConfigMapPropertySourceLocator.class, + InstanceSupplier.of(configMapPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configDataConfigMapPropertySourceLocator", + event.getBootstrapContext().get(ConfigMapPropertySourceLocator.class))); + } + if (secretsProperties.isEnabled()) { + Fabric8SecretsPropertySourceLocator secretsPropertySourceLocator = new Fabric8SecretsPropertySourceLocator( + kubernetesClient, secretsProperties, namespaceProvider); + bootstrapContext.registerIfAbsent(SecretsPropertySourceLocator.class, + InstanceSupplier.of(secretsPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory() + .registerSingleton("configDataSecretsPropertySourceLocator", + event.getBootstrapContext().get(SecretsPropertySourceLocator.class))); + } + } + } + + private void registerRetryBeans(ConfigMapConfigProperties configMapProperties, + SecretsConfigProperties secretsProperties, ConfigurableBootstrapContext bootstrapContext, + KubernetesClient kubernetesClient, KubernetesNamespaceProvider namespaceProvider) { + if (configMapProperties.isEnabled()) { + ConfigMapPropertySourceLocator configMapPropertySourceLocator = new Fabric8ConfigMapPropertySourceLocator( + kubernetesClient, configMapProperties, namespaceProvider); + if (isRetryEnabledForConfigMap(configMapProperties)) { + configMapPropertySourceLocator = new ConfigDataRetryableConfigMapPropertySourceLocator( + configMapPropertySourceLocator, configMapProperties); + } + + bootstrapContext.registerIfAbsent(ConfigMapPropertySourceLocator.class, + InstanceSupplier.of(configMapPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory().registerSingleton( + "configDataConfigMapPropertySourceLocator", + event.getBootstrapContext().get(ConfigMapPropertySourceLocator.class))); + } + + if (secretsProperties.isEnabled()) { + SecretsPropertySourceLocator secretsPropertySourceLocator = new Fabric8SecretsPropertySourceLocator( + kubernetesClient, secretsProperties, namespaceProvider); + if (isRetryEnabledForSecrets(secretsProperties)) { + secretsPropertySourceLocator = new ConfigDataRetryableSecretsPropertySourceLocator( + secretsPropertySourceLocator, secretsProperties); + } + bootstrapContext.registerIfAbsent(SecretsPropertySourceLocator.class, + InstanceSupplier.of(secretsPropertySourceLocator)); + bootstrapContext.addCloseListener(event -> event.getApplicationContext().getBeanFactory().registerSingleton( + "configDataSecretsPropertySourceLocator", + event.getBootstrapContext().get(SecretsPropertySourceLocator.class))); + } + } + + protected KubernetesNamespaceProvider kubernetesNamespaceProvider(Environment environment) { + return new KubernetesNamespaceProvider(environment); + } + + private Config config(KubernetesClientProperties properties) { + return new Fabric8AutoConfiguration().kubernetesClientConfig(properties); + } + + private KubernetesClient kubernetesClient(Config config) { + return new Fabric8AutoConfiguration().kubernetesClient(config); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java index 54ed82f856..77083d9d4f 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySource.java @@ -19,9 +19,9 @@ import java.util.EnumMap; import java.util.Optional; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSourceType; import org.springframework.cloud.kubernetes.commons.config.SourceData; +import org.springframework.cloud.kubernetes.commons.config.SourceDataEntriesProcessor; import org.springframework.core.env.MapPropertySource; /** @@ -32,16 +32,14 @@ * @author Michael Moudatsos * @author Isik Erhan */ -public final class Fabric8ConfigMapPropertySource extends ConfigMapPropertySource { +public final class Fabric8ConfigMapPropertySource extends SourceDataEntriesProcessor { private static final EnumMap STRATEGIES = new EnumMap<>( NormalizedSourceType.class); - // there is a single strategy here at the moment (unlike secrets), - // but this can change. - // to be on par with secrets implementation, I am keeping it the same static { STRATEGIES.put(NormalizedSourceType.NAMED_CONFIG_MAP, namedConfigMap()); + STRATEGIES.put(NormalizedSourceType.LABELED_CONFIG_MAP, labeledConfigMap()); } Fabric8ConfigMapPropertySource(Fabric8ConfigContext context) { @@ -54,11 +52,12 @@ private static SourceData getSourceData(Fabric8ConfigContext context) { .orElseThrow(() -> new IllegalArgumentException("no strategy found for : " + type)); } - // we need to pass various functions because the code we are interested in - // is protected in ConfigMapPropertySource, and must stay that way. private static Fabric8ContextToSourceData namedConfigMap() { - return NamedConfigMapContextToSourceDataProvider.of(ConfigMapPropertySource::processAllEntries, - ConfigMapPropertySource::getSourceName, ConfigMapPropertySource::withPrefix).get(); + return new NamedConfigMapContextToSourceDataProvider().get(); + } + + private static Fabric8ContextToSourceData labeledConfigMap() { + return new LabeledConfigMapContextToSourceDataProvider().get(); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java index ac702c917c..4bc66c5fe3 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtils.java @@ -16,10 +16,10 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Secret; @@ -28,8 +28,11 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; -import org.springframework.util.CollectionUtils; +import org.springframework.cloud.kubernetes.commons.config.StrippedSourceContainer; +import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; /** @@ -91,33 +94,110 @@ static String getApplicationNamespace(KubernetesClient client, String namespace, } - /* - * namespace that reaches this point is absolutely present, otherwise this would have - * resulted in a NamespaceResolutionFailedException + /** + *
+	 *     1. read all secrets in the provided namespace
+	 *     2. from the above, filter the ones that we care about (filter by labels)
+	 *     3. with secret names from (2), find out if there are any profile based secrets (if profiles is not empty)
+	 *     4. concat (2) and (3) and these are the secrets we are interested in
+	 *     5. see if any of the secrets from (4) has a single yaml/properties file
+	 *     6. gather all the names of the secrets (from 4) + data they hold
+	 * 
*/ - static Map getConfigMapData(KubernetesClient client, String namespace, String name) { - ConfigMap configMap = client.configMaps().inNamespace(namespace).withName(name).get(); + static MultipleSourcesContainer secretsDataByLabels(KubernetesClient client, String namespace, + Map labels, Environment environment, Set profiles) { + List secrets = secretsSearch(client, namespace); + if (ConfigUtils.noSources(secrets, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedSecrets(secrets); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, true); - if (configMap == null) { - LOG.warn("config-map with name : '" + name + "' not present in namespace : '" + namespace + "'"); - return Collections.emptyMap(); + } + + /** + *
+	 *     1. read all config maps in the provided namespace
+	 *     2. from the above, filter the ones that we care about (filter by labels)
+	 *     3. with config maps names from (2), find out if there are any profile based ones (if profiles is not empty)
+	 *     4. concat (2) and (3) and these are the config maps we are interested in
+	 *     5. see if any from (4) has a single yaml/properties file
+	 *     6. gather all the names of the config maps (from 4) + data they hold
+	 * 
+ */ + static MultipleSourcesContainer configMapsDataByLabels(KubernetesClient client, String namespace, + Map labels, Environment environment, Set profiles) { + + List configMaps = configMapsSearch(client, namespace); + if (ConfigUtils.noSources(configMaps, namespace)) { + return MultipleSourcesContainer.empty(); } - return configMap.getData(); + List strippedSources = strippedConfigMaps(configMaps); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, false); } /** - * return decoded data from a secret within a namespace. + *
+	 *     1. read all secrets in the provided namespace
+	 *     2. from the above, filter the ones that we care about (by name)
+	 *     3. see if any of the secrets has a single yaml/properties file
+	 *     4. gather all the names of the secrets + decoded data they hold
+	 * 
*/ - static Map dataFromSecret(Secret secret, String namespace) { - LOG.debug("reading secret with name : " + secret.getMetadata().getName() + " in namespace : " + namespace); - return secretData(secret.getData()); + static MultipleSourcesContainer secretsDataByName(KubernetesClient client, String namespace, + Set sourceNames, Environment environment) { + List secrets = secretsSearch(client, namespace); + if (ConfigUtils.noSources(secrets, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedSecrets(secrets); + return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, true); + + } + + /** + *
+	 *     1. read all config maps in the provided namespace
+	 *     2. from the above, filter the ones that we care about (by name)
+	 *     3. see if any of the config maps has a single yaml/properties file
+	 *     4. gather all the names of the config maps + data they hold
+	 * 
+ */ + static MultipleSourcesContainer configMapsDataByName(KubernetesClient client, String namespace, + Set sourceNames, Environment environment) { + List configMaps = configMapsSearch(client, namespace); + if (ConfigUtils.noSources(configMaps, namespace)) { + return MultipleSourcesContainer.empty(); + } + + List strippedSources = strippedConfigMaps(configMaps); + return ConfigUtils.processNamedData(strippedSources, environment, sourceNames, namespace, false); + + } + + // ******** non-exposed methods ******* + + private static List secretsSearch(KubernetesClient client, String namespace) { + LOG.debug("Loading all secrets in namespace '" + namespace + "'"); + return client.secrets().inNamespace(namespace).list().getItems(); + } + + private static List configMapsSearch(KubernetesClient client, String namespace) { + LOG.debug("Loading all config maps in namespace '" + namespace + "'"); + return client.configMaps().inNamespace(namespace).list().getItems(); + } + + private static List strippedSecrets(List secrets) { + return secrets.stream().map(secret -> new StrippedSourceContainer(secret.getMetadata().getLabels(), + secret.getMetadata().getName(), secret.getData())).collect(Collectors.toList()); } - private static Map secretData(Map data) { - Map result = new HashMap<>(CollectionUtils.newHashMap(data.size())); - data.forEach((key, value) -> result.put(key, new String(Base64.getDecoder().decode(value)).trim())); - return result; + private static List strippedConfigMaps(List configMaps) { + return configMaps.stream().map(configMap -> new StrippedSourceContainer(configMap.getMetadata().getLabels(), + configMap.getMetadata().getName(), configMap.getData())).collect(Collectors.toList()); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8RetryBootstrapConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8RetryBootstrapConfiguration.java index 159d9c266a..def50b684f 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8RetryBootstrapConfiguration.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8RetryBootstrapConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.cloud.kubernetes.commons.config.KubernetesBootstrapConfiguration; import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; import org.springframework.cloud.kubernetes.fabric8.Fabric8AutoConfiguration; +import org.springframework.cloud.util.ConditionalOnBootstrapEnabled; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -48,6 +49,7 @@ @AutoConfigureAfter(KubernetesBootstrapConfiguration.class) @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) @ConditionalOnKubernetesConfigOrSecretsRetryEnabled +@ConditionalOnBootstrapEnabled public class Fabric8RetryBootstrapConfiguration { @Bean diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySource.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySource.java index 47343c1f34..bd1472d10f 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySource.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySource.java @@ -51,11 +51,11 @@ private static SourceData getSourceData(Fabric8ConfigContext context) { } private static Fabric8ContextToSourceData namedSecret() { - return NamedSecretContextToSourceDataProvider.of(SecretsPropertySource::getSourceName).get(); + return new NamedSecretContextToSourceDataProvider().get(); } private static Fabric8ContextToSourceData labeledSecret() { - return LabeledSecretContextToSourceDataProvider.of(SecretsPropertySource::getSourceName).get(); + return new LabeledSecretContextToSourceDataProvider().get(); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProvider.java new file mode 100644 index 0000000000..8058f041dd --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProvider.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; + +/** + * Provides an implementation of {@link Fabric8ContextToSourceData} for a labeled config + * map. + * + * @author wind57 + */ +final class LabeledConfigMapContextToSourceDataProvider implements Supplier { + + LabeledConfigMapContextToSourceDataProvider() { + } + + /* + * Computes a ContextSourceData (think content) for configmap(s) based on some labels. + * There could be many sources that are read based on incoming labels, for which we + * will be computing a single Map in the end. + * + * If there is no config maps found for the provided labels, we will return an "empty" + * SourceData. Its name is going to be the concatenated labels mapped to an empty Map. + * + * If we find config maps(s) for the provided labels, its name is going to be the + * concatenated names mapped to the data they hold as a Map. + */ + @Override + public Fabric8ContextToSourceData get() { + + return context -> { + + LabeledConfigMapNormalizedSource source = (LabeledConfigMapNormalizedSource) context.normalizedSource(); + + return new LabeledSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Map labels, Set profiles) { + return Fabric8ConfigUtils.configMapsDataByLabels(context.client(), context.namespace(), labels, + context.environment(), profiles); + } + + }.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); + }; + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java index f8dfd480cd..efad27a7ba 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProvider.java @@ -16,25 +16,13 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.function.BiFunction; +import java.util.Set; import java.util.function.Supplier; -import java.util.stream.Collectors; - -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.Secret; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; - -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; -import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; -import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigUtils.dataFromSecret; +import org.springframework.cloud.kubernetes.commons.config.LabeledSourceData; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; /** * Provides an implementation of {@link Fabric8ContextToSourceData} for a labeled secret. @@ -43,16 +31,7 @@ */ final class LabeledSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - - private final BiFunction sourceNameMapper; - - private LabeledSecretContextToSourceDataProvider(BiFunction sourceNameFunction) { - this.sourceNameMapper = Objects.requireNonNull(sourceNameFunction); - } - - static LabeledSecretContextToSourceDataProvider of(BiFunction sourceNameFunction) { - return new LabeledSecretContextToSourceDataProvider(sourceNameFunction); + LabeledSecretContextToSourceDataProvider() { } /* @@ -71,37 +50,19 @@ public Fabric8ContextToSourceData get() { return context -> { - LabeledSecretNormalizedSource source = ((LabeledSecretNormalizedSource) context.normalizedSource()); - Map labels = source.labels(); + LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource(); - Map result = new HashMap<>(); - String namespace = context.namespace(); - String sourceName = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, labels.keySet()); - - try { - - LOG.info("Loading Secret(s) with labels '" + labels + "' in namespace '" + namespace + "'"); - List secrets = context.client().secrets().inNamespace(namespace).withLabels(labels).list() - .getItems(); - - if (!secrets.isEmpty()) { - secrets.forEach(secret -> result.putAll(dataFromSecret(secret, namespace))); - sourceName = secrets.stream().map(Secret::getMetadata).map(ObjectMeta::getName) - .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); + return new LabeledSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Map labels, Set profiles) { + return Fabric8ConfigUtils.secretsDataByLabels(context.client(), context.namespace(), labels, + context.environment(), profiles); } - else { - LOG.info("No Secret(s) with labels '" + labels + "' in namespace '" + namespace + "' found."); - } - - } - catch (Exception e) { - String message = "Unable to read Secret with labels [" + labels + "] in namespace '" + namespace + "'"; - onException(source.failFast(), message, e); - } - String propertySourceName = sourceNameMapper.apply(sourceName, namespace); - return new SourceData(propertySourceName, result); + }.compute(source.labels(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; + } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java index 5ee59a23ae..a1c6e4e22f 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProvider.java @@ -16,28 +16,12 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.Supplier; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPrefixContext; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; - -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.getApplicationName; -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; -import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; -import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigUtils.getConfigMapData; +import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; /** * Provides an implementation of {@link Fabric8ContextToSourceData} for a named config @@ -47,28 +31,7 @@ */ final class NamedConfigMapContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedConfigMapContextToSourceDataProvider.class); - - private final BiFunction, Environment, Map> entriesProcessor; - - private final BiFunction sourceNameMapper; - - private final Function withPrefix; - - private NamedConfigMapContextToSourceDataProvider( - BiFunction, Environment, Map> entriesProcessor, - BiFunction sourceNameMapper, - Function withPrefix) { - this.entriesProcessor = Objects.requireNonNull(entriesProcessor); - this.sourceNameMapper = Objects.requireNonNull(sourceNameMapper); - this.withPrefix = Objects.requireNonNull(withPrefix); - } - - static NamedConfigMapContextToSourceDataProvider of( - BiFunction, Environment, Map> entriesProcessor, - BiFunction sourceNameMapper, - Function withPrefix) { - return new NamedConfigMapContextToSourceDataProvider(entriesProcessor, sourceNameMapper, withPrefix); + NamedConfigMapContextToSourceDataProvider() { } /* @@ -84,52 +47,17 @@ public Fabric8ContextToSourceData get() { return context -> { NamedConfigMapNormalizedSource source = (NamedConfigMapNormalizedSource) context.normalizedSource(); - String namespace = context.namespace(); - String initialConfigMapName = appName(context.environment(), source).get(); - String currentConfigMapName = initialConfigMapName; - Set propertySourceNames = new LinkedHashSet<>(); - propertySourceNames.add(initialConfigMapName); - Map result = new HashMap<>(); - - LOG.info("Loading ConfigMap with name '" + initialConfigMapName + "' in namespace '" + namespace + "'"); - try { - Map data = getConfigMapData(context.client(), namespace, currentConfigMapName); - result.putAll(entriesProcessor.apply(data, context.environment())); - - if (context.environment() != null && source.profileSpecificSources()) { - for (String activeProfile : context.environment().getActiveProfiles()) { - currentConfigMapName = initialConfigMapName + "-" + activeProfile; - Map dataWithProfile = getConfigMapData(context.client(), namespace, - currentConfigMapName); - if (!dataWithProfile.isEmpty()) { - propertySourceNames.add(currentConfigMapName); - result.putAll(entriesProcessor.apply(dataWithProfile, context.environment())); - } - } - } - - if (!"".equals(source.prefix())) { - ConfigMapPrefixContext prefixContext = new ConfigMapPrefixContext(result, source.prefix(), - namespace, propertySourceNames); - return withPrefix.apply(prefixContext); + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return Fabric8ConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames, + context.environment()); } - - } - catch (Exception e) { - String message = "Unable to read ConfigMap with name '" + currentConfigMapName + "' in namespace '" - + namespace + "'"; - onException(source.failFast(), message, e); - } - - String names = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames); - return new SourceData(sourceNameMapper.apply(names, namespace), result); + }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; } - private Supplier appName(Environment environment, NormalizedSource normalizedSource) { - return () -> getApplicationName(environment, normalizedSource.name().orElse(null), normalizedSource.target()); - } - } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java index 06149f47a6..2d3d793c4b 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java @@ -16,21 +16,12 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiFunction; +import java.util.Set; import java.util.function.Supplier; -import io.fabric8.kubernetes.api.model.Secret; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SourceData; - -import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; -import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigUtils.dataFromSecret; +import org.springframework.cloud.kubernetes.commons.config.NamedSourceData; /** * Provides an implementation of {@link Fabric8ContextToSourceData} for a named secret. @@ -39,16 +30,7 @@ */ final class NamedSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - - private final BiFunction sourceNameMapper; - - private NamedSecretContextToSourceDataProvider(BiFunction sourceNameFunction) { - this.sourceNameMapper = Objects.requireNonNull(sourceNameFunction); - } - - static NamedSecretContextToSourceDataProvider of(BiFunction sourceNameFunction) { - return new NamedSecretContextToSourceDataProvider(sourceNameFunction); + NamedSecretContextToSourceDataProvider() { } @Override @@ -57,33 +39,14 @@ public Fabric8ContextToSourceData get() { NamedSecretNormalizedSource source = (NamedSecretNormalizedSource) context.normalizedSource(); - Map result = new HashMap<>(); - // error should never be thrown here, since we always expect a name - // explicit or implicit - String secretName = source.name().orElseThrow(); - String namespace = context.namespace(); - - try { - - LOG.info("Loading Secret with name '" + secretName + "' in namespace '" + namespace + "'"); - Secret secret = context.client().secrets().inNamespace(namespace).withName(secretName).get(); - // the API is documented that it might return null - if (secret == null) { - LOG.warn("secret with name : " + secretName + " in namespace : " + namespace + " not found"); - } - else { - result = dataFromSecret(secret, namespace); + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return Fabric8ConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames, + context.environment()); } - - } - catch (Exception e) { - String message = "Unable to read Secret with name '" + secretName + "' in namespace '" + namespace - + "'"; - onException(source.failFast(), message, e); - } - - String sourceName = sourceNameMapper.apply(secretName, namespace); - return new SourceData(sourceName, result); + }.compute(source.name().orElseThrow(), source.prefix(), source.target(), source.profileSpecificSources(), + source.failFast(), context.namespace(), context.environment().getActiveProfiles()); }; } diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfiguration.java deleted file mode 100644 index 0b9335659b..0000000000 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfiguration.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.fabric8.config.reload; - -import java.util.concurrent.ThreadLocalRandom; - -import io.fabric8.kubernetes.client.KubernetesClient; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration; -import org.springframework.cloud.commons.util.TaskSchedulerWrapper; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.cloud.context.restart.RestartEndpoint; -import org.springframework.cloud.kubernetes.commons.config.ConditionalOnKubernetesAndConfigEnabled; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.PollingSecretsChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.condition.EventReloadDetectionMode; -import org.springframework.cloud.kubernetes.commons.config.reload.condition.PollingReloadDetectionMode; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.util.Assert; - -/** - * Definition of beans needed for the automatic reload of configuration. - * - * @author Nicolla Ferraro - * @author Kris Iyer - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnKubernetesAndConfigEnabled -@ConditionalOnClass(EndpointAutoConfiguration.class) -@AutoConfigureAfter({ InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class, - RefreshAutoConfiguration.class }) -@EnableConfigurationProperties(ConfigReloadProperties.class) -public class ConfigReloadAutoConfiguration { - - /** - * Configuration reload must be enabled explicitly. - */ - @ConditionalOnProperty("spring.cloud.kubernetes.reload.enabled") - @ConditionalOnClass({ RestartEndpoint.class, ContextRefresher.class }) - protected static class ConfigReloadAutoConfigurationBeans { - - @Autowired - private AbstractEnvironment environment; - - @Autowired - private KubernetesClient kubernetesClient; - - /** - * Polling configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8ConfigMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator, - TaskSchedulerWrapper taskSchedulerWrapper) { - - return new PollingConfigMapChangeDetector(this.environment, properties, strategy, - Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, - taskSchedulerWrapper.getTaskScheduler()); - } - - /** - * Polling secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8SecretsPropertySourceLocator secrets property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator, - TaskSchedulerWrapper taskScheduler) { - - return new PollingSecretsChangeDetector(this.environment, properties, strategy, - Fabric8SecretsPropertySource.class, fabric8SecretsPropertySourceLocator, - taskScheduler.getTaskScheduler()); - } - - /** - * Event Based configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8ConfigMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configMap change events and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) { - - return new EventBasedConfigMapChangeDetector(this.environment, properties, this.kubernetesClient, strategy, - fabric8ConfigMapPropertySourceLocator); - } - - /** - * Event Based secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8SecretsPropertySourceLocator secrets property source locator - * @return a bean that listen to secrets change events and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator) { - - return new EventBasedSecretsChangeDetector(this.environment, properties, this.kubernetesClient, strategy, - fabric8SecretsPropertySourceLocator); - } - - /** - * @param properties config reload properties - * @param ctx application context - * @param restarter restart endpoint - * @param refresher context refresher - * @return provides the action to execute when the configuration changes. - */ - @Bean - @ConditionalOnMissingBean - public ConfigurationUpdateStrategy configurationUpdateStrategy(ConfigReloadProperties properties, - ConfigurableApplicationContext ctx, @Autowired(required = false) RestartEndpoint restarter, - ContextRefresher refresher) { - switch (properties.getStrategy()) { - case RESTART_CONTEXT: - Assert.notNull(restarter, "Restart endpoint is not enabled"); - return new ConfigurationUpdateStrategy(properties.getStrategy().name(), () -> { - wait(properties); - restarter.restart(); - }); - case REFRESH: - return new ConfigurationUpdateStrategy(properties.getStrategy().name(), refresher::refresh); - case SHUTDOWN: - return new ConfigurationUpdateStrategy(properties.getStrategy().name(), () -> { - wait(properties); - ctx.close(); - }); - } - throw new IllegalStateException("Unsupported configuration update strategy: " + properties.getStrategy()); - } - - private static void wait(ConfigReloadProperties properties) { - final long waitMillis = ThreadLocalRandom.current().nextLong(properties.getMaxWaitForRestart().toMillis()); - try { - Thread.sleep(waitMillis); - } - catch (InterruptedException ignored) { - } - } - - } - -} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadDefaultAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadDefaultAutoConfiguration.java deleted file mode 100644 index cd3246b10b..0000000000 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadDefaultAutoConfiguration.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.fabric8.config.reload; - -import java.util.concurrent.ThreadLocalRandom; - -import io.fabric8.kubernetes.client.KubernetesClient; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.cloud.CloudPlatform; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.commons.util.TaskSchedulerWrapper; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.PollingSecretsChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.condition.EventReloadDetectionMode; -import org.springframework.cloud.kubernetes.commons.config.reload.condition.PollingReloadDetectionMode; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.AbstractEnvironment; - -/** - * @author Ryan Baxter - * @author Kris Iyer - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES) -@ConditionalOnMissingBean(ConfigReloadAutoConfiguration.class) -@EnableConfigurationProperties(ConfigReloadProperties.class) -public class ConfigReloadDefaultAutoConfiguration { - - /** - * Configuration reload must be enabled explicitly. - */ - @ConditionalOnProperty("spring.cloud.kubernetes.reload.enabled") - protected static class ConfigReloadAutoConfigurationBeans { - - @Autowired - private AbstractEnvironment environment; - - @Autowired - private KubernetesClient kubernetesClient; - - @Autowired - private Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator; - - @Autowired - private Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator; - - private static void wait(ConfigReloadProperties properties) { - final long waitMillis = ThreadLocalRandom.current().nextLong(properties.getMaxWaitForRestart().toMillis()); - try { - Thread.sleep(waitMillis); - } - catch (InterruptedException ignored) { - } - } - - /** - * Polling configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8ConfigMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator, - TaskSchedulerWrapper taskSchedulerWrapper) { - - return new PollingConfigMapChangeDetector(this.environment, properties, strategy, - Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, - taskSchedulerWrapper.getTaskScheduler()); - } - - /** - * Polling secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8SecretsPropertySourceLocator secrets property source locator - * @return a bean that listen to configuration changes and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) - @Conditional(PollingReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangePollingWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator, - TaskSchedulerWrapper taskScheduler) { - - return new PollingSecretsChangeDetector(this.environment, properties, strategy, - Fabric8SecretsPropertySource.class, fabric8SecretsPropertySourceLocator, - taskScheduler.getTaskScheduler()); - } - - /** - * Event Based configMap ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8ConfigMapPropertySourceLocator configMap property source locator - * @return a bean that listen to configMap change events and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector configMapPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) { - - return new EventBasedConfigMapChangeDetector(this.environment, properties, this.kubernetesClient, strategy, - fabric8ConfigMapPropertySourceLocator); - } - - /** - * Event Based secrets ConfigurationChangeDetector. - * @param properties config reload properties - * @param strategy configuration update strategy - * @param fabric8SecretsPropertySourceLocator secrets property source locator - * @return a bean that listen to secrets change events and fire a reload. - */ - @Bean - @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) - @Conditional(EventReloadDetectionMode.class) - public ConfigurationChangeDetector secretsPropertyChangeEventWatcher(ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy, - Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator) { - - return new EventBasedSecretsChangeDetector(this.environment, properties, this.kubernetesClient, strategy, - fabric8SecretsPropertySourceLocator); - } - - /** - * @param properties config reload properties - * @param ctx application context - * @return provides the action to execute when the configuration changes. - */ - @Bean - @ConditionalOnMissingBean - public ConfigurationUpdateStrategy configurationUpdateStrategy(ConfigReloadProperties properties, - ConfigurableApplicationContext ctx) { - switch (properties.getStrategy()) { - case SHUTDOWN: - return new ConfigurationUpdateStrategy(properties.getStrategy().name(), () -> { - wait(properties); - ctx.close(); - }); - } - throw new IllegalStateException("Unsupported configuration update strategy: " + properties.getStrategy()); - } - - } - -} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedConfigMapChangeDetector.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedConfigMapChangeDetector.java deleted file mode 100644 index 56e3f04be1..0000000000 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedConfigMapChangeDetector.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.fabric8.config.reload; - -import java.net.HttpURLConnection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.ConfigMap; -import io.fabric8.kubernetes.api.model.Status; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.client.Watch; -import io.fabric8.kubernetes.client.Watcher; -import io.fabric8.kubernetes.client.WatcherException; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; - -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; -import org.springframework.core.env.AbstractEnvironment; - -/** - * An Event Based change detector that subscribes to changes in configMaps and fire a - * reload when something changes. - * - * @author Nicola Ferraro - * @author Haytham Mohamed - * @author Kris Iyer - */ -public class EventBasedConfigMapChangeDetector extends ConfigurationChangeDetector { - - private final Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator; - - private final Map watches; - - private KubernetesClient kubernetesClient; - - public EventBasedConfigMapChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, - KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, - Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) { - super(environment, properties, strategy); - this.kubernetesClient = kubernetesClient; - this.fabric8ConfigMapPropertySourceLocator = fabric8ConfigMapPropertySourceLocator; - this.watches = new HashMap<>(); - } - - @PreDestroy - public void shutdown() { - // Ensure the kubernetes client is cleaned up from spare threads when shutting - // down - this.kubernetesClient.close(); - } - - @PostConstruct - public void watch() { - boolean activated = false; - - if (this.properties.isMonitoringConfigMaps()) { - try { - String name = "config-maps-watch-event"; - this.watches.put(name, this.kubernetesClient.configMaps().watch(new Watcher() { - @Override - public void eventReceived(Watcher.Action action, ConfigMap configMap) { - if (log.isDebugEnabled()) { - log.debug(name + " received event for ConfigMap " + configMap.getMetadata().getName()); - } - onEvent(configMap); - } - - @Override - public void onClose(WatcherException exception) { - log.warn("ConfigMaps watch closed", exception); - Optional.ofNullable(exception).map(e -> { - log.debug("Exception received during watch", e); - return exception.asClientException(); - }).map(KubernetesClientException::getStatus).map(Status::getCode) - .filter(c -> c.equals(HttpURLConnection.HTTP_GONE)).ifPresent(c -> watch()); - } - })); - activated = true; - this.log.info("Added new Kubernetes watch: " + name); - } - catch (Exception e) { - this.log.error( - "Error while establishing a connection to watch config maps: configuration may remain stale", - e); - } - } - - if (activated) { - this.log.info("Kubernetes event-based configMap change detector activated"); - } - } - - @PreDestroy - public void unwatch() { - if (this.watches != null) { - for (Map.Entry entry : this.watches.entrySet()) { - try { - this.log.debug("Closing the watch " + entry.getKey()); - entry.getValue().close(); - - } - catch (Exception e) { - this.log.error("Error while closing the watch connection", e); - } - } - } - } - - protected void onEvent(ConfigMap configMap) { - this.log.debug(String.format("onEvent configMap: %s", configMap.toString())); - boolean changed = changed( - locateMapPropertySources(this.fabric8ConfigMapPropertySourceLocator, this.environment), - findPropertySources(Fabric8ConfigMapPropertySource.class)); - if (changed) { - this.log.info("Detected change in config maps"); - reloadProperties(); - } - } - -} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedSecretsChangeDetector.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedSecretsChangeDetector.java deleted file mode 100644 index 7149cf81ec..0000000000 --- a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/EventBasedSecretsChangeDetector.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.fabric8.config.reload; - -import java.net.HttpURLConnection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import io.fabric8.kubernetes.api.model.Secret; -import io.fabric8.kubernetes.api.model.Status; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.client.Watch; -import io.fabric8.kubernetes.client.Watcher; -import io.fabric8.kubernetes.client.WatcherException; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; - -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; -import org.springframework.core.env.AbstractEnvironment; - -/** - * An event based change detector that subscribes to changes in secrets and fire a reload - * when something changes. - * - * @author Nicola Ferraro - * @author Haytham Mohamed - * @author Kris Iyer - */ -public class EventBasedSecretsChangeDetector extends ConfigurationChangeDetector { - - private Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator; - - private Map watches; - - private KubernetesClient kubernetesClient; - - public EventBasedSecretsChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, - KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, - Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator) { - super(environment, properties, strategy); - this.kubernetesClient = kubernetesClient; - this.fabric8SecretsPropertySourceLocator = fabric8SecretsPropertySourceLocator; - this.watches = new HashMap<>(); - } - - @PreDestroy - public void shutdown() { - // Ensure the kubernetes client is cleaned up from spare threads when shutting - // down - this.kubernetesClient.close(); - } - - @PostConstruct - public void watch() { - boolean activated = false; - - if (this.properties.isMonitoringSecrets()) { - try { - activated = false; - String name = "secrets-watch-event"; - this.watches.put(name, this.kubernetesClient.secrets().watch(new Watcher() { - @Override - public void eventReceived(Action action, Secret secret) { - if (log.isDebugEnabled()) { - log.debug(name + " received event for Secret " + secret.getMetadata().getName()); - } - onEvent(secret); - } - - @Override - public void onClose(WatcherException exception) { - log.warn("Secrects watch closed", exception); - Optional.ofNullable(exception).map(e -> { - log.debug("Exception received during watch", e); - return exception.asClientException(); - }).map(KubernetesClientException::getStatus).map(Status::getCode) - .filter(c -> c.equals(HttpURLConnection.HTTP_GONE)).ifPresent(c -> watch()); - } - })); - activated = true; - this.log.info("Added new Kubernetes watch: " + name); - } - catch (Exception e) { - this.log.error("Error while establishing a connection to watch secrets: configuration may remain stale", - e); - } - } - - if (activated) { - this.log.info("Kubernetes event-based secrets change detector activated"); - } - } - - @PreDestroy - public void unwatch() { - if (this.watches != null) { - for (Map.Entry entry : this.watches.entrySet()) { - try { - this.log.debug("Closing the watch " + entry.getKey()); - entry.getValue().close(); - - } - catch (Exception e) { - this.log.error("Error while closing the watch connection", e); - } - } - } - } - - protected void onEvent(Secret secret) { - this.log.debug(String.format("onEvent configMap: %s", secret.toString())); - boolean changed = changed(locateMapPropertySources(this.fabric8SecretsPropertySourceLocator, this.environment), - findPropertySources(Fabric8SecretsPropertySource.class)); - if (changed) { - this.log.info("Detected change in secrets"); - reloadProperties(); - } - } - -} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8ConfigReloadAutoConfiguration.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8ConfigReloadAutoConfiguration.java new file mode 100644 index 0000000000..7297f22e8c --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8ConfigReloadAutoConfiguration.java @@ -0,0 +1,146 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.reload; + +import io.fabric8.kubernetes.client.KubernetesClient; + +import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration; +import org.springframework.cloud.commons.util.TaskSchedulerWrapper; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.cloud.context.restart.RestartEndpoint; +import org.springframework.cloud.kubernetes.commons.config.ConditionalOnKubernetesAndConfigEnabled; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadAutoConfiguration; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; +import org.springframework.cloud.kubernetes.commons.config.reload.PollingConfigMapChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.PollingSecretsChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.condition.ConditionalOnKubernetesReloadEnabled; +import org.springframework.cloud.kubernetes.commons.config.reload.condition.EventReloadDetectionMode; +import org.springframework.cloud.kubernetes.commons.config.reload.condition.PollingReloadDetectionMode; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.scheduling.TaskScheduler; + +/** + * Definition of beans needed for the automatic reload of configuration. + * + * @author Nicolla Ferraro + * @author Kris Iyer + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnKubernetesAndConfigEnabled +@ConditionalOnKubernetesReloadEnabled +@ConditionalOnClass({ EndpointAutoConfiguration.class, RestartEndpoint.class, ContextRefresher.class }) +@AutoConfigureAfter({ InfoEndpointAutoConfiguration.class, RefreshEndpointAutoConfiguration.class, + RefreshAutoConfiguration.class }) +@EnableConfigurationProperties(ConfigReloadProperties.class) +@Import(ConfigReloadAutoConfiguration.class) +public class Fabric8ConfigReloadAutoConfiguration { + + /** + * Polling configMap ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param fabric8ConfigMapPropertySourceLocator configMap property source locator + * @return a bean that listen to configuration changes and fire a reload. + */ + @Bean + @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) + @Conditional(PollingReloadDetectionMode.class) + public ConfigurationChangeDetector configMapPropertyChangePollingWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator, + TaskSchedulerWrapper taskSchedulerWrapper, AbstractEnvironment environment) { + + return new PollingConfigMapChangeDetector(environment, properties, strategy, + Fabric8ConfigMapPropertySource.class, fabric8ConfigMapPropertySourceLocator, + taskSchedulerWrapper.getTaskScheduler()); + } + + /** + * Polling secrets ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param fabric8SecretsPropertySourceLocator secrets property source locator + * @return a bean that listen to configuration changes and fire a reload. + */ + @Bean + @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) + @Conditional(PollingReloadDetectionMode.class) + public ConfigurationChangeDetector secretsPropertyChangePollingWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator, + TaskSchedulerWrapper taskScheduler, AbstractEnvironment environment) { + + return new PollingSecretsChangeDetector(environment, properties, strategy, Fabric8SecretsPropertySource.class, + fabric8SecretsPropertySourceLocator, taskScheduler.getTaskScheduler()); + } + + /** + * Event Based configMap ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param fabric8ConfigMapPropertySourceLocator configMap property source locator + * @return a bean that listen to configMap change events and fire a reload. + */ + @Bean + @ConditionalOnBean(Fabric8ConfigMapPropertySourceLocator.class) + @Conditional(EventReloadDetectionMode.class) + public ConfigurationChangeDetector configMapPropertyChangeEventWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator, + AbstractEnvironment environment, KubernetesClient kubernetesClient) { + + return new Fabric8EventBasedConfigMapChangeDetector(environment, properties, kubernetesClient, strategy, + fabric8ConfigMapPropertySourceLocator); + } + + /** + * Event Based secrets ConfigurationChangeDetector. + * @param properties config reload properties + * @param strategy configuration update strategy + * @param fabric8SecretsPropertySourceLocator secrets property source locator + * @return a bean that listen to secrets change events and fire a reload. + */ + @Bean + @ConditionalOnBean(Fabric8SecretsPropertySourceLocator.class) + @Conditional(EventReloadDetectionMode.class) + public ConfigurationChangeDetector secretsPropertyChangeEventWatcher(ConfigReloadProperties properties, + ConfigurationUpdateStrategy strategy, + Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator, AbstractEnvironment environment, + KubernetesClient kubernetesClient) { + + return new Fabric8EventBasedSecretsChangeDetector(environment, properties, kubernetesClient, strategy, + fabric8SecretsPropertySourceLocator); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedConfigMapChangeDetector.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedConfigMapChangeDetector.java new file mode 100644 index 0000000000..be4843fd7d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedConfigMapChangeDetector.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.reload; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.informers.ResourceEventHandler; +import io.fabric8.kubernetes.client.informers.SharedIndexInformer; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySource; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; +import org.springframework.core.env.AbstractEnvironment; + +/** + * An Event Based change detector that subscribes to changes in configMaps and fire a + * reload when something changes. + * + * @author Nicola Ferraro + * @author Haytham Mohamed + * @author Kris Iyer + */ +public class Fabric8EventBasedConfigMapChangeDetector extends ConfigurationChangeDetector { + + private final Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator; + + private final KubernetesClient kubernetesClient; + + private final boolean monitoringConfigMaps; + + private SharedIndexInformer informer; + + public Fabric8EventBasedConfigMapChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, + KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, + Fabric8ConfigMapPropertySourceLocator fabric8ConfigMapPropertySourceLocator) { + super(environment, properties, strategy); + this.kubernetesClient = kubernetesClient; + this.fabric8ConfigMapPropertySourceLocator = fabric8ConfigMapPropertySourceLocator; + monitoringConfigMaps = properties.isMonitoringConfigMaps(); + } + + @PostConstruct + private void inform() { + if (monitoringConfigMaps) { + log.info("Kubernetes event-based configMap change detector activated"); + informer = kubernetesClient.configMaps().inform(); + informer.addEventHandler(new ResourceEventHandler<>() { + @Override + public void onAdd(ConfigMap configMap) { + onEvent(configMap); + } + + @Override + public void onUpdate(ConfigMap oldConfigMap, ConfigMap newConfigMap) { + onEvent(newConfigMap); + } + + @Override + public void onDelete(ConfigMap configMap, boolean deletedFinalStateUnknown) { + onEvent(configMap); + } + + // leave as comment on purpose, may be this will be useful in the future + // @Override + // public void onNothing() { + // boolean isStoreEmpty = informer.getStore().list().isEmpty(); + // if(!isStoreEmpty) { + // // HTTP_GONE, thus re-inform + // inform(); + // } + // } + }); + } + } + + @PreDestroy + private void shutdown() { + if (informer != null) { + log.debug("closing configmap informer"); + informer.close(); + } + // Ensure the kubernetes client is cleaned up from spare threads when shutting + // down + kubernetesClient.close(); + } + + protected void onEvent(ConfigMap configMap) { + log.debug("onEvent configMap: " + configMap.toString()); + boolean changed = changed(locateMapPropertySources(fabric8ConfigMapPropertySourceLocator, environment), + findPropertySources(Fabric8ConfigMapPropertySource.class)); + if (changed) { + log.info("Detected change in config maps"); + reloadProperties(); + } + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedSecretsChangeDetector.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedSecretsChangeDetector.java new file mode 100644 index 0000000000..0147314e86 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/reload/Fabric8EventBasedSecretsChangeDetector.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.reload; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.informers.ResourceEventHandler; +import io.fabric8.kubernetes.client.informers.SharedIndexInformer; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; +import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.core.env.AbstractEnvironment; + +/** + * An event based change detector that subscribes to changes in secrets and fire a reload + * when something changes. + * + * @author Nicola Ferraro + * @author Haytham Mohamed + * @author Kris Iyer + */ +public class Fabric8EventBasedSecretsChangeDetector extends ConfigurationChangeDetector { + + private final Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator; + + private final KubernetesClient kubernetesClient; + + private final boolean monitorSecrets; + + private SharedIndexInformer informer; + + public Fabric8EventBasedSecretsChangeDetector(AbstractEnvironment environment, ConfigReloadProperties properties, + KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy, + Fabric8SecretsPropertySourceLocator fabric8SecretsPropertySourceLocator) { + super(environment, properties, strategy); + this.kubernetesClient = kubernetesClient; + this.fabric8SecretsPropertySourceLocator = fabric8SecretsPropertySourceLocator; + this.monitorSecrets = properties.isMonitoringSecrets(); + } + + @PreDestroy + private void shutdown() { + if (informer != null) { + log.debug("closing secrets informer"); + informer.close(); + } + // Ensure the kubernetes client is cleaned up from spare threads when shutting + // down + kubernetesClient.close(); + } + + @PostConstruct + private void inform() { + if (monitorSecrets) { + log.info("Kubernetes event-based secrets change detector activated"); + informer = kubernetesClient.secrets().inform(); + informer.addEventHandler(new ResourceEventHandler<>() { + @Override + public void onAdd(Secret secret) { + onEvent(secret); + } + + @Override + public void onUpdate(Secret oldSecret, Secret newSecret) { + onEvent(newSecret); + } + + @Override + public void onDelete(Secret secret, boolean deletedFinalStateUnknown) { + onEvent(secret); + } + + // leave as comment on purpose, may be this will be useful in the future + // @Override + // public void onNothing() { + // boolean isStoreEmpty = informer.getStore().list().isEmpty(); + // if(!isStoreEmpty) { + // // HTTP_GONE, thus re-inform + // inform(); + // } + // } + }); + } + } + + protected void onEvent(Secret secret) { + log.debug("onEvent secrets: " + secret.toString()); + boolean changed = changed(locateMapPropertySources(fabric8SecretsPropertySourceLocator, environment), + findPropertySources(Fabric8SecretsPropertySource.class)); + if (changed) { + log.info("Detected change in secrets"); + reloadProperties(); + } + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/main/resources/META-INF/spring.factories b/spring-cloud-kubernetes-fabric8-config/src/main/resources/META-INF/spring.factories index cdd2220a9d..95082d6258 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-kubernetes-fabric8-config/src/main/resources/META-INF/spring.factories @@ -1,5 +1,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.kubernetes.fabric8.config.reload.ConfigReloadAutoConfiguration +org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8ConfigReloadAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.kubernetes.fabric8.config.Fabric8BootstrapConfiguration,\ org.springframework.cloud.kubernetes.fabric8.config.Fabric8RetryBootstrapConfiguration +# ConfigData Location Resolvers +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigDataLocationResolver diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapConfigMapsTests.java new file mode 100644 index 0000000000..0f8757fabc --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapConfigMapsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-example", "spring.cloud.kubernetes.reload.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BoostrapConfigMapsTests extends ConfigMapsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapFabric8SecretsPropertySourceTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapFabric8SecretsPropertySourceTest.java new file mode 100644 index 0000000000..fb75d00582 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapFabric8SecretsPropertySourceTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@TestPropertySource("classpath:/application-secrets.properties") +@EnableKubernetesMockClient(crud = true, https = false) +class BoostrapFabric8SecretsPropertySourceTest extends Fabric8SecretsPropertySourceTest { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapMultipleConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapMultipleConfigMapsTests.java new file mode 100644 index 0000000000..d9efdbcd86 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BoostrapMultipleConfigMapsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example2.ExampleApp; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleApp.class, + properties = { "spring.cloud.bootstrap.name=multiplecms", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BoostrapMultipleConfigMapsTests extends MultipleConfigMapsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsFromFilePathsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsFromFilePathsTests.java new file mode 100644 index 0000000000..d21ecd5a67 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsFromFilePathsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.io.IOException; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { + "spring.application.name=configmap-path-example", "spring.cloud.kubernetes.config.enableApi=false", + "spring.cloud.kubernetes.config.paths=" + BootstrapConfigMapsFromFilePathsTests.FIRST_FILE_NAME_FULL_PATH + "," + + BootstrapConfigMapsFromFilePathsTests.SECOND_FILE_NAME_FULL_PATH + "," + + BootstrapConfigMapsFromFilePathsTests.FIRST_FILE_NAME_DUPLICATED_FULL_PATH, + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsFromFilePathsTests extends ConfigMapsFromFilePathsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() throws IOException { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsMixedTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsMixedTests.java new file mode 100644 index 0000000000..b550c93953 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsMixedTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.io.IOException; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=" + BootstrapConfigMapsMixedTests.APPLICATION_NAME, + "spring.cloud.kubernetes.config.enableApi=true", + "spring.cloud.kubernetes.config.paths=" + BootstrapConfigMapsMixedTests.FILE_NAME_FULL_PATH, + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsMixedTests extends ConfigMapsMixedTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() throws IOException { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithActiveProfilesNameTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithActiveProfilesNameTests.java new file mode 100644 index 0000000000..2be715332d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithActiveProfilesNameTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Ali Shahbour + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-active-profile-name-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsWithActiveProfilesNameTests extends ConfigMapsWithActiveProfilesNameTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfileExpressionTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfileExpressionTests.java new file mode 100644 index 0000000000..85109710f0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfileExpressionTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests reading property from YAML document specified by profile expression. + */ + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles({ "production", "us-east" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsWithProfileExpressionTests extends ConfigMapsWithProfileExpressionTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesNoActiveProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesNoActiveProfileTests.java new file mode 100644 index 0000000000..ab6304dd57 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesNoActiveProfileTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-no-active-profiles-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsWithProfilesNoActiveProfileTests extends ConfigMapsWithProfilesNoActiveProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesTests.java new file mode 100644 index 0000000000..b3b957d05a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithProfilesTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsWithProfilesTests extends ConfigMapsWithProfilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithoutProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithoutProfilesTests.java new file mode 100644 index 0000000000..83d053ff22 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapsWithoutProfilesTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-without-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapConfigMapsWithoutProfilesTests extends ConfigMapsWithoutProfilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTest.java new file mode 100644 index 0000000000..6f796ddc2d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = TestApplication.class, + properties = { "spring.application.name=testapp", "spring.cloud.kubernetes.client.namespace=testns", + "spring.cloud.kubernetes.client.trustCerts=true", "spring.cloud.kubernetes.config.namespace=testns", + "spring.cloud.kubernetes.secrets.enableApi=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapCoreTest extends CoreTest { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTestClientViaSystemProperties.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTestClientViaSystemProperties.java new file mode 100644 index 0000000000..3b50cb2bf4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapCoreTestClientViaSystemProperties.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = TestApplication.class, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.application.name=testapp", + "spring.cloud.kubernetes.client.namespace=testns", "spring.cloud.kubernetes.client.trustCerts=true", + "spring.cloud.kubernetes.config.namespace=testns", "spring.cloud.kubernetes.secrets.enableApi=true", + "spring.cloud.bootstrap.enabled=true" }) +public class BootstrapCoreTestClientViaSystemProperties extends CoreTestClientViaSystemProperties { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapMultipleSecretsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapMultipleSecretsTests.java new file mode 100644 index 0000000000..351df84023 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapMultipleSecretsTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example3.MultiSecretsApp; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Haytham Mohamed + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MultiSecretsApp.class, + properties = { "spring.cloud.bootstrap.name=multiple-secrets", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class BootstrapMultipleSecretsTests extends MultipleSecretsTests { + + // will be injected by KubernetesMockServerExtension + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsFromFilePathsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsFromFilePathsTests.java new file mode 100644 index 0000000000..4f21d7b3d5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsFromFilePathsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.io.IOException; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, properties = { + "spring.application.name=configmap-path-example", "spring.cloud.kubernetes.config.enableApi=false", + "spring.cloud.kubernetes.config.paths=" + ConfigDataConfigMapsFromFilePathsTests.FIRST_FILE_NAME_FULL_PATH + "," + + ConfigDataConfigMapsFromFilePathsTests.SECOND_FILE_NAME_FULL_PATH + "," + + ConfigDataConfigMapsFromFilePathsTests.FIRST_FILE_NAME_DUPLICATED_FULL_PATH, + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsFromFilePathsTests extends ConfigMapsFromFilePathsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() throws IOException { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsMixedTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsMixedTests.java new file mode 100644 index 0000000000..ccfe012396 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsMixedTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.io.IOException; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=" + ConfigDataConfigMapsMixedTests.APPLICATION_NAME, + "spring.cloud.kubernetes.config.enableApi=true", + "spring.cloud.kubernetes.config.paths=" + ConfigDataConfigMapsMixedTests.FILE_NAME_FULL_PATH, + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsMixedTests extends ConfigMapsMixedTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() throws IOException { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsTests.java new file mode 100644 index 0000000000..83abf5d853 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-example", "spring.cloud.kubernetes.reload.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsTests extends ConfigMapsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithActiveProfilesNameTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithActiveProfilesNameTests.java new file mode 100644 index 0000000000..ec6f6793ec --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithActiveProfilesNameTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Ali Shahbour + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-active-profile-name-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsWithActiveProfilesNameTests extends ConfigMapsWithActiveProfilesNameTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfileExpressionTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfileExpressionTests.java new file mode 100644 index 0000000000..5505c44d96 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfileExpressionTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Tests reading property from YAML document specified by profile expression. + */ + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@ActiveProfiles({ "production", "us-east" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsWithProfileExpressionTests extends ConfigMapsWithProfileExpressionTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesNoActiveProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesNoActiveProfileTests.java new file mode 100644 index 0000000000..b48eabb2dd --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesNoActiveProfileTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-no-active-profiles-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsWithProfilesNoActiveProfileTests extends ConfigMapsWithProfilesNoActiveProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesTests.java new file mode 100644 index 0000000000..e890b6abf2 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithProfilesTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-with-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsWithProfilesTests extends ConfigMapsWithProfilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithoutProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithoutProfilesTests.java new file mode 100644 index 0000000000..a35ecbfcaf --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapsWithoutProfilesTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.application.name=configmap-without-profile-example", + "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@ActiveProfiles("development") +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataConfigMapsWithoutProfilesTests extends ConfigMapsWithoutProfilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTest.java new file mode 100644 index 0000000000..209d7dd454 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = TestApplication.class, + properties = { "spring.application.name=testapp", "spring.cloud.kubernetes.client.namespace=testns", + "spring.cloud.kubernetes.client.trustCerts=true", "spring.cloud.kubernetes.config.namespace=testns", + "spring.cloud.kubernetes.secrets.enableApi=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataCoreTest extends CoreTest { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTestClientViaSystemProperties.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTestClientViaSystemProperties.java new file mode 100644 index 0000000000..ed344d81e6 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataCoreTestClientViaSystemProperties.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = TestApplication.class, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.application.name=testapp", + "spring.cloud.kubernetes.client.namespace=testns", "spring.cloud.kubernetes.client.trustCerts=true", + "spring.cloud.kubernetes.config.namespace=testns", "spring.cloud.kubernetes.secrets.enableApi=true", + "spring.config.import=kubernetes:" }) +public class ConfigDataCoreTestClientViaSystemProperties extends CoreTestClientViaSystemProperties { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataFabric8SecretsPropertySourceTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataFabric8SecretsPropertySourceTest.java new file mode 100644 index 0000000000..acc286796d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataFabric8SecretsPropertySourceTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example.App; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }) +@TestPropertySource("classpath:/application-secrets.properties") +@EnableKubernetesMockClient(crud = true, https = false) +class ConfigDataFabric8SecretsPropertySourceTest extends Fabric8SecretsPropertySourceTest { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleConfigMapsTests.java new file mode 100644 index 0000000000..1d381b0d48 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleConfigMapsTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example2.ExampleApp; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Charles Moulliard + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleApp.class, + properties = { "spring.application.name=multiplecms", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./multiplecms.yml" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataMultipleConfigMapsTests extends MultipleConfigMapsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleSecretsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleSecretsTests.java new file mode 100644 index 0000000000..441b2c3ef1 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataMultipleSecretsTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.example3.MultiSecretsApp; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Haytham Mohamed + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MultiSecretsApp.class, + properties = { "spring.cloud.bootstrap.name=multiple-secrets", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./multiple-secrets.yml" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +public class ConfigDataMultipleSecretsTests extends MultipleSecretsTests { + + // will be injected by KubernetesMockServerExtension + private static KubernetesClient mockClient; + + @BeforeAll + public static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithIncludeProfileSpecificSourcesTests.java index 8a1b7aa615..dfaa5e37a2 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithIncludeProfileSpecificSourcesTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithIncludeProfileSpecificSourcesTests.java @@ -22,41 +22,24 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.hamcrest.Matchers; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.IncludeProfileSpecificSourcesApp; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** * @author wind57 */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = IncludeProfileSpecificSourcesApp.class, - properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources", - "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -@ActiveProfiles("dev") -class ConfigMapWithIncludeProfileSpecificSourcesTests { +abstract class ConfigMapWithIncludeProfileSpecificSourcesTests { private static KubernetesClient mockClient; @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { - + public static void setUpBeforeClass(KubernetesClient mockClient) { + ConfigMapWithIncludeProfileSpecificSourcesTests.mockClient = mockClient; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsFromFilePathsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsFromFilePathsTests.java index afb245c3e7..0161a06d85 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsFromFilePathsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsFromFilePathsTests.java @@ -22,9 +22,7 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,8 +42,7 @@ + ConfigMapsFromFilePathsTests.SECOND_FILE_NAME_FULL_PATH + "," + ConfigMapsFromFilePathsTests.FIRST_FILE_NAME_DUPLICATED_FULL_PATH, "spring.main.cloud-platform=KUBERNETES" }) -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsFromFilePathsTests { +abstract class ConfigMapsFromFilePathsTests { protected static final String FILES_ROOT_PATH = "/tmp/scktests"; @@ -66,13 +63,10 @@ public class ConfigMapsFromFilePathsTests { protected static final String FIRST_FILE_NAME_DUPLICATED_FULL_PATH = FILES_ROOT_PATH + "/" + FILES_SUB_PATH + "/" + FIRST_FILE_NAME; - private static KubernetesClient mockClient; - @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() throws IOException { + public static void setUpBeforeClass(KubernetesClient mockClient) throws IOException { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsMixedTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsMixedTests.java index 11ec267d18..0e967afbd4 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsMixedTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsMixedTests.java @@ -25,28 +25,15 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.assertj.core.util.Lists.newArrayList; -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=" + ConfigMapsMixedTests.APPLICATION_NAME, - "spring.cloud.kubernetes.config.enableApi=true", - "spring.cloud.kubernetes.config.paths=" + ConfigMapsMixedTests.FILE_NAME_FULL_PATH, - "spring.main.cloud-platform=KUBERNETES" }) -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsMixedTests { +abstract class ConfigMapsMixedTests { protected static final String FILES_ROOT_PATH = "/tmp/scktests"; @@ -56,13 +43,10 @@ public class ConfigMapsMixedTests { protected static final String APPLICATION_NAME = "configmap-mixed-example"; - private static KubernetesClient mockClient; - @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() throws IOException { + public static void setUpBeforeClass(KubernetesClient mockClient) throws IOException { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTest.java index d693485690..d85a26f746 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTest.java @@ -75,7 +75,7 @@ public void testConfigMapFromSingleApplicationProperties() { .build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); @@ -92,7 +92,7 @@ public void testConfigMapFromSingleApplicationYaml() { .addToData("application.yaml", ConfigMapTestUtil.readResourceFile("application.yaml")).build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); @@ -109,7 +109,7 @@ public void testConfigMapFromSingleNonStandardFileName() { .addToData("adhoc.yml", ConfigMapTestUtil.readResourceFile("adhoc.yml")).build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); @@ -126,7 +126,7 @@ public void testConfigMapFromSingleInvalidPropertiesContent() { .addToData("application.properties", "somevalue").build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); @@ -141,7 +141,7 @@ public void testConfigMapFromSingleInvalidYamlContent() { .addToData("application.yaml", "somevalue").build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "namespace", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); @@ -157,7 +157,7 @@ public void testConfigMapFromMultipleApplicationProperties() { .addToData("adhoc.properties", ConfigMapTestUtil.readResourceFile("adhoc.properties")).build(); mockClient.configMaps().inNamespace("test").create(configMap); - NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(configMapName, "test", false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); Fabric8ConfigMapPropertySource cmps = new Fabric8ConfigMapPropertySource(context); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTests.java index e40ad1a056..ad769409fb 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsTests.java @@ -22,16 +22,9 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.assertj.core.api.Assertions.assertThat; @@ -39,13 +32,7 @@ /** * @author Charles Moulliard */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-example", "spring.cloud.kubernetes.reload.enabled=false", - "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsTests { +abstract class ConfigMapsTests { private static final String APPLICATION_NAME = "configmap-example"; @@ -57,9 +44,8 @@ public class ConfigMapsTests { @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { - + public static void setUpBeforeClass(KubernetesClient mockClient) { + ConfigMapsTests.mockClient = mockClient; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithActiveProfilesNameTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithActiveProfilesNameTests.java index b5b1716f23..23f4054425 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithActiveProfilesNameTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithActiveProfilesNameTests.java @@ -21,47 +21,27 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.springframework.cloud.kubernetes.fabric8.config.ConfigMapTestUtil.readResourceFile; /** * @author Ali Shahbour */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-with-active-profile-name-example", - "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -@ActiveProfiles("development") -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsWithActiveProfilesNameTests { +abstract class ConfigMapsWithActiveProfilesNameTests { private static final String APPLICATION_NAME = "configmap-with-active-profile-name-example"; - private static KubernetesClient mockClient; - @Autowired(required = false) Config config; @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { - + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfileExpressionTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfileExpressionTests.java index 46d7c52570..e0605cf227 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfileExpressionTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfileExpressionTests.java @@ -21,41 +21,23 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** * Tests reading property from YAML document specified by profile expression. */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-with-profile-example", - "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -@ActiveProfiles({ "production", "us-east" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsWithProfileExpressionTests { - - private static KubernetesClient mockClient; +abstract class ConfigMapsWithProfileExpressionTests { private static final String APPLICATION_NAME = "configmap-with-profile-example"; @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesNoActiveProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesNoActiveProfileTests.java index e81a6fa04f..d7afd453cb 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesNoActiveProfileTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesNoActiveProfileTests.java @@ -21,16 +21,9 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.cloud.kubernetes.fabric8.config.ConfigMapTestUtil.readResourceFile; @@ -38,23 +31,14 @@ /** * @author Charles Moulliard */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-with-profile-no-active-profiles-example", - "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsWithProfilesNoActiveProfileTests { +abstract class ConfigMapsWithProfilesNoActiveProfileTests { private static final String APPLICATION_NAME = "configmap-with-profile-no-active-profiles-example"; - private static KubernetesClient mockClient; - @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesTests.java index 0ac709d3b3..56a8d0009d 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithProfilesTests.java @@ -21,43 +21,25 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** * @author Charles Moulliard */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-with-profile-example", - "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -@ActiveProfiles("development") -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsWithProfilesTests { +abstract class ConfigMapsWithProfilesTests { private static final String APPLICATION_NAME = "configmap-with-profile-example"; - private static KubernetesClient mockClient; - @Autowired(required = false) Config config; @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithoutProfilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithoutProfilesTests.java index 3c26bb93dd..9f8849abf6 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithoutProfilesTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapsWithoutProfilesTests.java @@ -21,37 +21,19 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.application.name=configmap-without-profile-example", - "spring.cloud.kubernetes.reload.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -@ActiveProfiles("development") -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class ConfigMapsWithoutProfilesTests { +abstract class ConfigMapsWithoutProfilesTests { private static final String APPLICATION_NAME = "configmap-without-profile-example"; - private static KubernetesClient mockClient; - @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTest.java index 677c4bc44d..be0c9b98eb 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTest.java @@ -23,25 +23,14 @@ import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; -import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = TestApplication.class, - properties = { "spring.application.name=testapp", "spring.cloud.kubernetes.client.namespace=testns", - "spring.cloud.kubernetes.client.trustCerts=true", "spring.cloud.kubernetes.config.namespace=testns", - "spring.cloud.kubernetes.secrets.enableApi=true", "spring.main.cloud-platform=KUBERNETES" }) -@EnableKubernetesMockClient(crud = true, https = false) -public class CoreTest { +abstract class CoreTest { private static KubernetesClient mockClient; @@ -51,9 +40,8 @@ public class CoreTest { @Autowired private Config config; - @BeforeAll - public static void setUpBeforeClass() { - + public static void setUpBeforeClass(KubernetesClient mockClient) { + CoreTest.mockClient = mockClient; Map data1 = new HashMap<>(); data1.put("spring.kubernetes.test.value", "value1"); mockClient.configMaps().inNamespace("testns").create( diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTestClientViaSystemProperties.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTestClientViaSystemProperties.java index 6ebdbd83a0..2b922b65e8 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTestClientViaSystemProperties.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/CoreTestClientViaSystemProperties.java @@ -20,20 +20,12 @@ import io.fabric8.kubernetes.client.KubernetesClient; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = TestApplication.class, - properties = { "spring.main.cloud-platform=KUBERNETES", "spring.application.name=testapp", - "spring.cloud.kubernetes.client.namespace=testns", "spring.cloud.kubernetes.client.trustCerts=true", - "spring.cloud.kubernetes.config.namespace=testns", "spring.cloud.kubernetes.secrets.enableApi=true" }) -public class CoreTestClientViaSystemProperties { +abstract class CoreTestClientViaSystemProperties { @Autowired private KubernetesClient client; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/EventBasedConfigurationChangeDetectorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/EventBasedConfigurationChangeDetectorTests.java index 756e998fff..f4e30c64ad 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/EventBasedConfigurationChangeDetectorTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/EventBasedConfigurationChangeDetectorTests.java @@ -22,8 +22,11 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapList; +import io.fabric8.kubernetes.api.model.ConfigMapListBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import org.junit.jupiter.api.Test; @@ -32,47 +35,51 @@ import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.cloud.kubernetes.fabric8.config.reload.EventBasedConfigMapChangeDetector; +import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8EventBasedConfigMapChangeDetector; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * @author Ryan Baxter */ -public class EventBasedConfigurationChangeDetectorTests { +class EventBasedConfigurationChangeDetectorTests { - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "raw" }) @Test - public void verifyConfigChangesAccountsForBootstrapPropertySources() { + void verifyConfigChangesAccountsForBootstrapPropertySources() { ConfigReloadProperties configReloadProperties = new ConfigReloadProperties(); MockEnvironment env = new MockEnvironment(); KubernetesClient k8sClient = mock(KubernetesClient.class); ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new ObjectMetaBuilder().withName("myconfigmap").build()); Map data = new HashMap<>(); data.put("foo", "bar"); configMap.setData(data); MixedOperation> mixedOperation = mock(MixedOperation.class); when(k8sClient.configMaps()).thenReturn(mixedOperation); + NonNamespaceOperation nonNamespaceOperation = mock(NonNamespaceOperation.class); + when(mixedOperation.inNamespace("default")).thenReturn(nonNamespaceOperation); + when(nonNamespaceOperation.list()).thenReturn(new ConfigMapListBuilder().addToItems(configMap).build()); + Resource resource = mock(Resource.class); when(resource.get()).thenReturn(configMap); - when(mixedOperation.withName(eq("myconfigmap"))).thenReturn(resource); - when(mixedOperation.inNamespace("default")).thenReturn(mixedOperation); when(k8sClient.getNamespace()).thenReturn("default"); - NormalizedSource source = new NamedConfigMapNormalizedSource("myconfigmap", "default", true, "", false); - Fabric8ConfigContext context = new Fabric8ConfigContext(k8sClient, source, "default", new MockEnvironment()); + NormalizedSource source = new NamedConfigMapNormalizedSource("myconfigmap", "default", true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(k8sClient, source, "default", env); Fabric8ConfigMapPropertySource fabric8ConfigMapPropertySource = new Fabric8ConfigMapPropertySource(context); env.getPropertySources().addFirst(new BootstrapPropertySource<>(fabric8ConfigMapPropertySource)); - ConfigurationUpdateStrategy configurationUpdateStrategy = mock(ConfigurationUpdateStrategy.class); + ConfigurationUpdateStrategy configurationUpdateStrategy = new ConfigurationUpdateStrategy("strategy", () -> { + + }); Fabric8ConfigMapPropertySourceLocator configMapLocator = mock(Fabric8ConfigMapPropertySourceLocator.class); - EventBasedConfigMapChangeDetector detector = new EventBasedConfigMapChangeDetector(env, configReloadProperties, - k8sClient, configurationUpdateStrategy, configMapLocator); + Fabric8EventBasedConfigMapChangeDetector detector = new Fabric8EventBasedConfigMapChangeDetector(env, + configReloadProperties, k8sClient, configurationUpdateStrategy, configMapLocator); List sources = detector .findPropertySources(Fabric8ConfigMapPropertySource.class); assertThat(sources.size()).isEqualTo(1); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java index b359c9c52a..5e958a3c68 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceLocatorTests.java @@ -25,6 +25,7 @@ import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; @@ -45,11 +46,13 @@ class Fabric8ConfigMapPropertySourceLocatorTests { private final DefaultKubernetesClient client = Mockito.mock(DefaultKubernetesClient.class); + private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "irrelevant"); + @Test void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { String name = "my-config"; String namespace = "default"; - String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name); + String path = String.format("/api/v1/namespaces/%s/configmaps", namespace); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); @@ -62,7 +65,7 @@ void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment())); assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name '" + name + "' in namespace '" + namespace + "'"); + .hasMessageContaining("api/v1/namespaces/default/configmaps. Message: Internal Server Error."); } @Test @@ -95,7 +98,7 @@ void constructorWithoutClientNamespaceMustFail() { Mockito.when(client.getNamespace()).thenReturn(null); Fabric8ConfigMapPropertySourceLocator source = new Fabric8ConfigMapPropertySourceLocator(client, configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment())); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("name", null, false, "prefix", false); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("name", null, false, PREFIX, false); assertThatThrownBy(() -> source.getMapPropertySource(normalizedSource, new MockEnvironment())) .isInstanceOf(NamespaceResolutionFailedException.class); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java index b9299a7d9a..1b826ad2bf 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigMapPropertySourceTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.mock.env.MockEnvironment; @@ -43,17 +44,19 @@ class Fabric8ConfigMapPropertySourceTests { private final DefaultKubernetesClient client = Mockito.mock(DefaultKubernetesClient.class); + private static final ConfigUtils.Prefix DEFAULT = ConfigUtils.findPrefix("default", false, false, "irrelevant"); + @Test void constructorShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { final String name = "my-config"; final String namespace = "default"; - final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name); + final String path = String.format("/api/v1/namespaces/%s/configmaps", namespace); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); - NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, true, "default", true); + NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, true, DEFAULT, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "default", new MockEnvironment()); assertThatThrownBy(() -> new Fabric8ConfigMapPropertySource(context)).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name '" + name + "' in namespace '" + namespace + "'"); + .hasMessageContaining("v1/namespaces/default/configmaps. Message: Internal Server Error."); } @Test @@ -63,7 +66,7 @@ void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { final String path = String.format("/api/v1/namespaces/%s/configmaps/%s", namespace, name); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); - NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource(name, namespace, false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); assertThatNoException().isThrownBy(() -> new Fabric8ConfigMapPropertySource(context)); } @@ -72,7 +75,7 @@ void constructorShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { void constructorWithClientNamespaceMustNotFail() { Mockito.when(client.getNamespace()).thenReturn("namespace"); - NormalizedSource source = new NamedConfigMapNormalizedSource("configmap", null, false, "", false); + NormalizedSource source = new NamedConfigMapNormalizedSource("configmap", null, false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); assertThat(new Fabric8ConfigMapPropertySource(context)).isNotNull(); } @@ -81,7 +84,7 @@ void constructorWithClientNamespaceMustNotFail() { void constructorWithNamespaceMustNotFail() { Mockito.when(client.getNamespace()).thenReturn(null); - NormalizedSource source = new NamedConfigMapNormalizedSource("configMap", null, false, "", true); + NormalizedSource source = new NamedConfigMapNormalizedSource("configMap", null, false, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, source, "", new MockEnvironment()); assertThat(new Fabric8ConfigMapPropertySource(context)).isNotNull(); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java index bc8df7a2ab..0b779d465e 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8ConfigUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,24 @@ package org.springframework.cloud.kubernetes.fabric8.config; +import java.util.Base64; +import java.util.Map; +import java.util.Set; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamespaceResolutionFailedException; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -32,7 +42,7 @@ * @author wind57 */ @EnableKubernetesMockClient(crud = true, https = false) -public class Fabric8ConfigUtilsTests { +class Fabric8ConfigUtilsTests { private KubernetesClient client; @@ -41,13 +51,13 @@ public class Fabric8ConfigUtilsTests { private final KubernetesNamespaceProvider provider = Mockito.mock(KubernetesNamespaceProvider.class); @Test - public void testGetApplicationNamespaceNotPresent() { + void testGetApplicationNamespaceNotPresent() { String result = Fabric8ConfigUtils.getApplicationNamespace(client, "", "target", null); assertThat(result).isEqualTo("test"); } @Test - public void testGetApplicationNamespacePresent() { + void testGetApplicationNamespacePresent() { String result = Fabric8ConfigUtils.getApplicationNamespace(client, "namespace", "target", null); assertThat(result).isEqualTo("namespace"); } @@ -78,4 +88,240 @@ void testNamespaceResolutionFailed() { .isInstanceOf(NamespaceResolutionFailedException.class); } + // secret "my-secret" is deployed without any labels; we search for it by labels + // "color=red" and do not find it. + @Test + void testSecretDataByLabelsSecretNotFound() { + client.secrets().inNamespace("spring-k8s").create( + new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()).build()); + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByLabels(client, "spring-k8s", + Map.of("color", "red"), new MockEnvironment(), Set.of()); + Assertions.assertEquals(Map.of(), result.data()); + Assertions.assertTrue(result.names().isEmpty()); + } + + // secret "my-secret" is deployed with label {color:pink}; we search for it by same + // label and find it. + @Test + void testSecretDataByLabelsSecretFound() { + client.secrets().inNamespace("spring-k8s").create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("my-secret").withLabels(Map.of("color", "pink")).build()) + .addToData(Map.of("property", Base64.getEncoder().encodeToString("value".getBytes()))).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByLabels(client, "spring-k8s", + Map.of("color", "pink"), new MockEnvironment(), Set.of()); + Assertions.assertEquals(Set.of("my-secret"), result.names()); + Assertions.assertEquals(Map.of("property", "value"), result.data()); + } + + // secret "my-secret" is deployed with label {color:pink}; we search for it by same + // label and find it. This secret contains a single .yaml property, as such + // it gets some special treatment. + @Test + void testSecretDataByLabelsSecretFoundWithPropertyFile() { + client.secrets().inNamespace("spring-k8s").create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("my-secret").withLabels(Map.of("color", "pink")).build()) + .addToData(Map.of("application.yaml", Base64.getEncoder().encodeToString("key1: value1".getBytes()))) + .build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByLabels(client, "spring-k8s", + Map.of("color", "pink"), new MockEnvironment(), Set.of()); + Assertions.assertEquals(Set.of("my-secret"), result.names()); + Assertions.assertEquals(Map.of("key1", "value1"), result.data()); + } + + // secrets "my-secret" and "my-secret-2" are deployed with label {color:pink}; + // we search for them by same label and find them. + @Test + void testSecretDataByLabelsTwoSecretsFound() { + client.secrets().inNamespace("spring-k8s").create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("my-secret").withLabels(Map.of("color", "pink")).build()) + .addToData(Map.of("property", Base64.getEncoder().encodeToString("value".getBytes()))).build()); + + client.secrets().inNamespace("spring-k8s").create(new SecretBuilder() + .withMetadata( + new ObjectMetaBuilder().withName("my-secret-2").withLabels(Map.of("color", "pink")).build()) + .addToData(Map.of("property-2", Base64.getEncoder().encodeToString("value-2".getBytes()))).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByLabels(client, "spring-k8s", + Map.of("color", "pink"), new MockEnvironment(), Set.of()); + Assertions.assertTrue(result.names().contains("my-secret")); + Assertions.assertTrue(result.names().contains("my-secret-2")); + + Assertions.assertEquals(2, result.data().size()); + Assertions.assertEquals("value", result.data().get("property")); + Assertions.assertEquals("value-2", result.data().get("property-2")); + } + + /** + *
+	 *     - secret deployed with name "blue-circle-secret" and labels "color=blue, shape=circle, tag=fit"
+	 *     - secret deployed with name "blue-square-secret" and labels "color=blue, shape=square, tag=fit"
+	 *     - secret deployed with name "blue-triangle-secret" and labels "color=blue, shape=triangle, tag=no-fit"
+	 *     - secret deployed with name "blue-square-secret-k8s" and labels "color=blue, shape=triangle, tag=no-fit"
+	 *
+	 *     - we search by labels "color=blue, tag=fits", as such first find two secrets: "blue-circle-secret"
+	 *       and "blue-square-secret".
+	 *     - since "k8s" profile is enabled, we also take "blue-square-secret-k8s". Notice that this one does not match
+	 *       the initial labels (it has "tag=no-fit"), but it does not matter, we take it anyway.
+	 * 
+ */ + @Test + void testSecretDataByLabelsThreeSecretsFound() { + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("blue-circle-secret") + .withLabels(Map.of("color", "blue", "shape", "circle", "tag", "fit")).build()) + .addToData(Map.of("one", Base64.getEncoder().encodeToString("1".getBytes()))).build()); + + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("blue-square-secret") + .withLabels(Map.of("color", "blue", "shape", "square", "tag", "fit")).build()) + .addToData(Map.of("two", Base64.getEncoder().encodeToString("2".getBytes()))).build()); + + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("blue-triangle-secret") + .withLabels(Map.of("color", "blue", "shape", "triangle", "tag", "no-fit")).build()) + .addToData(Map.of("three", Base64.getEncoder().encodeToString("3".getBytes()))).build()); + + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withName("blue-square-secret-k8s") + .withLabels(Map.of("color", "blue", "shape", "triangle", "tag", "no-fit")).build()) + .addToData(Map.of("four", Base64.getEncoder().encodeToString("4".getBytes()))).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByLabels(client, "spring-k8s", + Map.of("tag", "fit", "color", "blue"), new MockEnvironment(), Set.of("k8s")); + + Assertions.assertTrue(result.names().contains("blue-circle-secret")); + Assertions.assertTrue(result.names().contains("blue-square-secret")); + Assertions.assertTrue(result.names().contains("blue-square-secret-k8s")); + + Assertions.assertEquals(3, result.data().size()); + Assertions.assertEquals("1", result.data().get("one")); + Assertions.assertEquals("2", result.data().get("two")); + Assertions.assertEquals("4", result.data().get("four")); + } + + // secret "my-secret" is deployed; we search for it by name and do not find it. + @Test + void testSecretDataByNameSecretNotFound() { + client.secrets().inNamespace("spring-k8s").create( + new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()).build()); + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", Set.of("nope"), + new MockEnvironment()); + Assertions.assertEquals(0, result.names().size()); + Assertions.assertEquals(0, result.data().size()); + } + + // secret "my-secret" is deployed; we search for it by name and find it. + @Test + void testSecretDataByNameSecretFound() { + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()) + .addToData(Map.of("property", Base64.getEncoder().encodeToString("value".getBytes()))).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", + Set.of("my-secret"), new MockEnvironment()); + Assertions.assertEquals(1, result.names().size()); + Assertions.assertEquals("value", result.data().get("property")); + } + + // secrets "my-secret" and "my-secret-2" are deployed; + // we search for them by name label and find them. + @Test + void testSecretDataByNameTwoSecretsFound() { + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret").build()) + .addToData(Map.of("property", Base64.getEncoder().encodeToString("value".getBytes()))).build()); + + client.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withMetadata(new ObjectMetaBuilder().withName("my-secret-2").build()) + .addToData(Map.of("property-2", Base64.getEncoder().encodeToString("value-2".getBytes()))) + .build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.secretsDataByName(client, "spring-k8s", + Set.of("my-secret", "my-secret-2"), new MockEnvironment()); + Assertions.assertTrue(result.names().contains("my-secret")); + Assertions.assertTrue(result.names().contains("my-secret-2")); + + Assertions.assertEquals(2, result.data().size()); + Assertions.assertEquals("value", result.data().get("property")); + Assertions.assertEquals("value-2", result.data().get("property-2")); + } + + // config-map "my-config-map" is deployed without any data; we search for it by name + // and find it; but it has no data. + @Test + void testConfigMapsDataByNameFoundNoData() { + client.configMaps().inNamespace("spring-k8s").create( + new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()).build()); + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", + Set.of("my-config-map"), new MockEnvironment()); + Assertions.assertEquals(Set.of("my-config-map"), result.names()); + Assertions.assertTrue(result.data().isEmpty()); + } + + // config-map "my-config-map" is deployed; we search for it and do not find it. + @Test + void testConfigMapsDataByNameNotFound() { + client.configMaps().inNamespace("spring-k8s").create( + new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()).build()); + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", + Set.of("my-config-map-not-found"), new MockEnvironment()); + Assertions.assertEquals(Set.of(), result.names()); + Assertions.assertTrue(result.data().isEmpty()); + } + + // config-map "my-config-map" is deployed; we search for it and find it + @Test + void testConfigMapDataByNameFound() { + client.configMaps().inNamespace("spring-k8s") + .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()) + .addToData(Map.of("property", "value")).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", + Set.of("my-config-map"), new MockEnvironment()); + Assertions.assertEquals(Set.of("my-config-map"), result.names()); + Assertions.assertEquals(Map.of("property", "value"), result.data()); + } + + // config-map "my-config-map" is deployed; we search for it and find it. + // It contains a single .yaml property, as such it gets some special treatment. + @Test + void testConfigMapDataByNameFoundWithPropertyFile() { + client.configMaps().inNamespace("spring-k8s") + .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()) + .addToData(Map.of("application.yaml", "key1: value1")).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", + Set.of("my-config-map"), new MockEnvironment()); + Assertions.assertEquals(Set.of("my-config-map"), result.names()); + Assertions.assertEquals(Map.of("key1", "value1"), result.data()); + } + + // config-map "my-config-map" and "my-config-map-2" are deployed; + // we search and find them. + @Test + void testConfigMapDataByNameTwoFound() { + client.configMaps().inNamespace("spring-k8s") + .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map").build()) + .addToData(Map.of("property", "value")).build()); + + client.configMaps().inNamespace("spring-k8s") + .create(new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder().withName("my-config-map-2").build()) + .addToData(Map.of("property-2", "value-2")).build()); + + MultipleSourcesContainer result = Fabric8ConfigUtils.configMapsDataByName(client, "spring-k8s", + Set.of("my-config-map", "my-config-map-2"), new MockEnvironment()); + Assertions.assertTrue(result.names().contains("my-config-map")); + Assertions.assertTrue(result.names().contains("my-config-map-2")); + + Assertions.assertEquals(2, result.data().size()); + Assertions.assertEquals("value", result.data().get("property")); + Assertions.assertEquals("value-2", result.data().get("property-2")); + } + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java index c5dfde9a73..fc975aa688 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceLocatorTests.java @@ -32,17 +32,17 @@ * @author Isik Erhan */ @EnableKubernetesMockClient -public class Fabric8SecretsPropertySourceLocatorTests { +class Fabric8SecretsPropertySourceLocatorTests { KubernetesMockServer mockServer; KubernetesClient mockClient; @Test - public void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { + void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { final String name = "my-config"; final String namespace = "default"; - final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name); + final String path = String.format("/api/v1/namespaces/%s/secrets", namespace); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); @@ -56,11 +56,11 @@ public void locateShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { configMapConfigProperties, new KubernetesNamespaceProvider(new MockEnvironment())); assertThatThrownBy(() -> locator.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name '" + name + "' in namespace '" + namespace + "'"); + .hasMessageContaining("v1/namespaces/default/secrets. Message: Internal Server Error."); } @Test - public void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { + void locateShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { final String name = "my-config"; final String namespace = "default"; final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java index da916d1a34..d4c03b27b3 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceMockTests.java @@ -49,26 +49,27 @@ void namedStrategyShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { final String namespace = "default"; final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name); - NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, true); + NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(client, named, "default", new MockEnvironment()); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); assertThatThrownBy(() -> new Fabric8SecretsPropertySource(context)).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name '" + name + "' in namespace '" + namespace + "'"); + .hasMessageContaining("Failure executing: GET at: https://localhost:") + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Not Found."); } @Test void labeledStrategyShouldThrowExceptionOnFailureWhenFailFastIsEnabled() { final String namespace = "default"; final Map labels = Collections.singletonMap("a", "b"); - final String path = String.format("/api/v1/namespaces/%s/secrets?labelSelector=", namespace) + "a%3Db"; + final String path = String.format("/api/v1/namespaces/%s/secrets", namespace); - LabeledSecretNormalizedSource labeled = new LabeledSecretNormalizedSource(namespace, labels, true); + LabeledSecretNormalizedSource labeled = new LabeledSecretNormalizedSource(namespace, labels, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(client, labeled, "default", new MockEnvironment()); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); assertThatThrownBy(() -> new Fabric8SecretsPropertySource(context)).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with labels [" + labels + "] in namespace '" + namespace + "'"); + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Internal Server Error."); } @Test @@ -77,7 +78,7 @@ void namedStrategyShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { final String namespace = "default"; final String path = String.format("/api/v1/namespaces/%s/secrets/%s", namespace, name); - NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, false); + NamedSecretNormalizedSource named = new NamedSecretNormalizedSource(name, namespace, false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(client, named, "default", new MockEnvironment()); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); @@ -90,7 +91,7 @@ void labeledStrategyShouldNotThrowExceptionOnFailureWhenFailFastIsDisabled() { final Map labels = Collections.singletonMap("a", "b"); final String path = String.format("/api/v1/namespaces/%s/secrets?labelSelector=", namespace) + "a%3Db"; - LabeledSecretNormalizedSource labeled = new LabeledSecretNormalizedSource(namespace, labels, false); + LabeledSecretNormalizedSource labeled = new LabeledSecretNormalizedSource(namespace, labels, false, false); Fabric8ConfigContext context = new Fabric8ConfigContext(client, labeled, "default", new MockEnvironment()); mockServer.expect().withPath(path).andReturn(500, "Internal Server Error").once(); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceTest.java index d6816bc557..767ec54703 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/Fabric8SecretsPropertySourceTest.java @@ -22,33 +22,19 @@ import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example.App; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit.jupiter.SpringExtension; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = "spring.main.cloud-platform=KUBERNETES") -@TestPropertySource("classpath:/application-secrets.properties") -@EnableKubernetesMockClient(crud = true, https = false) -class Fabric8SecretsPropertySourceTest { +abstract class Fabric8SecretsPropertySourceTest { private static final String NAMESPACE = "test"; - private static KubernetesClient mockClient; - private static final String SECRET_VALUE = "secretValue"; @Autowired @@ -57,8 +43,7 @@ class Fabric8SecretsPropertySourceTest { @Autowired private Environment environment; - @BeforeAll - static void setUpBeforeClass() { + static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java index eafda75f97..6d27eb2c59 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigConfigurationTest.java @@ -41,13 +41,21 @@ public class KubernetesConfigConfigurationTest extends KubernetesConfigTestBase private static KubernetesClient mockClient; @Test - public void kubernetesWhenKubernetesDefaultEnabled() { + public void kubernetesBootstrapWhenKubernetesDefaultEnabled() { setup(KubernetesClientTestConfiguration.class, "spring.main.cloud-platform=KUBERNETES", - "spring.cloud.kubernetes.client.namespace=default"); + "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.bootstrap.enabled=true"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isTrue(); } + @Test + public void kubernetesConfigDataWhenKubernetesDefaultEnabled() { + setup(KubernetesClientTestConfiguration.class, "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.kubernetes.client.namespace=default", "spring.config.import=kubernetes:"); + assertThat(getContext().containsBean("configDataConfigMapPropertySourceLocator")).isTrue(); + assertThat(getContext().containsBean("configDataSecretsPropertySourceLocator")).isTrue(); + } + @Test public void kubernetesWhenKubernetesDisabled() { setup(KubernetesClientTestConfiguration.class); @@ -64,31 +72,59 @@ public void kubernetesWhenKubernetesConfigAndSecretDisabled() { } @Test - public void kubernetesWhenKubernetesConfigEnabledButSecretDisabled() { + public void kubernetesBootstrapWhenKubernetesConfigEnabledButSecretDisabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.client.namespace=default", - "spring.main.cloud-platform=KUBERNETES"); + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isFalse(); } @Test - public void kubernetesWhenKubernetesConfigDisabledButSecretEnabled() { + public void kubernetesConfigDataWhenKubernetesConfigEnabledButSecretDisabled() { + setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", + "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.client.namespace=default", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:"); + assertThat(getContext().containsBean("configDataConfigMapPropertySourceLocator")).isTrue(); + assertThat(getContext().containsBean("configDataSecretsPropertySourceLocator")).isFalse(); + } + + @Test + public void kubernetesBootstrapWhenKubernetesConfigDisabledButSecretEnabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=false", - "spring.cloud.kubernetes.secrets.enabled=true", "spring.main.cloud-platform=KUBERNETES"); + "spring.cloud.kubernetes.secrets.enabled=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isFalse(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isTrue(); } @Test - public void kubernetesConfigWhenKubernetesEnabledAndKubernetesConfigEnabled() { + public void kubernetesConfigDataWhenKubernetesConfigDisabledButSecretEnabled() { + setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=false", + "spring.cloud.kubernetes.secrets.enabled=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:"); + assertThat(getContext().containsBean("configDataConfigMapPropertySourceLocator")).isFalse(); + assertThat(getContext().containsBean("configDataSecretsPropertySourceLocator")).isTrue(); + } + + @Test + public void kubernetesBootstrapConfigWhenKubernetesEnabledAndKubernetesConfigEnabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", "spring.cloud.kubernetes.secrets.enabled=true", "spring.cloud.kubernetes.client.namespace=default", - "spring.main.cloud-platform=KUBERNETES"); + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true"); assertThat(getContext().containsBean("configMapPropertySourceLocator")).isTrue(); assertThat(getContext().containsBean("secretsPropertySourceLocator")).isTrue(); } + @Test + public void kubernetesConfigDataConfigWhenKubernetesEnabledAndKubernetesConfigEnabled() { + setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=true", + "spring.cloud.kubernetes.secrets.enabled=true", "spring.cloud.kubernetes.client.namespace=default", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:"); + assertThat(getContext().containsBean("configDataConfigMapPropertySourceLocator")).isTrue(); + assertThat(getContext().containsBean("configDataSecretsPropertySourceLocator")).isTrue(); + } + @Test public void kubernetesConfigWhenKubernetesEnabledAndKubernetesConfigDisabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.config.enabled=false"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigTestBase.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigTestBase.java index 9d3075bbb3..462d8b446b 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigTestBase.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/KubernetesConfigTestBase.java @@ -16,13 +16,16 @@ package org.springframework.cloud.kubernetes.fabric8.config; +import java.util.Arrays; +import java.util.stream.Stream; + import org.junit.jupiter.api.AfterEach; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.cloud.bootstrap.BootstrapConfiguration; -import org.springframework.cloud.kubernetes.fabric8.config.reload.ConfigReloadAutoConfiguration; +import org.springframework.cloud.kubernetes.fabric8.config.reload.Fabric8ConfigReloadAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; /** @@ -32,14 +35,18 @@ public class KubernetesConfigTestBase { private ConfigurableApplicationContext context; + protected String[] commonProperties = new String[0]; + protected ConfigurableApplicationContext getContext() { return context; } protected void setup(Class mockClientConfiguration, String... env) { + String[] properties = Stream.concat(Arrays.stream(commonProperties), Arrays.stream(env)).toArray(String[]::new); context = new SpringApplicationBuilder(PropertyPlaceholderAutoConfiguration.class, mockClientConfiguration, - BootstrapConfiguration.class, ConfigReloadAutoConfiguration.class, RefreshAutoConfiguration.class) - .web(org.springframework.boot.WebApplicationType.NONE).properties(env).run(); + BootstrapConfiguration.class, Fabric8ConfigReloadAutoConfiguration.class, + RefreshAutoConfiguration.class).web(org.springframework.boot.WebApplicationType.NONE) + .properties(properties).run(); } @AfterEach diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProviderTests.java new file mode 100644 index 0000000000..1c967145d2 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProviderTests.java @@ -0,0 +1,414 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.LabeledConfigMapNormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; +import org.springframework.cloud.kubernetes.commons.config.SourceData; +import org.springframework.mock.env.MockEnvironment; + +/** + * @author wind57 + */ +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledConfigMapContextToSourceDataProviderTests { + + private static final String NAMESPACE = "default"; + + private static final Map LABELS = new LinkedHashMap<>(); + + private static final Map RED_LABEL = Map.of("color", "red"); + + private static final Map PINK_LABEL = Map.of("color", "pink"); + + private static final Map BLUE_LABEL = Map.of("color", "blue"); + + private static KubernetesClient mockClient; + + static { + LABELS.put("label2", "value2"); + LABELS.put("label1", "value1"); + } + + @BeforeAll + static void beforeAll() { + + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, NAMESPACE); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + } + + @AfterEach + void afterEach() { + mockClient.configMaps().inNamespace(NAMESPACE).delete(); + } + + /** + * we have a single config map deployed. it has two labels and these match against our + * queries. + */ + @Test + void singleConfigMapMatchAgainstLabels() { + + ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("test-configmap").withLabels(LABELS) + .endMetadata().addToData("name", "value").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, LABELS, true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData()); + + } + + /** + * we have three configmaps deployed. two of them have labels that match (color=red), + * one does not (color=blue). + */ + @Test + void twoConfigMapsMatchAgainstLabels() { + + ConfigMap redOne = new ConfigMapBuilder().withNewMetadata().withName("red-configmap").withLabels(RED_LABEL) + .endMetadata().addToData("colorOne", "really-red").build(); + + ConfigMap redTwo = new ConfigMapBuilder().withNewMetadata().withName("red-configmap-again") + .withLabels(RED_LABEL).endMetadata().addToData("colorTwo", "really-red-again").build(); + + ConfigMap blue = new ConfigMapBuilder().withNewMetadata().withName("blue-configmap").withLabels(BLUE_LABEL) + .endMetadata().addToData("color", "blue").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(redOne); + mockClient.configMaps().inNamespace(NAMESPACE).create(redTwo); + mockClient.configMaps().inNamespace(NAMESPACE).create(blue); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, RED_LABEL, true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.red-configmap.red-configmap-again.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("colorOne"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("colorTwo"), "really-red-again"); + + } + + /** + * one configmap deployed (pink), does not match our query (blue). + */ + @Test + void configMapNoMatch() { + + ConfigMap pink = new ConfigMapBuilder().withNewMetadata().withName("pink-configmap").withLabels(PINK_LABEL) + .endMetadata().addToData("color", "pink").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(pink); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, BLUE_LABEL, true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default"); + Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); + } + + /** + * LabeledConfigMapContextToSourceDataProvider gets as input a Fabric8ConfigContext. + * This context has a namespace as well as a NormalizedSource, that has a namespace + * too. It is easy to get confused in code on which namespace to use. This test makes + * sure that we use the proper one. + */ + @Test + void namespaceMatch() { + + ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("test-configmap").withLabels(LABELS) + .endMetadata().addToData("name", "value").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + + // different namespace + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE + "nope", LABELS, true, + false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.test-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("name", "value"), sourceData.sourceData()); + } + + /** + * one configmap with name : "blue-configmap" and labels "color=blue" is deployed. we + * search it with the same labels, find it, and assert that name of the SourceData (it + * must use its name, not its labels) and values in the SourceData must be prefixed + * (since we have provided an explicit prefix). + */ + @Test + void testWithPrefix() { + ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("blue-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("what-color", "blue-color").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + + ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant"); + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, mePrefix, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("configmap.blue-configmap.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData()); + } + + /** + * two configmaps are deployed (name:blue-configmap, name:another-blue-configmap) and + * labels "color=blue" (on both). we search with the same labels, find them, and + * assert that name of the SourceData (it must use its name, not its labels) and + * values in the SourceData must be prefixed (since we have provided a delayed + * prefix). + * + * Also notice that the prefix is made up from both configmap names. + * + */ + @Test + void testTwoConfigmapsWithPrefix() { + ConfigMap blueConfigMap = new ConfigMapBuilder().withNewMetadata().withName("blue-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("first", "blue").build(); + + ConfigMap anotherBlue = new ConfigMapBuilder().withNewMetadata().withName("another-blue-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("second", "blue") + .build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(blueConfigMap); + mockClient.configMaps().inNamespace(NAMESPACE).create(anotherBlue); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.another-blue-configmap.blue-configmap.default"); + + Map properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator keys = properties.keySet().iterator(); + String firstKey = keys.next(); + String secondKey = keys.next(); + + if (firstKey.contains("first")) { + Assertions.assertEquals(firstKey, "another-blue-configmap.blue-configmap.first"); + } + + Assertions.assertEquals(secondKey, "another-blue-configmap.blue-configmap.second"); + Assertions.assertEquals(properties.get(firstKey), "blue"); + Assertions.assertEquals(properties.get(secondKey), "blue"); + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "color-configmap-k8s" with no labels. We search by "{color:red}", do not find + * anything and thus have an empty SourceData. profile based sources are enabled, but + * it has no effect. + */ + @Test + void searchWithLabelsNoConfigmapsFound() { + ConfigMap colorConfigmap = new ConfigMapBuilder().withNewMetadata().withName("color-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("one", "1").build(); + + ConfigMap colorConfigmapK8s = new ConfigMapBuilder().withNewMetadata().withName("color-configmap-k8s") + .endMetadata().addToData("two", "2").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmap); + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmapK8s); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "red"), true, ConfigUtils.Prefix.DEFAULT, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertTrue(sourceData.sourceData().isEmpty()); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color.default"); + + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "shape-configmap" with label: "{shape:round}". We search by "{color:blue}" and find + * one configmap. profile based sources are enabled, but it has no effect. + */ + @Test + void searchWithLabelsOneConfigMapFound() { + ConfigMap colorConfigmap = new ConfigMapBuilder().withNewMetadata().withName("color-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("one", "1").build(); + + ConfigMap shapeConfigmap = new ConfigMapBuilder().withNewMetadata().withName("shape-configmap").endMetadata() + .addToData("two", "2").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmap); + mockClient.configMaps().inNamespace(NAMESPACE).create(shapeConfigmap); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DEFAULT, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("one"), "1"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.default"); + + } + + /** + * two configmaps are deployed: "color-configmap" with label: "{color:blue}" and + * "color-configmap-k8s" with label: "{color:red}". We search by "{color:blue}" and + * find one configmap. Since profiles are enabled, we will also be reading + * "color-configmap-k8s", even if its labels do not match provided ones. + */ + @Test + void searchWithLabelsOneConfigMapFoundAndOneFromProfileFound() { + ConfigMap colorConfigmap = new ConfigMapBuilder().withNewMetadata().withName("color-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("one", "1").build(); + + ConfigMap colorConfigmapK8s = new ConfigMapBuilder().withNewMetadata().withName("color-configmap-k8s") + .withLabels(Collections.singletonMap("color", "red")).endMetadata().addToData("two", "2").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmap); + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmapK8s); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData().get("color-configmap.color-configmap-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.color-configmap.color-configmap-k8s.default"); + + } + + /** + *
+	 *     - configmap "color-configmap" with label "{color:blue}"
+	 *     - configmap "shape-configmap" with labels "{color:blue, shape:round}"
+	 *     - configmap "no-fit" with labels "{tag:no-fit}"
+	 *     - configmap "color-configmap-k8s" with label "{color:red}"
+	 *     - configmap "shape-configmap-k8s" with label "{shape:triangle}"
+	 * 
+ */ + @Test + void searchWithLabelsTwoConfigMapsFoundAndOneFromProfileFound() { + ConfigMap colorConfigMap = new ConfigMapBuilder().withNewMetadata().withName("color-configmap") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata().addToData("one", "1").build(); + + ConfigMap shapeConfigmap = new ConfigMapBuilder().withNewMetadata().withName("shape-configmap") + .withLabels(Map.of("color", "blue", "shape", "round")).endMetadata().addToData("two", "2").build(); + + ConfigMap noFit = new ConfigMapBuilder().withNewMetadata().withName("no-fit") + .withLabels(Map.of("tag", "no-fit")).endMetadata().addToData("three", "3").build(); + + ConfigMap colorConfigmapK8s = new ConfigMapBuilder().withNewMetadata().withName("color-configmap-k8s") + .withLabels(Map.of("color", "red")).endMetadata().addToData("four", "4").build(); + + ConfigMap shapeConfigmapK8s = new ConfigMapBuilder().withNewMetadata().withName("shape-configmap-k8s") + .withLabels(Map.of("shape", "triangle")).endMetadata().addToData("five", "5").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigMap); + mockClient.configMaps().inNamespace(NAMESPACE).create(shapeConfigmap); + mockClient.configMaps().inNamespace(NAMESPACE).create(noFit); + mockClient.configMaps().inNamespace(NAMESPACE).create(colorConfigmapK8s); + mockClient.configMaps().inNamespace(NAMESPACE).create(shapeConfigmapK8s); + + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledConfigMapNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 4); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.four"), "4"); + Assertions.assertEquals(sourceData.sourceData() + .get("color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.five"), "5"); + + Assertions.assertEquals(sourceData.sourceName(), + "configmap.color-configmap.color-configmap-k8s.shape-configmap.shape-configmap-k8s.default"); + + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java index 662762956e..c9099d5da6 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledSecretContextToSourceDataProviderTests.java @@ -18,6 +18,7 @@ import java.util.Base64; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @@ -31,9 +32,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource; import org.springframework.cloud.kubernetes.commons.config.SourceData; import org.springframework.mock.env.MockEnvironment; @@ -92,20 +93,20 @@ void singleSecretMatchAgainstLabels() { mockClient.secrets().inNamespace(NAMESPACE).create(secret); - NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, true); + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, LABELS, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = LabeledSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals("secrets.test-secret.default", sourceData.sourceName()); + Assertions.assertEquals("secret.test-secret.default", sourceData.sourceName()); Assertions.assertEquals(Map.of("secretName", "secretValue"), sourceData.sourceData()); } /** - * we have three secret deployed. two of them have labels that match (color=red), one + * we have three secrets deployed. two of them have labels that match (color=red), one * does not (color=blue). */ @Test @@ -125,14 +126,14 @@ void twoSecretsMatchAgainstLabels() { mockClient.secrets().inNamespace(NAMESPACE).create(redTwo); mockClient.secrets().inNamespace(NAMESPACE).create(blue); - NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, true); + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, RED_LABEL, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = LabeledSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red-secret.red-secret-again.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red-secret.red-secret-again.default"); Assertions.assertEquals(sourceData.sourceData().size(), 2); Assertions.assertEquals(sourceData.sourceData().get("colorOne"), "really-red"); Assertions.assertEquals(sourceData.sourceData().get("colorTwo"), "really-red-again"); @@ -150,14 +151,14 @@ void secretNoMatch() { mockClient.secrets().inNamespace(NAMESPACE).create(pink); - NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, BLUE_LABEL, true); + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, BLUE_LABEL, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = LabeledSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.color.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color.default"); Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); } @@ -176,28 +177,273 @@ void namespaceMatch() { mockClient.secrets().inNamespace(NAMESPACE).create(secret); // different namespace - NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, true); + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE + "nope", LABELS, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = LabeledSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals("secrets.test-secret.default", sourceData.sourceName()); + Assertions.assertEquals("secret.test-secret.default", sourceData.sourceName()); Assertions.assertEquals(Map.of("secretName", "secretValue"), sourceData.sourceData()); } - // needed only to allow access to the super methods - private final static class Dummy extends SecretsPropertySource { + /** + * one secret with name : "blue-secret" and labels "color=blue" is deployed. we search + * it with the same labels, find it, and assert that name of the SourceData (it must + * use its name, not its labels) and values in the SourceData must be prefixed (since + * we have provided an explicit prefix). + */ + @Test + void testWithPrefix() { + Secret secret = new SecretBuilder().withNewMetadata().withName("blue-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("what-color", Base64.getEncoder().encodeToString("blue-color".getBytes())).build(); - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + mockClient.secrets().inNamespace(NAMESPACE).create(secret); + + ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant"); + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, mePrefix, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals("secret.blue-secret.default", sourceData.sourceName()); + Assertions.assertEquals(Map.of("me.what-color", "blue-color"), sourceData.sourceData()); + } + + /** + * two secrets are deployed (name:blue-secret, name:another-blue-secret) and labels + * "color=blue" (on both). we search with the same labels, find them, and assert that + * name of the SourceData (it must use its name, not its labels) and values in the + * SourceData must be prefixed (since we have provided a delayed prefix). + * + * Also notice that the prefix is made up from both secret names. + * + */ + @Test + void testTwoSecretsWithPrefix() { + Secret blueSecret = new SecretBuilder().withNewMetadata().withName("blue-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("first", Base64.getEncoder().encodeToString("blue".getBytes())).build(); + + Secret anotherBlue = new SecretBuilder().withNewMetadata().withName("another-blue-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("second", Base64.getEncoder().encodeToString("blue".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(blueSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(anotherBlue); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); + Assertions.assertEquals(sourceData.sourceName(), "secret.another-blue-secret.blue-secret.default"); + + Map properties = sourceData.sourceData(); + Assertions.assertEquals(2, properties.size()); + Iterator keys = properties.keySet().iterator(); + String firstKey = keys.next(); + String secondKey = keys.next(); + + if (firstKey.contains("first")) { + Assertions.assertEquals(firstKey, "another-blue-secret.blue-secret.first"); } + Assertions.assertEquals(secondKey, "another-blue-secret.blue-secret.second"); + Assertions.assertEquals(properties.get(firstKey), "blue"); + Assertions.assertEquals(properties.get(secondKey), "blue"); + } + + /** + * two secrets are deployed: secret "color-secret" with label: "{color:blue}" and + * "color-secret-k8s" with no labels. We search by "{color:red}", do not find anything + * and thus have an empty SourceData. profile based sources are enabled, but it has no + * effect. + */ + @Test + void searchWithLabelsNoSecretFound() { + Secret colorSecret = new SecretBuilder().withNewMetadata().withName("color-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("one", Base64.getEncoder().encodeToString("1".getBytes())).build(); + + Secret colorSecretK8s = new SecretBuilder().withNewMetadata().withName("color-secret-k8s").endMetadata() + .addToData("two", Base64.getEncoder().encodeToString("2".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecretK8s); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "red"), true, ConfigUtils.Prefix.DEFAULT, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertTrue(sourceData.sourceData().isEmpty()); + Assertions.assertEquals(sourceData.sourceName(), "secret.color.default"); + + } + + /** + * two secrets are deployed: secret "color-secret" with label: "{color:blue}" and + * "shape-secret" with label: "{shape:round}". We search by "{color:blue}" and find + * one secret. profile based sources are enabled, but it has no effect. + */ + @Test + void searchWithLabelsOneSecretFound() { + Secret colorSecret = new SecretBuilder().withNewMetadata().withName("color-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("one", Base64.getEncoder().encodeToString("1".getBytes())).build(); + + Secret shapeSecret = new SecretBuilder().withNewMetadata().withName("shape-secret").endMetadata() + .addToData("two", Base64.getEncoder().encodeToString("2".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(shapeSecret); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DEFAULT, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("one"), "1"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default"); + + } + + /** + * two secrets are deployed: secret "color-secret" with label: "{color:blue}" and + * "color-secret-k8s" with label: "{color:red}". We search by "{color:blue}" and find + * one secret. Since profiles are enabled, we will also be reading "color-secret-k8s", + * even if its labels do not match provided ones. + */ + @Test + void searchWithLabelsOneSecretFoundAndOneFromProfileFound() { + Secret colorSecret = new SecretBuilder().withNewMetadata().withName("color-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("one", Base64.getEncoder().encodeToString("1".getBytes())).build(); + + Secret colorSecretK8s = new SecretBuilder().withNewMetadata().withName("color-secret-k8s") + .withLabels(Collections.singletonMap("color", "red")).endMetadata() + .addToData("two", Base64.getEncoder().encodeToString("2".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecretK8s); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.one"), "1"); + Assertions.assertEquals(sourceData.sourceData().get("color-secret.color-secret-k8s.two"), "2"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.color-secret-k8s.default"); + + } + + /** + *
+	 *     - secret "color-secret" with label "{color:blue}"
+	 *     - secret "shape-secret" with labels "{color:blue, shape:round}"
+	 *     - secret "no-fit" with labels "{tag:no-fit}"
+	 *     - secret "color-secret-k8s" with label "{color:red}"
+	 *     - secret "shape-secret-k8s" with label "{shape:triangle}"
+	 * 
+ */ + @Test + void searchWithLabelsTwoSecretsFoundAndOneFromProfileFound() { + Secret colorSecret = new SecretBuilder().withNewMetadata().withName("color-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("one", Base64.getEncoder().encodeToString("1".getBytes())).build(); + + Secret shapeSecret = new SecretBuilder().withNewMetadata().withName("shape-secret") + .withLabels(Map.of("color", "blue", "shape", "round")).endMetadata() + .addToData("two", Base64.getEncoder().encodeToString("2".getBytes())).build(); + + Secret noFit = new SecretBuilder().withNewMetadata().withName("no-fit").withLabels(Map.of("tag", "no-fit")) + .endMetadata().addToData("three", Base64.getEncoder().encodeToString("3".getBytes())).build(); + + Secret colorSecretK8s = new SecretBuilder().withNewMetadata().withName("color-secret-k8s") + .withLabels(Map.of("color", "red")).endMetadata() + .addToData("four", Base64.getEncoder().encodeToString("4".getBytes())).build(); + + Secret shapeSecretK8s = new SecretBuilder().withNewMetadata().withName("shape-secret-k8s") + .withLabels(Map.of("shape", "triangle")).endMetadata() + .addToData("five", Base64.getEncoder().encodeToString("5".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(shapeSecret); + mockClient.secrets().inNamespace(NAMESPACE).create(noFit); + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecretK8s); + mockClient.secrets().inNamespace(NAMESPACE).create(shapeSecretK8s); + + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 4); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.one"), "1"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.two"), "2"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.four"), "4"); + Assertions.assertEquals( + sourceData.sourceData().get("color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.five"), "5"); + + Assertions.assertEquals(sourceData.sourceName(), + "secret.color-secret.color-secret-k8s.shape-secret.shape-secret-k8s.default"); + + } + + /** + * yaml/properties gets special treatment + */ + @Test + void testYaml() { + Secret colorSecret = new SecretBuilder().withNewMetadata().withName("color-secret") + .withLabels(Collections.singletonMap("color", "blue")).endMetadata() + .addToData("test.yaml", Base64.getEncoder().encodeToString("color: blue".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(colorSecret); + + NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, + Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DEFAULT, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new LabeledSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceData().size(), 1); + Assertions.assertEquals(sourceData.sourceData().get("color"), "blue"); + Assertions.assertEquals(sourceData.sourceName(), "secret.color-secret.default"); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleConfigMapsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleConfigMapsTests.java index 6b54f403a2..535cab7e23 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleConfigMapsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleConfigMapsTests.java @@ -23,7 +23,6 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -42,15 +41,12 @@ properties = { "spring.cloud.bootstrap.name=multiplecms", "spring.main.cloud-platform=KUBERNETES" }) @AutoConfigureWebTestClient @EnableKubernetesMockClient(crud = true, https = false) -public class MultipleConfigMapsTests { - - private static KubernetesClient mockClient; +abstract class MultipleConfigMapsTests { @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleSecretsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleSecretsTests.java index 7e254bcc02..26b2592329 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleSecretsTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/MultipleSecretsTests.java @@ -24,27 +24,15 @@ import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.example3.MultiSecretsApp; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** * @author Haytham Mohamed */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = MultiSecretsApp.class, - properties = { "spring.cloud.bootstrap.name=multiple-secrets", "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -public class MultipleSecretsTests { +abstract class MultipleSecretsTests { private static final String DEFAULT_NAMESPACE = "ns1"; @@ -54,14 +42,10 @@ public class MultipleSecretsTests { private static final String SECRET_VALUE_2 = "secretValue-2"; - // will be injected by KubernetesMockServerExtension - private static KubernetesClient mockClient; - @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { + public static void setUpBeforeClass(KubernetesClient mockClient) { // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java index 3afaa2557c..31896e46b9 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java @@ -29,12 +29,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPrefixContext; -import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySource; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; import org.springframework.mock.env.MockEnvironment; /** @@ -49,6 +47,10 @@ class NamedConfigMapContextToSourceDataProviderTests { private static KubernetesClient mockClient; + private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("some", false, false, "irrelevant"); + + private static final Map COLOR_REALLY_RED = Map.of("color", "really-red"); + @BeforeAll static void beforeAll() { @@ -68,22 +70,24 @@ void afterEach() { } /** - * we have a single config map deployed. it does not match our query. + *
+	 *     one configmap deployed with name "red"
+	 *     we search by name, but for the "blue" one, as such not find it
+	 * 
*/ @Test void noMatch() { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("blue", NAMESPACE, true, "", false); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("blue", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.blue.default"); @@ -92,38 +96,43 @@ void noMatch() { } /** - * we have a single config map deployed. it matches our query. + *
+	 *     one configmap deployed with name "red"
+	 *     we search by name, for the "red" one, as such we find it
+	 * 
*/ @Test void match() { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "", false); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); - Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red")); + Assertions.assertEquals(sourceData.sourceData(), COLOR_REALLY_RED); } /** - * we have two config maps deployed. one matches the query name. the other matches the - * active profile + name, thus is taken also. + *
+	 *     - two configmaps deployed : "red" and "red-with-profile".
+	 *     - "red" is matched directly, "red-with-profile" is matched because we have an active profile
+	 *       "active-profile"
+	 * 
*/ @Test void matchIncludeSingleProfile() { ConfigMap red = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); ConfigMap redWithProfile = new ConfigMapBuilder().withNewMetadata().withName("red-with-profile").endMetadata() .addToData("taste", "mango").build(); @@ -134,12 +143,11 @@ void matchIncludeSingleProfile() { // add one more profile and specify that we want profile based config maps MockEnvironment env = new MockEnvironment(); env.setActiveProfiles("with-profile"); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "", true); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -150,16 +158,19 @@ void matchIncludeSingleProfile() { } /** - * we have two config maps deployed. one matches the query name. the other matches the - * active profile + name, thus is taken also. This takes into consideration the - * prefix, that we explicitly specify. Notice that prefix works for profile based - * config maps as well. + *
+	*     - two configmaps deployed : "red" and "red-with-profile".
+	*     - "red" is matched directly, "red-with-profile" is matched because we have an active profile
+	*       "active-profile"
+	*     -  This takes into consideration the prefix, that we explicitly specify.
+	*        Notice that prefix works for profile based config maps as well.
+	* 
*/ @Test void matchIncludeSingleProfileWithPrefix() { ConfigMap red = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); ConfigMap redWithProfile = new ConfigMapBuilder().withNewMetadata().withName("red-with-profile").endMetadata() .addToData("taste", "mango").build(); @@ -171,12 +182,11 @@ void matchIncludeSingleProfileWithPrefix() { // also append prefix MockEnvironment env = new MockEnvironment(); env.setActiveProfiles("with-profile"); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "some", true); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, PREFIX, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -187,16 +197,18 @@ void matchIncludeSingleProfileWithPrefix() { } /** - * we have three config maps deployed. one matches the query name. the other two match - * the active profile + name, thus are taken also. This takes into consideration the - * prefix, that we explicitly specify. Notice that prefix works for profile based - * config maps as well. + *
+	 *     - three configmaps deployed : "red", "red-with-taste" and "red-with-shape"
+	 *     - "red" is matched directly, the other two are matched because of active profiles
+	 *     -  This takes into consideration the prefix, that we explicitly specify.
+	 *        Notice that prefix works for profile based config maps as well.
+	 * 
*/ @Test void matchIncludeTwoProfilesWithPrefix() { ConfigMap red = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); ConfigMap redWithTaste = new ConfigMapBuilder().withNewMetadata().withName("red-with-taste").endMetadata() .addToData("taste", "mango").build(); @@ -212,15 +224,14 @@ void matchIncludeTwoProfilesWithPrefix() { // also append prefix MockEnvironment env = new MockEnvironment(); env.setActiveProfiles("with-taste", "with-shape"); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, "some", true); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, PREFIX, true); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-taste.red-with-shape.default"); + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-shape.red-with-taste.default"); Assertions.assertEquals(sourceData.sourceData().size(), 3); Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); @@ -228,22 +239,24 @@ void matchIncludeTwoProfilesWithPrefix() { } - // this test makes sure that even if NormalizedSource has no name (which is a valid - // case for config maps), - // it will default to "application" and such a config map will be read. + /** + *
+	 * 		proves that an implicit configmap is going to be generated and read, even if
+	 * 	    we did not provide one
+	 * 
+ */ @Test - void matchWithoutName() { + void matchWithName() { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("application").endMetadata() .addToData("color", "red").build(); mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource(null, NAMESPACE, true, "", false); + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("application", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.application.default"); @@ -251,52 +264,80 @@ void matchWithoutName() { } /** - * NamedSecretContextToSourceDataProvider gets as input a Fabric8ConfigContext. This - * context has a namespace as well as a NormalizedSource, that has a namespace too. It - * is easy to get confused in code on which namespace to use. This test makes sure - * that we use the proper one. + *
+	 *     - NamedSecretContextToSourceDataProvider gets as input a KubernetesClientConfigContext
+	 *     - This context has a namespace as well as a NormalizedSource, that has a namespace too.
+	 *     - This test makes sure that we use the proper one.
+	 * 
*/ @Test void namespaceMatch() { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() - .addToData("color", "really-red").build(); + .addToData(COLOR_REALLY_RED).build(); mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); - // different namespace - NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE + "nope", true, "", - false); + String wrongNamespace = NAMESPACE + "nope"; + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", wrongNamespace, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider - .of(Dummy::processEntries, Dummy::sourceName, Dummy::prefix).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("color", "really-red")); } - // needed only to allow access to the super methods - private static final class Dummy extends ConfigMapPropertySource { + /** + *
+	 *     - proves that single yaml file gets special treatment
+	 * 
+ */ + @Test + void testSingleYaml() { + ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("red").endMetadata() + .addToData("single.yaml", "key: value").build(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("red", NAMESPACE, true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "configmap.red.default"); + Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("key", "value")); + } - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + /** + *
+	 *     - one configmap is deployed with name "one"
+	 *     - profile is enabled with name "k8s"
+	 *
+	 *     we assert that the name of the source is "one" and does not contain "one-dev"
+	 * 
+ */ + @Test + void testCorrectNameWithProfile() { + ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("one").endMetadata() + .addToData("key", "value").build(); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); - } + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); - private static Map processEntries(Map map, Environment environment) { - return processAllEntries(map, environment); - } + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("one", NAMESPACE, true, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); - private static SourceData prefix(ConfigMapPrefixContext context) { - return withPrefix(context); - } + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + Assertions.assertEquals(sourceData.sourceName(), "configmap.one.default"); + Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("key", "value")); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java index 9ea37d3a81..bcfa758ca5 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProviderTests.java @@ -30,9 +30,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; import org.springframework.cloud.kubernetes.commons.config.NormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySource; import org.springframework.cloud.kubernetes.commons.config.SourceData; import org.springframework.mock.env.MockEnvironment; @@ -48,6 +48,8 @@ class NamedSecretContextToSourceDataProviderTests { private static KubernetesClient mockClient; + private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("some", false, false, "irrelevant"); + @BeforeAll static void beforeAll() { @@ -77,14 +79,14 @@ void singleSecretMatchAgainstLabels() { mockClient.secrets().inNamespace(NAMESPACE).create(secret); - NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } @@ -103,20 +105,20 @@ void twoSecretMatchAgainstLabels() { .addToData("color", Base64.getEncoder().encodeToString("really-blue".getBytes())).build(); Secret yellow = new SecretBuilder().withNewMetadata().withName("yellow").endMetadata() - .addToData("color", Base64.getEncoder().encodeToString("really-yeallow".getBytes())).build(); + .addToData("color", Base64.getEncoder().encodeToString("really-yellow".getBytes())).build(); mockClient.secrets().inNamespace(NAMESPACE).create(red); mockClient.secrets().inNamespace(NAMESPACE).create(blue); mockClient.secrets().inNamespace(NAMESPACE).create(yellow); - NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData().size(), 1); Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); @@ -133,14 +135,14 @@ void testSecretNoMatch() { mockClient.secrets().inNamespace(NAMESPACE).create(pink); - NormalizedSource normalizedSource = new NamedSecretNormalizedSource("blue", NAMESPACE, true); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("blue", NAMESPACE, true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.blue.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.blue.default"); Assertions.assertEquals(sourceData.sourceData(), Collections.emptyMap()); } @@ -159,28 +161,149 @@ void namespaceMatch() { mockClient.secrets().inNamespace(NAMESPACE).create(secret); // different namespace - NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", true); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE + "nope", true, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedSecretContextToSourceDataProvider.of(Dummy::sourceName).get(); + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); - Assertions.assertEquals(sourceData.sourceName(), "secrets.red.default"); + Assertions.assertEquals(sourceData.sourceName(), "secret.red.default"); Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } - // needed only to allow access to the super methods - private static final class Dummy extends SecretsPropertySource { + /** + * we have two secrets deployed. one matches the query name. the other matches the + * active profile + name, thus is taken also. + */ + @Test + void matchIncludeSingleProfile() { + + Secret red = new SecretBuilder().withNewMetadata().withName("red").endMetadata() + .addToData("color", Base64.getEncoder().encodeToString("really-red".getBytes())).build(); + + Secret redWithProfile = new SecretBuilder().withNewMetadata().withName("red-with-profile").endMetadata() + .addToData("taste", Base64.getEncoder().encodeToString("mango".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(red); + mockClient.secrets().inNamespace(NAMESPACE).create(redWithProfile); + + // add one more profile and specify that we want profile based config maps + MockEnvironment env = new MockEnvironment(); + env.setActiveProfiles("with-profile"); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); + + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("taste"), "mango"); + + } + + /** + * we have two secrets deployed. one matches the query name. the other matches the + * active profile + name, thus is taken also. This takes into consideration the + * prefix, that we explicitly specify. Notice that prefix works for profile based + * secrets as well. + */ + @Test + void matchIncludeSingleProfileWithPrefix() { + + Secret red = new SecretBuilder().withNewMetadata().withName("red").endMetadata() + .addToData("color", Base64.getEncoder().encodeToString("really-red".getBytes())).build(); + + Secret redWithProfile = new SecretBuilder().withNewMetadata().withName("red-with-profile").endMetadata() + .addToData("taste", Base64.getEncoder().encodeToString("mango".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(red); + mockClient.secrets().inNamespace(NAMESPACE).create(redWithProfile); + + // add one more profile and specify that we want profile based config maps + // also append prefix + MockEnvironment env = new MockEnvironment(); + env.setActiveProfiles("with-profile"); - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - private static String sourceName(String name, String namespace) { - return getSourceName(name, namespace); - } + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-profile.default"); + Assertions.assertEquals(sourceData.sourceData().size(), 2); + Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); + + } + + /** + * we have three secrets deployed. one matches the query name. the other two match the + * active profile + name, thus are taken also. This takes into consideration the + * prefix, that we explicitly specify. Notice that prefix works for profile based + * config maps as well. + */ + @Test + void matchIncludeTwoProfilesWithPrefix() { + + Secret red = new SecretBuilder().withNewMetadata().withName("red").endMetadata() + .addToData("color", Base64.getEncoder().encodeToString("really-red".getBytes())).build(); + + Secret redWithTaste = new SecretBuilder().withNewMetadata().withName("red-with-taste").endMetadata() + .addToData("taste", Base64.getEncoder().encodeToString("mango".getBytes())).build(); + + Secret redWithShape = new SecretBuilder().withNewMetadata().withName("red-with-shape").endMetadata() + .addToData("shape", Base64.getEncoder().encodeToString("round".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(red); + mockClient.secrets().inNamespace(NAMESPACE).create(redWithTaste); + mockClient.secrets().inNamespace(NAMESPACE).create(redWithShape); + + // add one more profile and specify that we want profile based config maps + // also append prefix + MockEnvironment env = new MockEnvironment(); + env.setActiveProfiles("with-taste", "with-shape"); + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("red", NAMESPACE, true, PREFIX, true); + + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); + + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + + Assertions.assertEquals(sourceData.sourceName(), "secret.red.red-with-shape.red-with-taste.default"); + + Assertions.assertEquals(sourceData.sourceData().size(), 3); + Assertions.assertEquals(sourceData.sourceData().get("some.color"), "really-red"); + Assertions.assertEquals(sourceData.sourceData().get("some.taste"), "mango"); + Assertions.assertEquals(sourceData.sourceData().get("some.shape"), "round"); + + } + + /** + *
+	 *     - proves that single yaml file gets special treatment
+	 * 
+ */ + @Test + void testSingleYaml() { + Secret secret = new SecretBuilder().withNewMetadata().withName("single-yaml").endMetadata() + .addToData("single.yaml", Base64.getEncoder().encodeToString("key: value".getBytes())).build(); + + mockClient.secrets().inNamespace(NAMESPACE).create(secret); + + // different namespace + NormalizedSource normalizedSource = new NamedSecretNormalizedSource("single-yaml", NAMESPACE, true, false); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, + new MockEnvironment()); + + Fabric8ContextToSourceData data = new NamedSecretContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(context); + Assertions.assertEquals(sourceData.sourceName(), "secret.single-yaml.default"); + Assertions.assertEquals(sourceData.sourceData(), Collections.singletonMap("key", "value")); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BoostrapKubernetesEnabledConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BoostrapKubernetesEnabledConfigDisabled.java new file mode 100644 index 0000000000..2b82fcf125 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BoostrapKubernetesEnabledConfigDisabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +class BoostrapKubernetesEnabledConfigDisabled extends KubernetesEnabledConfigDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesDisabled.java new file mode 100644 index 0000000000..21a4d384c9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesDisabled.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.bootstrap.enabled=true" }) +class BootstrapKubernetesDisabled extends KubernetesDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabled.java new file mode 100644 index 0000000000..42fe2aa992 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +class BootstrapKubernetesEnabled extends KubernetesEnabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledOnPurpose.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledOnPurpose.java new file mode 100644 index 0000000000..9091f57814 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledOnPurpose.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=true", + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +class BootstrapKubernetesEnabledOnPurpose extends KubernetesEnabledOnPurpose { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsAndConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsAndConfigDisabled.java new file mode 100644 index 0000000000..dcb58982eb --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsAndConfigDisabled.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.config.enabled=false", + "spring.cloud.bootstrap.enabled=true" }) +class BootstrapKubernetesEnabledSecretsAndConfigDisabled extends KubernetesEnabledSecretsAndConfigDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsDisabled.java new file mode 100644 index 0000000000..c8f369f093 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/BootstrapKubernetesEnabledSecretsDisabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +class BootstrapKubernetesEnabledSecretsDisabled extends KubernetesEnabledSecretsDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesDisabled.java new file mode 100644 index 0000000000..e71c94627a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesDisabled.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.main.cloud-platform=KUBERNETES" }) +class ConfigDataKubernetesDisabled extends KubernetesDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabled.java new file mode 100644 index 0000000000..b385dc0ba4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +class ConfigDataKubernetesEnabled extends KubernetesEnabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledConfigDisabled.java new file mode 100644 index 0000000000..01a23b94c1 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledConfigDisabled.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +class ConfigDataKubernetesEnabledConfigDisabled extends KubernetesEnabledConfigDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledOnPurpose.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledOnPurpose.java new file mode 100644 index 0000000000..98c019d37d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledOnPurpose.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=true", + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +class ConfigDataKubernetesEnabledOnPurpose extends KubernetesEnabledOnPurpose { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsAndConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsAndConfigDisabled.java new file mode 100644 index 0000000000..25575eca2c --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsAndConfigDisabled.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.config.enabled=false", + "spring.config.import=kubernetes:", "spring.main.cloud-platform=KUBERNETES" }) +class ConfigDataKubernetesEnabledSecretsAndConfigDisabled extends KubernetesEnabledSecretsAndConfigDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsDisabled.java new file mode 100644 index 0000000000..e375762c2e --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/ConfigDataKubernetesEnabledSecretsDisabled.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.bootstrap; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, + properties = { "spring.cloud.kubernetes.secrets.enabled=false", + "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }) +class ConfigDataKubernetesEnabledSecretsDisabled extends KubernetesEnabledSecretsDisabled { + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/Fabric8BootstrapConfigurationInsideK8s.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/Fabric8BootstrapConfigurationInsideK8s.java index e00146305a..2f8265cda3 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/Fabric8BootstrapConfigurationInsideK8s.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/Fabric8BootstrapConfigurationInsideK8s.java @@ -31,7 +31,8 @@ * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=abc" }) + properties = { "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.client.namespace=abc", + "spring.cloud.bootstrap.enabled=true" }) class Fabric8BootstrapConfigurationInsideK8s { @Autowired diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesDisabled.java index 08b21f1973..eb757e4b36 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesDisabled.java @@ -19,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; @@ -30,9 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = "spring.cloud.kubernetes.enabled=false") -class KubernetesDisabled { +abstract class KubernetesDisabled { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabled.java index c8ba70e0f5..8fd7f739cf 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabled.java @@ -19,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; @@ -30,9 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) -class KubernetesEnabled { +abstract class KubernetesEnabled { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledConfigDisabled.java index d438ed1ed5..a9a2e69721 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledConfigDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledConfigDisabled.java @@ -19,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; @@ -30,9 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.config.enabled=false", "spring.main.cloud-platform=KUBERNETES" }) -class KubernetesEnabledConfigDisabled { +abstract class KubernetesEnabledConfigDisabled { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledOnPurpose.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledOnPurpose.java index 70b75f936b..bdc2776a91 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledOnPurpose.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledOnPurpose.java @@ -19,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; @@ -30,10 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.secrets.enabled=true", - "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) -class KubernetesEnabledOnPurpose { +abstract class KubernetesEnabledOnPurpose { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsAndConfigDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsAndConfigDisabled.java index 7a50d8e3e8..2c9519e684 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsAndConfigDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsAndConfigDisabled.java @@ -19,17 +19,13 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = { - "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.kubernetes.config.enabled=false" }) -class KubernetesEnabledSecretsAndConfigDisabled { +abstract class KubernetesEnabledSecretsAndConfigDisabled { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsDisabled.java index ae4763d56a..9fa115a992 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/bootstrap/KubernetesEnabledSecretsDisabled.java @@ -19,8 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; import org.springframework.context.ConfigurableApplicationContext; @@ -30,10 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, - properties = { "spring.cloud.kubernetes.secrets.enabled=false", - "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }) -class KubernetesEnabledSecretsDisabled { +abstract class KubernetesEnabledSecretsDisabled { @Autowired private ConfigurableApplicationContext context; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java new file mode 100644 index 0000000000..2d564ccff8 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class, Four.class }) +public class LabeledConfigMapWithPrefixApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledConfigMapWithPrefixApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..2e5cca4019 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-prefix", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledConfigMapWithPrefixBootstrapTests extends LabeledConfigMapWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..15c13eaf05 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithPrefixApp.class, + properties = { "spring.application.name=labeled-configmap-with-prefix", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-configmap-with-prefix.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledConfigMapWithPrefixConfigDataTests extends LabeledConfigMapWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java new file mode 100644 index 0000000000..18de2b4302 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix; + +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class LabeledConfigMapWithPrefixTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + LabeledConfigMapWithPrefixTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = Collections.singletonMap("one.property", "one"); + createConfigMap("configmap-one", one, Collections.singletonMap("letter", "a")); + + Map two = Collections.singletonMap("property", "two"); + createConfigMap("configmap-two", two, Collections.singletonMap("letter", "b")); + + Map three = Collections.singletonMap("property", "three"); + createConfigMap("configmap-three", three, Collections.singletonMap("letter", "c")); + + Map four = Collections.singletonMap("property", "four"); + createConfigMap("configmap-four", four, Collections.singletonMap("letter", "d")); + + } + + private static void createConfigMap(String name, Map data, Map labels) { + mockClient.configMaps().inNamespace("spring-k8s").create(new ConfigMapBuilder().withNewMetadata().withName(name) + .withLabels(labels).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.configmap.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.configmap.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-configmap/prefix/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-configmap/prefix/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[2].labels=letter:c'
+	 * 	 ("property", "three")
+	 *
+	 *   We find the configmap by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "configmap-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-configmap/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[3].labels=letter:d'
+	 * 	 ("property", "four")
+	 *
+	 *   We find the configmap by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "configmap-four")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-configmap/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java new file mode 100644 index 0000000000..3a250a84c3 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledConfigMapWithPrefixController { + + private final One one; + + private final Two two; + + private final Three three; + + private final Four four; + + public LabeledConfigMapWithPrefixController(One one, Two two, Three three, Four four) { + this.one = one; + this.two = two; + this.three = three; + this.four = four; + } + + @GetMapping("/labeled-configmap/prefix/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/three") + public String three() { + return three.getProperty(); + } + + @GetMapping("/labeled-configmap/prefix/four") + public String four() { + return four.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Four.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Four.java new file mode 100644 index 0000000000..7b74ea90c9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Four.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "configmap-four") +public class Four { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/One.java similarity index 89% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/One.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/One.java index af88a5c0f8..d993255b6a 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/One.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/One.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Three.java new file mode 100644 index 0000000000..fb2d925e01 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "configmap-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Two.java similarity index 89% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Two.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Two.java index c14b1b0f40..160d8bdd72 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Two.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Two.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java new file mode 100644 index 0000000000..ca4b2d1c0a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties.Green; + +@SpringBootApplication +@EnableConfigurationProperties({ Blue.class, Green.class }) +public class LabeledConfigMapWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledConfigMapWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java new file mode 100644 index 0000000000..bda9a6daf2 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-profile", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledConfigMapWithProfileBootstrapTests extends LabeledConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java new file mode 100644 index 0000000000..4a49edacc0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = LabeledConfigMapWithProfileApp.class, + properties = { "spring.application.name=labeled-configmap-with-profile", + "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-configmap-with-profile.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledConfigMapWithProfileConfigDataTests extends LabeledConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java new file mode 100644 index 0000000000..a3915d0cce --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile; + +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class LabeledConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + /** + *
+	 *     - configmap with name "color-configmap", with labels: "{color: blue}" and "explicitPrefix: blue"
+	 *     - configmap with name "green-configmap", with labels: "{color: green}" and "explicitPrefix: blue-again"
+	 *     - configmap with name "red-configmap", with labels "{color: not-red}" and "useNameAsPrefix: true"
+	 *     - configmap with name "yellow-configmap" with labels "{color: not-yellow}" and useNameAsPrefix: true
+	 *     - configmap with name "color-configmap-k8s", with labels : "{color: not-blue}"
+	 *     - configmap with name "green-configmap-k8s", with labels : "{color: green-k8s}"
+	 *     - configmap with name "green-configmap-prod", with labels : "{color: green-prod}"
+	 * 
+ */ + static void setUpBeforeClass(KubernetesClient mockClient) { + LabeledConfigMapWithProfileTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + // is found by labels + Map colorConfigMap = Collections.singletonMap("one", "1"); + createConfigMap("color-configmap", colorConfigMap, Collections.singletonMap("color", "blue")); + + // is not taken, since "profileSpecificSources=false" for the above + Map colorConfigMapK8s = Collections.singletonMap("five", "5"); + createConfigMap("color-configmap-k8s", colorConfigMapK8s, Collections.singletonMap("color", "not-blue")); + + // is found by labels + Map greenConfigMap = Collections.singletonMap("two", "2"); + createConfigMap("green-configmap", greenConfigMap, Collections.singletonMap("color", "green")); + + // is taken because k8s profile is active and "profileSpecificSources=true" + Map shapeConfigMapK8s = Collections.singletonMap("six", "6"); + createConfigMap("green-configmap-k8s", shapeConfigMapK8s, Collections.singletonMap("color", "green-k8s")); + + // is taken because prod profile is active and "profileSpecificSources=true" + Map shapeConfigMapProd = Collections.singletonMap("seven", "7"); + createConfigMap("green-configmap-prod", shapeConfigMapProd, Collections.singletonMap("color", "green-prod")); + + // not taken + Map redConfigMap = Collections.singletonMap("three", "3"); + createConfigMap("red-configmap", redConfigMap, Collections.singletonMap("color", "not-red")); + + // not taken + Map yellowConfigMap = Collections.singletonMap("four", "4"); + createConfigMap("yellow-configmap", yellowConfigMap, Collections.singletonMap("color", "not-yellow")); + + } + + private static void createConfigMap(String name, Map data, Map labels) { + mockClient.configMaps().inNamespace("spring-k8s").create(new ConfigMapBuilder().withNewMetadata().withName(name) + .withLabels(labels).endMetadata().addToData(data).build()); + } + + /** + *
+	 *     this one is taken from : "blue.one". We find "color-configmap" by labels, and
+	 *     "color-configmap-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
+	 *     Since "explicitPrefix=blue", we take "blue.one"
+	 * 
+ */ + @Test + void testBlue() { + this.webClient.get().uri("/labeled-configmap/profile/blue").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("1")); + } + + /** + *
+	 *   this one is taken from : "green-configmap.green-configmap-k8s.green-configmap-prod".
+	 *   We find "green-configmap" by labels, also "green-configmap-k8s" and "green-configmap-prod" exists,
+	 *   because "includeProfileSpecificSources=true" is set.
+	 * 
+ */ + @Test + void testGreen() { + this.webClient.get().uri("/labeled-configmap/profile/green").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java new file mode 100644 index 0000000000..6f9818d106 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties.Green; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledConfigMapWithProfileController { + + private final Blue blue; + + private final Green green; + + public LabeledConfigMapWithProfileController(Blue blue, Green green) { + this.blue = blue; + this.green = green; + } + + @GetMapping("/labeled-configmap/profile/blue") + public String blue() { + return blue.getOne(); + } + + @GetMapping("/labeled-configmap/profile/green") + public String green() { + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Blue.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Blue.java new file mode 100644 index 0000000000..40ad433078 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Blue.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("blue") +public class Blue { + + private String one; + + public String getOne() { + return one; + } + + public void setOne(String one) { + this.one = one; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java new file mode 100644 index 0000000000..0c34cbe1a5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("green-configmap.green-configmap-k8s.green-configmap-prod") +public class Green { + + private String two; + + private String six; + + private String seven; + + public String getTwo() { + return two; + } + + public void setTwo(String two) { + this.two = two; + } + + public String getSix() { + return six; + } + + public void setSix(String six) { + this.six = six; + } + + public String getSeven() { + return seven; + } + + public void setSeven(String seven) { + this.seven = seven; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java new file mode 100644 index 0000000000..fa71ec9cb8 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class, Four.class }) +public class LabeledSecretWithPrefixApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledSecretWithPrefixApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..5567b60432 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixBootstrapTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-secret-with-prefix", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledSecretWithPrefixBootstrapTests extends LabeledSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..d21095aaa5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class, + properties = { "spring.application.name=labeled-secret-with-prefix", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-secret-with-prefix.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledSecretWithPrefixConfigDataTests extends LabeledSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java new file mode 100644 index 0000000000..a43164f429 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class LabeledSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + LabeledSecretWithPrefixTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = Collections.singletonMap("one.property", + Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-one", one, Collections.singletonMap("letter", "a")); + + Map two = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("two".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-two", two, Collections.singletonMap("letter", "b")); + + Map three = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("three".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-three", three, Collections.singletonMap("letter", "c")); + + Map four = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("four".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-four", four, Collections.singletonMap("letter", "d")); + + } + + private static void createSecret(String name, Map data, Map labels) { + mockClient.secrets().inNamespace("spring-k8s").create(new SecretBuilder().withNewMetadata().withName(name) + .withLabels(labels).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/labeled-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/labeled-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].labels=letter:c'
+	 * 	 ("property", "three")
+	 *
+	 *   We find the secret by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/labeled-secret/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[3].labels=letter:d'
+	 * 	 ("property", "four")
+	 *
+	 *   We find the secret by labels, and use it's name as the prefix.
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-four")
+	 * 
+ */ + @Test + void testFour() { + this.webClient.get().uri("/labeled-secret/prefix/four").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("four")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java new file mode 100644 index 0000000000..ff10bfe3ab --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/controller/LabeledSecretWithPrefixController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Four; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledSecretWithPrefixController { + + private final One one; + + private final Two two; + + private final Three three; + + private final Four four; + + public LabeledSecretWithPrefixController(One one, Two two, Three three, Four four) { + this.one = one; + this.two = two; + this.three = three; + this.four = four; + } + + @GetMapping("/labeled-secret/prefix/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/three") + public String three() { + return three.getProperty(); + } + + @GetMapping("/labeled-secret/prefix/four") + public String four() { + return four.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java new file mode 100644 index 0000000000..536b2744b5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Four.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-four") +public class Four { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java new file mode 100644 index 0000000000..f14774f467 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java new file mode 100644 index 0000000000..251f0f3ee0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java new file mode 100644 index 0000000000..a4a70d2c1d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileApp.java new file mode 100644 index 0000000000..29b5e311d6 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileApp.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties.Green; + +@SpringBootApplication +@EnableConfigurationProperties({ Blue.class, Green.class }) +public class LabeledSecretWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(LabeledSecretWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java new file mode 100644 index 0000000000..972e78e63f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-secret-with-profile", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.config.enabled=false" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledSecretWithProfileBootstrapTests extends LabeledSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java new file mode 100644 index 0000000000..c2e5054909 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles({ "k8s", "prod" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithProfileApp.class, + properties = { "spring.application.name=labeled-secret-with-profile", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./labeled-secret-with-profile.yaml", + "spring.cloud.kubernetes.config.enabled=false" }) +@EnableKubernetesMockClient(crud = true, https = false) +class LabeledSecretWithProfileConfigDataTests extends LabeledSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java new file mode 100644 index 0000000000..f2b8a22c87 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class LabeledSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + /* + *
 - secret with name "color-secret", with labels: "{color: blue}" and
+	 * "explicitPrefix: blue" - secret with name "green-secret", with labels:
+	 * "{color: green}" and "explicitPrefix: blue-again" - secret with name "red-secret",
+	 * with labels "{color: not-red}" and "useNameAsPrefix: true" - secret with name
+	 * "yellow-secret" with labels "{color: not-yellow}" and useNameAsPrefix: true -
+	 * secret with name "color-secret-k8s", with labels : "{color: not-blue}" - secret
+	 * with name "green-secret-k8s", with labels : "{color: green-k8s}" - secret with name
+	 * "green-secret-prod", with labels : "{color: green-prod}" 
+ */ + static void setUpBeforeClass(KubernetesClient mockClient) { + LabeledSecretWithProfileTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + // is found by labels + Map colorSecret = Collections.singletonMap("one", + Base64.getEncoder().encodeToString("1".getBytes(StandardCharsets.UTF_8))); + createSecret("color-secret", colorSecret, Collections.singletonMap("color", "blue")); + + // is not taken, since "profileSpecificSources=false" for the above + Map colorSecretK8s = Collections.singletonMap("five", + Base64.getEncoder().encodeToString("5".getBytes(StandardCharsets.UTF_8))); + createSecret("color-secret-k8s", colorSecretK8s, Collections.singletonMap("color", "not-blue")); + + // is found by labels + Map greenSecret = Collections.singletonMap("two", + Base64.getEncoder().encodeToString("2".getBytes(StandardCharsets.UTF_8))); + createSecret("green-secret", greenSecret, Collections.singletonMap("color", "green")); + + // is taken because k8s profile is active and "profileSpecificSources=true" + Map shapeSecretK8s = Collections.singletonMap("six", + Base64.getEncoder().encodeToString("6".getBytes(StandardCharsets.UTF_8))); + createSecret("green-secret-k8s", shapeSecretK8s, Collections.singletonMap("color", "green-k8s")); + + // // is taken because prod profile is active and "profileSpecificSources=true" + Map shapeSecretProd = Collections.singletonMap("seven", + Base64.getEncoder().encodeToString("7".getBytes(StandardCharsets.UTF_8))); + createSecret("green-secret-prod", shapeSecretProd, Collections.singletonMap("color", "green-prod")); + + // not taken + Map redSecret = Collections.singletonMap("three", + Base64.getEncoder().encodeToString("3".getBytes(StandardCharsets.UTF_8))); + createSecret("red-secret", redSecret, Collections.singletonMap("color", "not-red")); + + // not taken + Map yellowSecret = Collections.singletonMap("four", + Base64.getEncoder().encodeToString("4".getBytes(StandardCharsets.UTF_8))); + createSecret("yellow-secret", yellowSecret, Collections.singletonMap("color", "not-yellow")); + + } + + private static void createSecret(String name, Map data, Map labels) { + mockClient.secrets().inNamespace("spring-k8s").create(new SecretBuilder().withNewMetadata().withName(name) + .withLabels(labels).endMetadata().addToData(data).build()); + } + + /** + *
+	 *     this one is taken from : "blue.one". We find "color-secret" by labels, and
+	 *     "color-secrets-k8s" exists, but "includeProfileSpecificSources=false", thus not taken.
+	 *     Since "explicitPrefix=blue", we take "blue.one"
+	 * 
+ */ + @Test + void testBlue() { + this.webClient.get().uri("/labeled-secret/profile/blue").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("1")); + } + + /** + *
+	 *   this one is taken from : "green-secret.green-secret-k8s.green-secret-prod".
+	 *   We find "green-secret" by labels, also "green-secrets-k8s" and "green-secrets-prod" exists,
+	 *   because "includeProfileSpecificSources=true" is set.
+	 * 
+ */ + @Test + void testGreen() { + this.webClient.get().uri("/labeled-secret/profile/green").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("2#6#7")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java new file mode 100644 index 0000000000..8464aec5a5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties.Blue; +import org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties.Green; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LabeledSecretWithProfileController { + + private final Blue blue; + + private final Green green; + + public LabeledSecretWithProfileController(Blue blue, Green green) { + this.blue = blue; + this.green = green; + } + + @GetMapping("/labeled-secret/profile/blue") + public String blue() { + return blue.getOne(); + } + + @GetMapping("/labeled-secret/profile/green") + public String green() { + return green.getTwo() + "#" + green.getSix() + "#" + green.getSeven(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Blue.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Blue.java new file mode 100644 index 0000000000..7f1b9aacea --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Blue.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("blue") +public class Blue { + + private String one; + + public String getOne() { + return one; + } + + public void setOne(String one) { + this.one = one; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java new file mode 100644 index 0000000000..1d08459dcf --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.labeled_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("green-secret.green-secret-k8s.green-secret-prod") +public class Green { + + private String two; + + private String six; + + private String seven; + + public String getTwo() { + return two; + } + + public void setTwo(String two) { + this.two = two; + } + + public String getSix() { + return six; + } + + public void setSix(String six) { + this.six = six; + } + + public String getSeven() { + return seven; + } + + public void setSeven(String seven) { + this.seven = seven; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigFailFastDisabled.java new file mode 100644 index 0000000000..d9f4071ad5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigFailFastDisabled.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BoostrapConfigFailFastDisabled extends ConfigFailFastDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryDisabledButSecretsRetryEnabled.java new file mode 100644 index 0000000000..cb482ea690 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryDisabledButSecretsRetryEnabled.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BoostrapConfigRetryDisabledButSecretsRetryEnabled extends ConfigRetryDisabledButSecretsRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @Override + protected void assertRetryBean(ApplicationContext context) { + assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isTrue(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryEnabled.java new file mode 100644 index 0000000000..a29eef0fcc --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BoostrapConfigRetryEnabled.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BoostrapConfigRetryEnabled extends ConfigRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @SpyBean + Fabric8ConfigMapPropertySourceLocator propertySourceLocator; + + @BeforeEach + public void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = propertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..4ac5c75c1b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/BootstrapConfigFailFastEnabledButRetryDisabled.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * we call Fabric8ConfigMapPropertySourceLocator::locate directly, thus no need for + * bootstrap phase to kick in. As such two flags that might look a bit un-expected: + * "spring.cloud.kubernetes.config.enabled=false" + * "spring.cloud.kubernetes.secrets.enabled=false" + * + * @author Isik Erhan + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.config.enabled=false", + "spring.cloud.kubernetes.secrets.enabled=false", "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BootstrapConfigFailFastEnabledButRetryDisabled extends ConfigFailFastEnabledButRetryDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastDisabled.java new file mode 100644 index 0000000000..becc6a23d2 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastDisabled.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataConfigFailFastDisabled extends ConfigFailFastDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..faffb9cb46 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigFailFastEnabledButRetryDisabled.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +/** + * we call Fabric8ConfigMapPropertySourceLocator::locate directly, thus no need for + * bootstrap phase to kick in. As such two flags that might look a bit un-expected: + * "spring.cloud.kubernetes.config.enabled=false" + * "spring.cloud.kubernetes.secrets.enabled=false" + * + * @author Isik Erhan + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.config.enabled=false", + "spring.cloud.kubernetes.secrets.enabled=false", "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataConfigFailFastEnabledButRetryDisabled extends ConfigFailFastEnabledButRetryDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @MockBean + KubernetesNamespaceProvider kubernetesNamespaceProvider; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java new file mode 100644 index 0000000000..0832b42f81 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryDisabledButSecretsRetryEnabled.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableSecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataConfigRetryDisabledButSecretsRetryEnabled extends ConfigRetryDisabledButSecretsRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @Override + protected void assertRetryBean(ApplicationContext context) { + assertThat(context.getBean(ConfigDataRetryableSecretsPropertySourceLocator.class)).isNotNull(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryEnabled.java new file mode 100644 index 0000000000..6390e21328 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigDataConfigRetryEnabled.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cloud.kubernetes.commons.KubernetesNamespaceProvider; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +import static org.mockito.Mockito.spy; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataConfigRetryEnabled extends ConfigRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @MockBean + private KubernetesNamespaceProvider namespaceProvider; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @Autowired + ConfigDataRetryableConfigMapPropertySourceLocator propertySourceLocator; + + @BeforeEach + public void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = spy(propertySourceLocator.getConfigMapPropertySourceLocator()); + propertySourceLocator.setConfigMapPropertySourceLocator(verifiablePsl); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastDisabled.java index 37b57207b0..2e1c754b1b 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastDisabled.java @@ -18,30 +18,23 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.mock.env.MockEnvironment; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class ConfigFailFastDisabled { +abstract class ConfigFailFastDisabled { private static final String API = "/api/v1/namespaces/default/configmaps/application"; @@ -49,8 +42,12 @@ class ConfigFailFastDisabled { private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + @Autowired + private Fabric8ConfigMapPropertySourceLocator propertySourceLocator; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + ConfigFailFastDisabled.mockClient = mockClient; + ConfigFailFastDisabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -59,17 +56,15 @@ static void setup() { System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); } - @SpyBean - private Fabric8ConfigMapPropertySourceLocator propertySourceLocator; - @Test void locateShouldNotRetry() { + Fabric8ConfigMapPropertySourceLocator psl = spy(propertySourceLocator); mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); - Assertions.assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify that propertySourceLocator.locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(psl, times(1)).locate(any()); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastEnabledButRetryDisabled.java index 7cf2b960f7..ec78e9b90f 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastEnabledButRetryDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastEnabledButRetryDisabled.java @@ -18,15 +18,11 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.context.ApplicationContext; import org.springframework.mock.env.MockEnvironment; @@ -46,14 +42,7 @@ * @author Isik Erhan * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", - "spring.main.cloud-platform=KUBERNETES", "spring.cloud.kubernetes.config.enabled=false", - "spring.cloud.kubernetes.secrets.enabled=false" }, - classes = Application.class) -@EnableKubernetesMockClient -class ConfigFailFastEnabledButRetryDisabled { +abstract class ConfigFailFastEnabledButRetryDisabled { private static final String API = "/api/v1/namespaces/default/configmaps/application"; @@ -61,8 +50,9 @@ class ConfigFailFastEnabledButRetryDisabled { private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + ConfigFailFastEnabledButRetryDisabled.mockClient = mockClient; + ConfigFailFastEnabledButRetryDisabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -85,7 +75,7 @@ void locateShouldFailWithoutRetrying() { assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isFalse(); assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name 'application' in namespace 'default'"); + .hasMessageContaining("api/v1/namespaces/default/configmaps. Message: Not Found."); // verify that propertySourceLocator.locate is called only once verify(propertySourceLocator, times(1)).locate(any()); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryDisabledButSecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryDisabledButSecretsRetryEnabled.java index a19a6e378f..39335f317b 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryDisabledButSecretsRetryEnabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryDisabledButSecretsRetryEnabled.java @@ -16,79 +16,74 @@ package org.springframework.cloud.kubernetes.fabric8.config.locator_retry; +import io.fabric8.kubernetes.api.model.ConfigMapListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; import org.springframework.context.ApplicationContext; import org.springframework.mock.env.MockEnvironment; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.enabled=false", - "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class ConfigRetryDisabledButSecretsRetryEnabled { +abstract class ConfigRetryDisabledButSecretsRetryEnabled { - private static final String API = "/api/v1/namespaces/default/configmaps/application"; + private static final String API = "/api/v1/namespaces/default/configmaps"; private static KubernetesMockServer mockServer; private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + @Autowired + private Fabric8ConfigMapPropertySourceLocator propertySourceLocator; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + ConfigRetryDisabledButSecretsRetryEnabled.mockClient = mockClient; + ConfigRetryDisabledButSecretsRetryEnabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); - } - @SpyBean - private Fabric8ConfigMapPropertySourceLocator propertySourceLocator; + // return empty secret list to not fail context creation + mockServer.expect().withPath(API).andReturn(200, new ConfigMapListBuilder().build()).always(); + } @Autowired private ApplicationContext context; @Test void locateShouldFailWithoutRetrying() { - + Fabric8ConfigMapPropertySourceLocator psl = spy(propertySourceLocator); /* * Enabling secrets retry causes Spring Retry to be enabled and a * RetryOperationsInterceptor bean with NeverRetryPolicy for config maps to be * defined. ConfigMapPropertySourceLocator should not retry even Spring Retry is * enabled. */ - + mockServer.clearExpectations(); mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); - assertThat(context.containsBean("kubernetesConfigRetryInterceptor")).isTrue(); - assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name 'application' in namespace 'default'"); + assertRetryBean(context); + assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("v1/namespaces/default/configmaps. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(psl, times(1)).locate(any()); } + protected abstract void assertRetryBean(ApplicationContext context); + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryEnabled.java index caea6275e3..e2e27b0c8e 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryEnabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryEnabled.java @@ -20,18 +20,14 @@ import java.util.Map; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ConfigMapListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigMapPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapPropertySourceLocator; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -44,32 +40,31 @@ /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.config.retry.max-attempts=5", - "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class ConfigRetryEnabled { +abstract class ConfigRetryEnabled { - private static final String API = "/api/v1/namespaces/default/configmaps/application"; + private static final String API = "/api/v1/namespaces/default/configmaps"; private static KubernetesMockServer mockServer; private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + protected ConfigMapPropertySourceLocator psl; + + protected ConfigMapPropertySourceLocator verifiablePsl; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + ConfigRetryEnabled.mockClient = mockClient; + ConfigRetryEnabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); - } - @SpyBean - private Fabric8ConfigMapPropertySourceLocator propertySourceLocator; + // needed so that initial call, before our test method kicks in, succeeds + mockServer.expect().withPath(API).andReturn(200, new ConfigMapListBuilder().build()).once(); + } @Test void locateShouldNotRetryWhenThereIsNoFailure() { @@ -78,15 +73,15 @@ void locateShouldNotRetryWhenThereIsNoFailure() { data.put("some.number", "0"); // return config map without failing - mockServer.expect().withPath(API).andReturn(200, - new ConfigMapBuilder().withNewMetadata().withName("application").endMetadata().addToData(data).build()) + mockServer + .expect().withPath(API).andReturn(200, new ConfigMapListBuilder().withItems(new ConfigMapBuilder() + .withNewMetadata().withName("application").endMetadata().addToData(data).build()).build()) .once(); - PropertySource propertySource = Assertions - .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + PropertySource propertySource = Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(verifiablePsl, times(1)).locate(any()); // validate the contents of the property source assertThat(propertySource.getProperty("some.prop")).isEqualTo("theValue"); @@ -101,15 +96,15 @@ void locateShouldRetryAndRecover() { // fail 3 times then succeed at the 4th call mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(3); - mockServer.expect().withPath(API).andReturn(200, - new ConfigMapBuilder().withNewMetadata().withName("application").endMetadata().addToData(data).build()) + mockServer + .expect().withPath(API).andReturn(200, new ConfigMapListBuilder().withItems(new ConfigMapBuilder() + .withNewMetadata().withName("application").endMetadata().addToData(data).build()).build()) .once(); - PropertySource propertySource = Assertions - .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + PropertySource propertySource = Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify retried 4 times - verify(propertySourceLocator, times(4)).locate(any()); + verify(verifiablePsl, times(4)).locate(any()); // validate the contents of the property source assertThat(propertySource.getProperty("some.prop")).isEqualTo("theValue"); @@ -121,12 +116,11 @@ void locateShouldRetryAndFail() { // fail all the 5 requests mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(5); - assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name 'application' in namespace 'default'"); + assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("api/v1/namespaces/default/configmaps. Message: Internal Server Error"); // verify retried 5 times until failure - verify(propertySourceLocator, times(5)).locate(any()); + verify(verifiablePsl, times(5)).locate(any()); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java similarity index 64% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java index 992de0d88e..f4cc4bf857 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixApp.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources; +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.One; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Three; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Two; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.Two; @SpringBootApplication @EnableConfigurationProperties({ One.class, Two.class, Three.class }) -public class IncludeProfileSpecificSourcesApp { +public class NamedConfigMapWithPrefixApp { public static void main(String[] args) { - SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args); + SpringApplication.run(NamedConfigMapWithPrefixApp.class, args); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..ee248408d3 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixBootstrapTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=named-config-map-with-prefix", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +class NamedConfigMapWithPrefixBootstrapTests extends NamedConfigMapWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..972a89e3c0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixConfigDataTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedConfigMapWithPrefixApp.class, + properties = { "spring.application.name=config-map-name-as-prefix", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-config-map-with-prefix.yaml" }) +@AutoConfigureWebTestClient +@EnableKubernetesMockClient(crud = true, https = false) +class NamedConfigMapWithPrefixConfigDataTests extends NamedConfigMapWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithPrefixTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java similarity index 65% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithPrefixTests.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java index 86d8698729..04e4c960bc 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigMapWithPrefixTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config; +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix; import java.util.HashMap; import java.util.Map; @@ -22,38 +22,25 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.hamcrest.Matchers; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.WithPrefixApp; -import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; /** * @author wind57 + * @author Ryan Baxter */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WithPrefixApp.class, - properties = { "spring.cloud.bootstrap.name=config-map-name-as-prefix", - "spring.main.cloud-platform=KUBERNETES" }) -@AutoConfigureWebTestClient -@EnableKubernetesMockClient(crud = true, https = false) -class ConfigMapWithPrefixTests { +abstract class NamedConfigMapWithPrefixTests { private static KubernetesClient mockClient; @Autowired private WebTestClient webClient; - @BeforeAll - public static void setUpBeforeClass() { - + static void setUpBeforeClass(KubernetesClient mockClient) { + NamedConfigMapWithPrefixTests.mockClient = mockClient; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -76,7 +63,7 @@ public static void setUpBeforeClass() { } - private static void createConfigmap(String name, Map data) { + static void createConfigmap(String name, Map data) { mockClient.configMaps().inNamespace("spring-k8s") .create(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build()); } @@ -91,9 +78,9 @@ private static void createConfigmap(String name, Map data) { * */ @Test - public void testOne() { - this.webClient.get().uri("/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("one")); + void testOne() { + this.webClient.get().uri("/named-config-map/prefix/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one")); } /** @@ -106,9 +93,9 @@ public void testOne() { * */ @Test - public void testTwo() { - this.webClient.get().uri("/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("two")); + void testTwo() { + this.webClient.get().uri("/named-config-map/prefix/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); } /** @@ -121,9 +108,9 @@ public void testTwo() { * */ @Test - public void testThree() { - this.webClient.get().uri("/prefix/three").exchange().expectStatus().isOk().expectBody(String.class) - .value(Matchers.equalTo("three")); + void testThree() { + this.webClient.get().uri("/named-config-map/prefix/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three")); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/controller/NamedConfigMapWithPrefixController.java similarity index 61% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/controller/NamedConfigMapWithPrefixController.java index e6aa453c74..a65c109695 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/controller/NamedConfigMapWithPrefixController.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.controller; +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.controller; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.One; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Three; -import org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties.Two; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties.Two; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController -public class IncludeProfileSpecificSourcesController { +public class NamedConfigMapWithPrefixController { private final One one; @@ -31,23 +31,23 @@ public class IncludeProfileSpecificSourcesController { private final Three three; - public IncludeProfileSpecificSourcesController(One one, Two two, Three three) { + public NamedConfigMapWithPrefixController(One one, Two two, Three three) { this.one = one; this.two = two; this.three = three; } - @GetMapping("/profile-specific/one") + @GetMapping("/named-config-map/prefix/one") public String one() { return one.getProperty(); } - @GetMapping("/profile-specific/two") + @GetMapping("/named-config-map/prefix/two") public String two() { return two.getProperty(); } - @GetMapping("/profile-specific/three") + @GetMapping("/named-config-map/prefix/three") public String three() { return three.getProperty(); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/One.java new file mode 100644 index 0000000000..db1b24b2a5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Three.java similarity index 90% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Three.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Three.java index 93d4164987..1608e7808d 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Three.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Three.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties; +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Two.java new file mode 100644 index 0000000000..ec24787c81 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_prefix/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_prefix.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileApp.java new file mode 100644 index 0000000000..4ad63b1cdb --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileApp.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class }) +public class NamedConfigMapWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(NamedConfigMapWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java new file mode 100644 index 0000000000..b97290ce79 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = NamedConfigMapWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=named-configmap-with-profile", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedConfigMapWithProfileBootstrapTests extends NamedConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java new file mode 100644 index 0000000000..f4a498107d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = NamedConfigMapWithProfileApp.class, + properties = { "spring.application.name=named-configmap-with-profile", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-configmap-with-profile.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedConfigMapWithProfileConfigDataTests extends NamedConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java new file mode 100644 index 0000000000..ec19f47fc3 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile; + +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class NamedConfigMapWithProfileTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + NamedConfigMapWithProfileTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = Collections.singletonMap("one.property", "one"); + Map oneFromKubernetesProfile = Collections.singletonMap("one.property", "one-from-k8s"); + + createConfigmap("configmap-one", one); + createConfigmap("configmap-one-k8s", oneFromKubernetesProfile); + + Map two = Collections.singletonMap("property", "two"); + Map twoFromKubernetesProfile = Collections.singletonMap("property", "two-from-k8s"); + + createConfigmap("configmap-two", two); + createConfigmap("configmap-two-k8s", twoFromKubernetesProfile); + + Map three = Collections.singletonMap("property", "three"); + Map threeFromKubernetesProfile = Collections.singletonMap("property", "three-from-k8s"); + + createConfigmap("configmap-three", three); + createConfigmap("configmap-three-k8s", threeFromKubernetesProfile); + + } + + static void createConfigmap(String name, Map data) { + mockClient.configMaps().inNamespace("spring-k8s") + .create(new ConfigMapBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[0].useNameAsPrefix=false'
+	 *   'spring.cloud.kubernetes.config.sources[0].includeProfileSpecificSources=true'
+	 * 	 ("one.property", "one-from-k8s")
+	 *
+	 * 	 As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
+	 * 	 the profile based source.
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-configmap/profile/one").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("one-from-k8s")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[1].explicitPrefix=two'
+	 *   'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=false'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two").
+	 *
+	 * 	 Even if there is a profile based source, we disabled reading it.
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-configmap/profile/two").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.config.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.config.sources[2].name=configmap-three'
+	 *   'spring.cloud.kubernetes.config.sources[1].includeProfileSpecificSources=true'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "config-three"), value is overridden by the one that we read from
+	 * 	 the profile based source
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-configmap/profile/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three-from-k8s")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java new file mode 100644 index 0000000000..8f353d88ec --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class NamedConfigMapWithProfileController { + + private final One one; + + private final Two two; + + private final Three three; + + public NamedConfigMapWithProfileController(One one, Two two, Three three) { + this.one = one; + this.two = two; + this.three = three; + } + + @GetMapping("/named-configmap/profile/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/named-configmap/profile/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/named-configmap/profile/three") + public String three() { + return three.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/One.java new file mode 100644 index 0000000000..2fbc67f057 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Three.java new file mode 100644 index 0000000000..7a192e5eae --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "configmap-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Two.java new file mode 100644 index 0000000000..7cf2943a7a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_config_map_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/WithPrefixApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixApp.java similarity index 66% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/WithPrefixApp.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixApp.java index 116ac5e5ab..d7c01c67dd 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/WithPrefixApp.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixApp.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.with_prefix; +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.One; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.Three; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.Two; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.Two; @SpringBootApplication @EnableConfigurationProperties({ One.class, Two.class, Three.class }) -public class WithPrefixApp { +public class NamedSecretWithPrefixApp { public static void main(String[] args) { - SpringApplication.run(WithPrefixApp.class, args); + SpringApplication.run(NamedSecretWithPrefixApp.class, args); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java new file mode 100644 index 0000000000..0f79837937 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixBootstrapTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithPrefixApp.class, + properties = { "spring.cloud.bootstrap.name=named-secret-with-prefix", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedSecretWithPrefixBootstrapTests extends NamedSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java new file mode 100644 index 0000000000..05cf66ac02 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixConfigDataTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithPrefixApp.class, + properties = { "spring.application.name=named-secret-with-prefix", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-secret-with-prefix.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedSecretWithPrefixConfigDataTests extends NamedSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixTests.java new file mode 100644 index 0000000000..45bc5c6b82 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/NamedSecretWithPrefixTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class NamedSecretWithPrefixTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + NamedSecretWithPrefixTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = Collections.singletonMap("one.property", + Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-one", one); + + Map two = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("two".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-two", two); + + Map three = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("three".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-three", three); + + } + + private static void createSecret(String name, Map data) { + mockClient.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 * 	 ("one.property", "one")
+	 *
+	 * 	 As such: @ConfigurationProperties("one")
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-secret/prefix/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two")
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-secret/prefix/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].name=secret-three'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-three")
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-secret/prefix/three").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("three")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/controller/Controller.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java similarity index 62% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/controller/Controller.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java index adbed9f95f..dc8284373e 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/controller/Controller.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/controller/NamedSecretWithPrefixController.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.with_prefix.controller; +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.controller; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.One; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.Three; -import org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties.Two; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties.Two; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController -public class Controller { +public class NamedSecretWithPrefixController { private final One one; @@ -31,23 +31,23 @@ public class Controller { private final Three three; - public Controller(One one, Two two, Three three) { + public NamedSecretWithPrefixController(One one, Two two, Three three) { this.one = one; this.two = two; this.three = three; } - @GetMapping("/prefix/one") + @GetMapping("/named-secret/prefix/one") public String one() { return one.getProperty(); } - @GetMapping("/prefix/two") + @GetMapping("/named-secret/prefix/two") public String two() { return two.getProperty(); } - @GetMapping("/prefix/three") + @GetMapping("/named-secret/prefix/three") public String three() { return three.getProperty(); } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/One.java similarity index 90% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/One.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/One.java index 5503f6c0a8..7371a5d823 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/One.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/One.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties; +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Three.java similarity index 85% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Three.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Three.java index 239f4374dc..becf8ee03e 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/include_profile_specific_sources/properties/Three.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Three.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.include_profile_specific_sources.properties; +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("three") +@ConfigurationProperties(prefix = "secret-three") public class Three { private String property; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Two.java similarity index 90% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Two.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Two.java index ee33bc75bb..cd7960d700 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/with_prefix/properties/Two.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_prefix/properties/Two.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config.with_prefix.properties; +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_prefix.properties; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileApp.java new file mode 100644 index 0000000000..28c9f2aa52 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileApp.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.Two; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class, Two.class, Three.class }) +public class NamedSecretWithProfileApp { + + public static void main(String[] args) { + SpringApplication.run(NamedSecretWithProfileApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java new file mode 100644 index 0000000000..1c11e0daf9 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=named-secret-with-profile", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedSecretWithProfileBootstrapTests extends NamedSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java new file mode 100644 index 0000000000..f4cc3ba07b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("k8s") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = NamedSecretWithProfileApp.class, + properties = { "spring.application.name=named-secret-with-profile", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./named-secret-with-profile.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class NamedSecretWithProfileConfigDataTests extends NamedSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java new file mode 100644 index 0000000000..9c0e8a21b8 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class NamedSecretWithProfileTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + NamedSecretWithProfileTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = Collections.singletonMap("one.property", + Base64.getEncoder().encodeToString("one".getBytes(StandardCharsets.UTF_8))); + Map oneFromKubernetesProfile = Collections.singletonMap("one.property", + Base64.getEncoder().encodeToString("one-from-k8s".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-one", one); + createSecret("secret-one-k8s", oneFromKubernetesProfile); + + Map two = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("two".getBytes(StandardCharsets.UTF_8))); + Map twoFromKubernetesProfile = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("two-from-k8s".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-two", two); + createSecret("secret-two-k8s", twoFromKubernetesProfile); + + Map three = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("three".getBytes(StandardCharsets.UTF_8))); + Map threeFromKubernetesProfile = Collections.singletonMap("property", + Base64.getEncoder().encodeToString("three-from-k8s".getBytes(StandardCharsets.UTF_8))); + + createSecret("secret-three", three); + createSecret("secret-three-k8s", threeFromKubernetesProfile); + + } + + private static void createSecret(String name, Map data) { + mockClient.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build()); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].useNameAsPrefix=false'
+	 *   'spring.cloud.kubernetes.secrets.sources[0].includeProfileSpecificSources=true'
+	 * 	 ("one.property", "one-from-k8s")
+	 *
+	 * 	 As such: @ConfigurationProperties("one"), value is overridden by the one that we read from
+	 * 	 the profile based source.
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("/named-secret/profile/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("one-from-k8s")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].explicitPrefix=two'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=false'
+	 * 	 ("property", "two")
+	 *
+	 * 	 As such: @ConfigurationProperties("two").
+	 *
+	 * 	 Even if there is a profile based source, we disabled reading it.
+	 * 
+ */ + @Test + void testTwo() { + this.webClient.get().uri("/named-secret/profile/two").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("two")); + } + + /** + *
+	 *   'spring.cloud.kubernetes.secrets.useNameAsPrefix=true'
+	 *   'spring.cloud.kubernetes.secrets.sources[2].name=secret-three'
+	 *   'spring.cloud.kubernetes.secrets.sources[1].includeProfileSpecificSources=true'
+	 * 	 ("property", "three")
+	 *
+	 * 	 As such: @ConfigurationProperties(prefix = "secret-three"), value is overridden by the one that we read from
+	 * 	 * 	 the profile based source
+	 * 
+ */ + @Test + void testThree() { + this.webClient.get().uri("/named-secret/profile/three").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("three-from-k8s")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/controller/NamedSecretWithProfileController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/controller/NamedSecretWithProfileController.java new file mode 100644 index 0000000000..e241ab48bb --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/controller/NamedSecretWithProfileController.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.One; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.Three; +import org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties.Two; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class NamedSecretWithProfileController { + + private final One one; + + private final Two two; + + private final Three three; + + public NamedSecretWithProfileController(One one, Two two, Three three) { + this.one = one; + this.two = two; + this.three = three; + } + + @GetMapping("/named-secret/profile/one") + public String one() { + return one.getProperty(); + } + + @GetMapping("/named-secret/profile/two") + public String two() { + return two.getProperty(); + } + + @GetMapping("/named-secret/profile/three") + public String three() { + return three.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/One.java new file mode 100644 index 0000000000..9290f45be4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("one") +public class One { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Three.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Three.java new file mode 100644 index 0000000000..5a8c79cd0a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Three.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "secret-three") +public class Three { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Two.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Two.java new file mode 100644 index 0000000000..3c94d1543c --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Two.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.named_secret_with_profile.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("two") +public class Two { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfigurationTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfigurationTest.java index 5b090d6a91..e350a2746c 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfigurationTest.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigReloadAutoConfigurationTest.java @@ -24,6 +24,7 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -74,6 +75,11 @@ public static void setUpBeforeClass() { mockClient.configMaps().inNamespace("spring").create(configMap2); } + @BeforeEach + public void beforeEach() { + commonProperties = new String[] { "spring.cloud.bootstrap.enabled=true" }; + } + @Test public void kubernetesConfigReloadDisabled() { setup(KubernetesClientTestConfiguration.class, "spring.cloud.kubernetes.reload.enabled=false"); diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigurationChangeDetectorTest.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigurationChangeDetectorTest.java deleted file mode 100644 index 59715890fc..0000000000 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/reload/ConfigurationChangeDetectorTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.fabric8.config.reload; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigReloadProperties; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationChangeDetector; -import org.springframework.cloud.kubernetes.commons.config.reload.ConfigurationUpdateStrategy; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author wind57 - */ -public class ConfigurationChangeDetectorTest { - - private final ConfigurationChangeDetectorStub stub = new ConfigurationChangeDetectorStub(null, null, null); - - @Test - public void testChangedTwoNulls() { - boolean changed = stub.changed(null, (MapPropertySource) null); - assertThat(changed).isFalse(); - } - - @Test - public void testChangedLeftNullRightNonNull() { - MapPropertySource right = new MapPropertySource("rightNonNull", Collections.emptyMap()); - boolean changed = stub.changed(null, right); - assertThat(changed).isTrue(); - } - - @Test - public void testChangedLeftNonNullRightNull() { - MapPropertySource left = new MapPropertySource("leftNonNull", Collections.emptyMap()); - boolean changed = stub.changed(left, null); - assertThat(changed).isTrue(); - } - - @Test - public void testChangedEqualMaps() { - Object value = new Object(); - Map leftMap = new HashMap<>(); - leftMap.put("key", value); - Map rightMap = new HashMap<>(); - rightMap.put("key", value); - MapPropertySource left = new MapPropertySource("left", leftMap); - MapPropertySource right = new MapPropertySource("right", rightMap); - boolean changed = stub.changed(left, right); - assertThat(changed).isFalse(); - } - - @Test - public void testChangedNonEqualMaps() { - Object value = new Object(); - Map leftMap = new HashMap<>(); - leftMap.put("key", value); - leftMap.put("anotherKey", value); - Map rightMap = new HashMap<>(); - rightMap.put("key", value); - MapPropertySource left = new MapPropertySource("left", leftMap); - MapPropertySource right = new MapPropertySource("right", rightMap); - boolean changed = stub.changed(left, right); - assertThat(changed).isTrue(); - } - - @Test - public void testChangedListsDifferentSizes() { - List left = Collections.singletonList(new MapPropertySource("one", Collections.emptyMap())); - List right = Collections.emptyList(); - boolean changed = stub.changed(left, right); - assertThat(changed).isFalse(); - } - - @Test - public void testChangedListSameSizesButNotEqual() { - Object value = new Object(); - Map leftMap = new HashMap<>(); - leftMap.put("key", value); - Map rightMap = new HashMap<>(); - leftMap.put("anotherKey", value); - List left = Collections.singletonList(new MapPropertySource("one", leftMap)); - List right = Collections.singletonList(new MapPropertySource("two", rightMap)); - boolean changed = stub.changed(left, right); - assertThat(changed).isTrue(); - } - - @Test - public void testChangedListSameSizesEqual() { - Object value = new Object(); - Map leftMap = new HashMap<>(); - leftMap.put("key", value); - Map rightMap = new HashMap<>(); - leftMap.put("key", value); - List left = Collections.singletonList(new MapPropertySource("one", leftMap)); - List right = Collections.singletonList(new MapPropertySource("two", rightMap)); - boolean changed = stub.changed(left, right); - assertThat(changed).isTrue(); - } - - /** - * only needed to test some protected methods it defines - */ - private static final class ConfigurationChangeDetectorStub extends ConfigurationChangeDetector { - - private ConfigurationChangeDetectorStub(ConfigurableEnvironment environment, ConfigReloadProperties properties, - ConfigurationUpdateStrategy strategy) { - super(environment, properties, strategy); - } - - } - -} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastDisabled.java new file mode 100644 index 0000000000..f42dc5b39d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastDisabled.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BootstrapSecretsFailFastDisabled extends SecretsFailFastDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @SpyBean + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + public void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = propertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..78d38dfa55 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastEnabledButRetryDisabled.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.config.enabled=false" }, + classes = Application.class) +@EnableKubernetesMockClient +class BootstrapSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEnabledButRetryDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @SpyBean + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = propertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryDisabledButConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryDisabledButConfigRetryEnabled.java new file mode 100644 index 0000000000..45cf342f76 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryDisabledButConfigRetryEnabled.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.secrets.name=my-secret", + "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BootstrapSecretsRetryDisabledButConfigRetryEnabled extends SecretsRetryDisabledButConfigRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @SpyBean + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = propertySourceLocator; + } + + @Override + protected void assertRetryBean(ApplicationContext context) { + assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isTrue(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryEnabled.java new file mode 100644 index 0000000000..89cabd905f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsRetryEnabled.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", + "spring.cloud.kubernetes.secrets.retry.max-attempts=5", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }, + classes = Application.class) +@EnableKubernetesMockClient +class BootstrapSecretsRetryEnabled extends SecretsRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @SpyBean + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + public void beforeEach() { + psl = propertySourceLocator; + verifiablePsl = propertySourceLocator; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastDisabled.java new file mode 100644 index 0000000000..64a25d7d91 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastDisabled.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; + +import static org.mockito.Mockito.spy; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataSecretsFailFastDisabled extends SecretsFailFastDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @Autowired + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + public void beforeEach() { + psl = spy(propertySourceLocator); + verifiablePsl = psl; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastEnabledButRetryDisabled.java new file mode 100644 index 0000000000..8b23556acf --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastEnabledButRetryDisabled.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; + +import static org.mockito.Mockito.spy; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:", + "spring.cloud.kubernetes.config.enabled=false" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEnabledButRetryDisabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @Autowired + private Fabric8SecretsPropertySourceLocator propertySourceLocator; + + @BeforeEach + void beforeEach() { + psl = spy(propertySourceLocator); + verifiablePsl = psl; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryDisabledButConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryDisabledButConfigRetryEnabled.java new file mode 100644 index 0000000000..829f355ad6 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryDisabledButConfigRetryEnabled.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.fabric8.config.Application; +import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.context.ApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", + "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.secrets.name=my-secret", + "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataSecretsRetryDisabledButConfigRetryEnabled extends SecretsRetryDisabledButConfigRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @Autowired + private Fabric8SecretsPropertySourceLocator secretsPropertySourceLocator; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @BeforeEach + public void beforeEach() { + psl = spy(secretsPropertySourceLocator); + verifiablePsl = psl; + } + + @Override + protected void assertRetryBean(ApplicationContext context) { + assertThat(context.containsBean("configDataSecretsPropertySourceLocator")).isTrue(); + assertThat(context.getBean("configDataSecretsPropertySourceLocator")) + .isInstanceOf(Fabric8SecretsPropertySourceLocator.class); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryEnabled.java new file mode 100644 index 0000000000..d15eff9f0b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsRetryEnabled.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.retry; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.kubernetes.commons.config.ConfigDataRetryableSecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.fabric8.config.Application; + +import static org.mockito.Mockito.spy; + +/** + * @author Isik Erhan + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { "spring.cloud.kubernetes.client.namespace=default", + "spring.cloud.kubernetes.secrets.fail-fast=true", + "spring.cloud.kubernetes.secrets.retry.max-attempts=5", + "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", + "spring.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:" }, + classes = Application.class) +@EnableKubernetesMockClient +class ConfigDataSecretsRetryEnabled extends SecretsRetryEnabled { + + private static KubernetesMockServer mockServer; + + private static KubernetesClient mockClient; + + @Autowired + private ConfigDataRetryableSecretsPropertySourceLocator configDataRetryableSecretsPropertySourceLocator; + + @BeforeAll + static void setup() { + setup(mockClient, mockServer); + } + + @BeforeEach + public void beforeEach() { + psl = configDataRetryableSecretsPropertySourceLocator; + verifiablePsl = spy(configDataRetryableSecretsPropertySourceLocator.getSecretsPropertySourceLocator()); + configDataRetryableSecretsPropertySourceLocator.setSecretsPropertySourceLocator(verifiablePsl); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastDisabled.java index 162722f18f..1ce88b41d8 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastDisabled.java @@ -19,16 +19,11 @@ import io.fabric8.kubernetes.api.model.SecretListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.mock.env.MockEnvironment; import static org.mockito.ArgumentMatchers.any; @@ -38,13 +33,7 @@ /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", - "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class SecretsFailFastDisabled { +abstract class SecretsFailFastDisabled { private static final String API = "/api/v1/namespaces/default/secrets/my-secret"; @@ -54,8 +43,13 @@ class SecretsFailFastDisabled { private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + protected SecretsPropertySourceLocator psl; + + protected SecretsPropertySourceLocator verifiablePsl; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + SecretsFailFastDisabled.mockClient = mockClient; + SecretsFailFastDisabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -67,17 +61,14 @@ static void setup() { mockServer.expect().withPath(LIST_API).andReturn(200, new SecretListBuilder().build()).always(); } - @SpyBean - private Fabric8SecretsPropertySourceLocator propertySourceLocator; - @Test void locateShouldNotRetry() { mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); - Assertions.assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(verifiablePsl, times(1)).locate(any()); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastEnabledButRetryDisabled.java index 2d00828dcf..4ed82085be 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastEnabledButRetryDisabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastEnabledButRetryDisabled.java @@ -19,16 +19,11 @@ import io.fabric8.kubernetes.api.model.SecretListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.context.ApplicationContext; import org.springframework.mock.env.MockEnvironment; @@ -41,16 +36,7 @@ /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", - "spring.cloud.kubernetes.secrets.name=my-secret", "spring.cloud.kubernetes.secrets.enable-api=true", - "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class SecretsFailFastEnabledButRetryDisabled { - - private static final String API = "/api/v1/namespaces/default/secrets/my-secret"; +abstract class SecretsFailFastEnabledButRetryDisabled { private static final String LIST_API = "/api/v1/namespaces/default/secrets"; @@ -58,8 +44,13 @@ class SecretsFailFastEnabledButRetryDisabled { private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + protected SecretsPropertySourceLocator psl; + + protected SecretsPropertySourceLocator verifiablePsl; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + SecretsFailFastEnabledButRetryDisabled.mockClient = mockClient; + SecretsFailFastEnabledButRetryDisabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -71,24 +62,21 @@ static void setup() { mockServer.expect().withPath(LIST_API).andReturn(200, new SecretListBuilder().build()).always(); } - @SpyBean - private Fabric8SecretsPropertySourceLocator propertySourceLocator; - @Autowired private ApplicationContext context; @Test void locateShouldFailWithoutRetrying() { - - mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); + // clear so that previous mock is disabled + mockServer.clearExpectations(); + mockServer.expect().withPath(LIST_API).andReturn(500, "Internal Server Error").once(); assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isFalse(); - assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(verifiablePsl, times(1)).locate(any()); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryDisabledButConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryDisabledButConfigRetryEnabled.java index 3b08471605..322785ac5c 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryDisabledButConfigRetryEnabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryDisabledButConfigRetryEnabled.java @@ -16,23 +16,18 @@ package org.springframework.cloud.kubernetes.fabric8.config.retry; +import io.fabric8.kubernetes.api.model.ConfigMapListBuilder; import io.fabric8.kubernetes.api.model.SecretListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.context.ApplicationContext; import org.springframework.mock.env.MockEnvironment; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -41,25 +36,23 @@ /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "spring.cloud.kubernetes.client.namespace=default", - "spring.cloud.kubernetes.secrets.fail-fast=true", "spring.cloud.kubernetes.secrets.retry.enabled=false", - "spring.cloud.kubernetes.config.fail-fast=true", "spring.cloud.kubernetes.secrets.name=my-secret", - "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class SecretsRetryDisabledButConfigRetryEnabled { +abstract class SecretsRetryDisabledButConfigRetryEnabled { - private static final String API = "/api/v1/namespaces/default/secrets/my-secret"; + private static final String SECRET_API = "/api/v1/namespaces/default/secrets"; - private static final String LIST_API = "/api/v1/namespaces/default/secrets"; + private static final String CONFIG_MAP_API = "/api/v1/namespaces/default/configmaps"; private static KubernetesMockServer mockServer; private static KubernetesClient mockClient; - @BeforeAll - static void setup() { + protected SecretsPropertySourceLocator psl; + + protected SecretsPropertySourceLocator verifiablePsl; + + protected static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + SecretsRetryDisabledButConfigRetryEnabled.mockClient = mockClient; + SecretsRetryDisabledButConfigRetryEnabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -68,34 +61,32 @@ static void setup() { System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); // return empty secret list to not fail context creation - mockServer.expect().withPath(LIST_API).andReturn(200, new SecretListBuilder().build()).always(); + mockServer.expect().withPath(SECRET_API).andReturn(200, new SecretListBuilder().build()).always(); + mockServer.expect().withPath(CONFIG_MAP_API).andReturn(200, new ConfigMapListBuilder().build()).always(); } - @SpyBean - private Fabric8SecretsPropertySourceLocator propertySourceLocator; - @Autowired private ApplicationContext context; @Test void locateShouldFailWithoutRetrying() { - /* * Enabling config retry causes Spring Retry to be enabled and a * RetryOperationsInterceptor bean with NeverRetryPolicy for secrets to be * defined. SecretsPropertySourceLocator should not retry even Spring Retry is * enabled. */ + mockServer.clearExpectations(); + mockServer.expect().withPath(SECRET_API).andReturn(500, "Internal Server Error").once(); - mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); - - assertThat(context.containsBean("kubernetesSecretsRetryInterceptor")).isTrue(); - assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + assertRetryBean(context); + assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("v1/namespaces/default/secrets. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(verifiablePsl, times(1)).locate(any()); } + protected abstract void assertRetryBean(ApplicationContext context); + } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryEnabled.java index 75ee58deb9..26909120d1 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryEnabled.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsRetryEnabled.java @@ -24,16 +24,11 @@ import io.fabric8.kubernetes.api.model.SecretListBuilder; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.cloud.kubernetes.fabric8.config.Application; -import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySourceLocator; +import org.springframework.cloud.kubernetes.commons.config.SecretsPropertySourceLocator; import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; @@ -46,24 +41,20 @@ /** * @author Isik Erhan */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { - "spring.cloud.kubernetes.client.namespace=default", "spring.cloud.kubernetes.secrets.fail-fast=true", - "spring.cloud.kubernetes.secrets.retry.max-attempts=5", "spring.cloud.kubernetes.secrets.name=my-secret", - "spring.cloud.kubernetes.secrets.enable-api=true", "spring.main.cloud-platform=KUBERNETES" }, - classes = Application.class) -@EnableKubernetesMockClient -class SecretsRetryEnabled { +abstract class SecretsRetryEnabled { - private static final String API = "/api/v1/namespaces/default/secrets/my-secret"; + private static final String API = "/api/v1/namespaces/default/secrets"; private static final String LIST_API = "/api/v1/namespaces/default/secrets"; private static KubernetesMockServer mockServer; - private static KubernetesClient mockClient; + protected SecretsPropertySourceLocator psl; - @BeforeAll - static void setup() { + protected SecretsPropertySourceLocator verifiablePsl; + + static void setup(KubernetesClient mockClient, KubernetesMockServer mockServer) { + SecretsRetryEnabled.mockServer = mockServer; // Configure the kubernetes master url to point to the mock server System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); @@ -72,12 +63,9 @@ static void setup() { System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); // return empty secret list to not fail context creation - mockServer.expect().withPath(LIST_API).andReturn(200, new SecretListBuilder().build()).always(); + mockServer.expect().withPath(LIST_API).andReturn(200, new SecretListBuilder().build()).once(); } - @SpyBean - private Fabric8SecretsPropertySourceLocator propertySourceLocator; - @Test void locateShouldNotRetryWhenThereIsNoFailure() { Map data = new HashMap<>(); @@ -85,15 +73,14 @@ void locateShouldNotRetryWhenThereIsNoFailure() { data.put("some.sensitive.number", Base64.getEncoder().encodeToString("1".getBytes())); // return secret without failing - mockServer.expect().withPath(API).andReturn(200, + mockServer.expect().withPath(API).andReturn(200, new SecretListBuilder().withItems( new SecretBuilder().withNewMetadata().withName("my-secret").endMetadata().addToData(data).build()) - .once(); + .build()).once(); - PropertySource propertySource = Assertions - .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + PropertySource propertySource = Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify locate is called only once - verify(propertySourceLocator, times(1)).locate(any()); + verify(verifiablePsl, times(1)).locate(any()); // validate the contents of the property source assertThat(propertySource.getProperty("some.sensitive.prop")).isEqualTo("theSensitiveValue"); @@ -108,15 +95,15 @@ void locateShouldRetryAndRecover() { // fail 3 times then succeed at the 4th call mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(3); - mockServer.expect().withPath(API).andReturn(200, + + mockServer.expect().withPath(API).andReturn(200, new SecretListBuilder().withItems( new SecretBuilder().withNewMetadata().withName("my-secret").endMetadata().addToData(data).build()) - .once(); + .build()).once(); - PropertySource propertySource = Assertions - .assertDoesNotThrow(() -> propertySourceLocator.locate(new MockEnvironment())); + PropertySource propertySource = Assertions.assertDoesNotThrow(() -> psl.locate(new MockEnvironment())); // verify retried 4 times - verify(propertySourceLocator, times(4)).locate(any()); + verify(verifiablePsl, times(4)).locate(any()); // validate the contents of the property source assertThat(propertySource.getProperty("some.sensitive.prop")).isEqualTo("theSensitiveValue"); @@ -128,12 +115,12 @@ void locateShouldRetryAndFail() { // fail all the 5 requests mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(5); - assertThatThrownBy(() -> propertySourceLocator.locate(new MockEnvironment())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Internal Server Error."); // verify retried 5 times until failure - verify(propertySourceLocator, times(5)).locate(any()); + verify(verifiablePsl, times(5)).locate(any()); + } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/BootstrapSecretsWithLabelsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/BootstrapSecretsWithLabelsTests.java new file mode 100644 index 0000000000..c56fd7dbc0 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/BootstrapSecretsWithLabelsTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SecretsWithLabelsApp.class, + properties = { "spring.cloud.bootstrap.name=secret-with-labels-config", "spring.main.cloud-platform=KUBERNETES", + "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class BootstrapSecretsWithLabelsTests extends SecretsWithLabelsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/ConfigDataSecretsWithLabelsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/ConfigDataSecretsWithLabelsTests.java new file mode 100644 index 0000000000..ccadf90f1a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/ConfigDataSecretsWithLabelsTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author wind57 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SecretsWithLabelsApp.class, + properties = { "spring.application.name=secret-with-labels-config", "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./secret-with-labels-config.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class ConfigDataSecretsWithLabelsTests extends SecretsWithLabelsTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/One.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/One.java new file mode 100644 index 0000000000..6b167c3c4b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/One.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("secret") +class One { + + private String property; + + String getProperty() { + return property; + } + + void setProperty(String property) { + this.property = property; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsApp.java new file mode 100644 index 0000000000..b4a720a44b --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsApp.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties({ One.class }) +class SecretsWithLabelsApp { + + static void main(String[] args) { + SpringApplication.run(SecretsWithLabelsApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsController.java new file mode 100644 index 0000000000..4eec11c6a5 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsController.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +class SecretsWithLabelsController { + + private final One one; + + SecretsWithLabelsController(One one) { + this.one = one; + } + + @GetMapping("secrets/labels/one") + String one() { + return one.getProperty(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsTests.java new file mode 100644 index 0000000000..838355a5ba --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/secrets_with_labels/SecretsWithLabelsTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.secrets_with_labels; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + */ +abstract class SecretsWithLabelsTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + SecretsWithLabelsTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map two = Collections.singletonMap("secret.property", + Base64.getEncoder().encodeToString("value".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-two", two); + + Map three = Collections.singletonMap("secret.property", + Base64.getEncoder().encodeToString("diff-value".getBytes(StandardCharsets.UTF_8))); + createSecret("secret-three", three); + + } + + private static void createSecret(String name, Map data) { + mockClient.secrets().inNamespace("spring-k8s") + .create(new SecretBuilder().withNewMetadata().withName(name).endMetadata().addToData(data).build()); + } + + /** + *
+	 *	 1. We have two secrets in a certain namespace: "secret-two" and "secret-three".
+	 *	 2. Both of the above configure the same secret data: "secret.property", but with different
+	 *	    values : "value" and "diff-value"
+	 *	 3. In our configuration we want to read only "secret-two" (the one that stores "value" inside)
+	 *	 4. This test proves that we do not touch "secret-three"
+	 * 
+ */ + @Test + void testOne() { + this.webClient.get().uri("secrets/labels/one").exchange().expectStatus().isOk().expectBody(String.class) + .value(Matchers.equalTo("value")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesApp.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesApp.java new file mode 100644 index 0000000000..dae74a94be --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Color; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Name; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Shape; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Type; + +@SpringBootApplication +@EnableConfigurationProperties({ Name.class, Shape.class, Color.class, Type.class }) +public class SingleSourceMultipleFilesApp { + + public static void main(String[] args) { + SpringApplication.run(SingleSourceMultipleFilesApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java new file mode 100644 index 0000000000..38f3d39366 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesBootstrapTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("color") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = SingleSourceMultipleFilesApp.class, + properties = { "spring.cloud.bootstrap.name=single-source-multiple-files", + "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) +@EnableKubernetesMockClient(crud = true, https = false) +class SingleSourceMultipleFilesBootstrapTests extends SingleSourceMultipleFilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java new file mode 100644 index 0000000000..6462948ced --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesConfigDataTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient; +import org.junit.jupiter.api.BeforeAll; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * @author wind57 + */ +@ActiveProfiles("color") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = SingleSourceMultipleFilesApp.class, + properties = { "spring.main.cloud-platform=KUBERNETES", + "spring.config.import=kubernetes:,classpath:./single-source-multiple-files.yaml" }) +@EnableKubernetesMockClient(crud = true, https = false) +class SingleSourceMultipleFilesConfigDataTests extends SingleSourceMultipleFilesTests { + + private static KubernetesClient mockClient; + + @BeforeAll + static void setUpBeforeClass() { + setUpBeforeClass(mockClient); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesTests.java new file mode 100644 index 0000000000..552a9d5ba3 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/SingleSourceMultipleFilesTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files; + +import java.util.HashMap; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author wind57 + * + * issue: https://github.com/spring-cloud/spring-cloud-kubernetes/issues/640 + */ +abstract class SingleSourceMultipleFilesTests { + + private static KubernetesClient mockClient; + + @Autowired + private WebTestClient webClient; + + static void setUpBeforeClass(KubernetesClient mockClient) { + SingleSourceMultipleFilesTests.mockClient = mockClient; + // Configure the kubernetes master url to point to the mock server + System.setProperty(Config.KUBERNETES_MASTER_SYSTEM_PROPERTY, mockClient.getConfiguration().getMasterUrl()); + System.setProperty(Config.KUBERNETES_TRUST_CERT_SYSTEM_PROPERTY, "true"); + System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false"); + System.setProperty(Config.KUBERNETES_NAMESPACE_SYSTEM_PROPERTY, "test"); + System.setProperty(Config.KUBERNETES_HTTP2_DISABLE, "true"); + + Map one = new HashMap<>(); + one.put("fruit.type", "yummy"); + one.put("fruit.properties", "cool.name=banana"); + one.put("fruit-color.properties", "color.when.raw=green\ncolor.when.ripe=yellow"); + + // this is not taken, since "shape" is not an active profile + one.put("fruit-shape.properties", "shape.when.raw=small-sphere\nshape.when.ripe=bigger-sphere"); + createConfigmap(one); + + } + + static void createConfigmap(Map data) { + mockClient.configMaps().inNamespace("spring-k8s").create(new ConfigMapBuilder().withNewMetadata() + .withName("my-configmap").endMetadata().addToData(data).build()); + } + + /** + *
+	 *   "fruit-color.properties" is taken since "spring.application.name=fruit" and
+	 *   "color" is an active profile
+	 * 
+ */ + @Test + void color() { + this.webClient.get().uri("/single_source-multiple-files/color").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("raw:green###ripe:yellow")); + } + + /** + *
+	 *   "fruit.properties" is read, since it matches "spring.application.name"
+	 * 
+ */ + @Test + void name() { + this.webClient.get().uri("/single_source-multiple-files/name").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("banana")); + } + + /** + *
+	 *   shape profile is not active, thus property "fruit-shape.properties" is skipped
+	 *   and as such, a null comes here.
+	 * 
+ */ + @Test + void shape() { + this.webClient.get().uri("/single_source-multiple-files/shape").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.nullValue()); + } + + /** + *
+	 *   this is a non-file property in the configmap
+	 * 
+ */ + @Test + void type() { + this.webClient.get().uri("/single_source-multiple-files/type").exchange().expectStatus().isOk() + .expectBody(String.class).value(Matchers.equalTo("yummy")); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java new file mode 100644 index 0000000000..1ff8f6c2b8 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/controller/SingleSourceMultipleFilesController.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.controller; + +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Color; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Name; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Shape; +import org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties.Type; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SingleSourceMultipleFilesController { + + private final Name name; + + private final Shape shape; + + private final Color color; + + private final Type type; + + public SingleSourceMultipleFilesController(Name name, Shape shape, Color color, Type type) { + this.name = name; + this.shape = shape; + this.color = color; + this.type = type; + } + + @GetMapping("/single_source-multiple-files/type") + public String type() { + return type.getType(); + } + + @GetMapping("/single_source-multiple-files/shape") + public String shape() { + return shape.getRaw(); + } + + @GetMapping("/single_source-multiple-files/color") + public String color() { + return "raw:" + color.getRaw() + "###" + "ripe:" + color.getRipe(); + } + + @GetMapping("/single_source-multiple-files/name") + public String name() { + return name.getName(); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Color.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Color.java new file mode 100644 index 0000000000..34cf51dd15 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Color.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "color.when") +public class Color { + + private String raw; + + private String ripe; + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getRipe() { + return ripe; + } + + public void setRipe(String ripe) { + this.ripe = ripe; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Name.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Name.java new file mode 100644 index 0000000000..e19b512fdc --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Name.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("cool") +public class Name { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Shape.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Shape.java new file mode 100644 index 0000000000..cda2835a67 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Shape.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("shape.when") +public class Shape { + + private String raw; + + private String ripe; + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getRipe() { + return ripe; + } + + public void setRipe(String ripe) { + this.ripe = ripe; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Type.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Type.java new file mode 100644 index 0000000000..7704e5a722 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/single_source_multiple_files/properties/Type.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.config.single_source_multiple_files.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("fruit") +public class Type { + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-secrets.properties b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-secrets.properties index 6273507334..bd4cea9d00 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-secrets.properties +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-secrets.properties @@ -1,4 +1,4 @@ -spring.application.name=configmap-example +spring.application.name=test-secret spring.cloud.kubernetes.reload.enabled=false logging.level.org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropertySource=DEBUG spring.cloud.kubernetes.secrets.labels.foo=bar diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-active-profiles-name.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-active-profiles-name.yaml index 98357fa6c5..7f99eb5067 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-active-profiles-name.yaml +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-active-profiles-name.yaml @@ -1,4 +1,6 @@ spring: - profiles: development + config: + activate: + on-profile: development bean: greeting: "Hello ConfigMap Active Profile Name, %s!" diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-profiles.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-profiles.yaml index 5e296b8a4e..a0ea2d28f1 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-profiles.yaml +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/application-with-profiles.yaml @@ -3,16 +3,22 @@ bean: farewell: "Goodbye ConfigMap default, %s!" --- spring: - profiles: development + config: + activate: + on-profile: development bean: greeting: "Hello ConfigMap dev, %s!" --- spring: - profiles: production + config: + activate: + on-profile: production bean: greeting: "Hello ConfigMap prod, %s!" --- spring: - profiles: production & us-east + config: + activate: + on-profile: production & us-east bean: greeting: "Hello ConfigMap production and us-east, %s!" diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-prefix.yaml new file mode 100644 index 0000000000..40bea776b8 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-prefix.yaml @@ -0,0 +1,21 @@ +spring: + application: + name: labeled-configmap-with-prefix + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-profile.yaml new file mode 100644 index 0000000000..8aa88d2deb --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-profile.yaml @@ -0,0 +1,22 @@ +spring: + application: + name: labeled-configmap-with-profile + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - labels: + color: blue + explicitPrefix: blue + includeProfileSpecificSources: false + - labels: + color: green + - labels: + color: red + - labels: + color: yellow + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml new file mode 100644 index 0000000000..fa6cb72d0f --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-prefix.yaml @@ -0,0 +1,21 @@ +spring: + application: + name: labeled-secret-with-prefix + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - labels: + letter: a + useNameAsPrefix: false + - labels: + letter: b + explicitPrefix: two + - labels: + letter: c + - labels: + letter: d + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-profile.yaml new file mode 100644 index 0000000000..89255f0810 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-profile.yaml @@ -0,0 +1,22 @@ +spring: + application: + name: labeled-secret-with-profile + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - labels: + color: blue + explicitPrefix: blue + includeProfileSpecificSources: false + - labels: + color: green + - labels: + color: red + - labels: + color: yellow + useNameAsPrefix: true diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/config-map-name-as-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-config-map-with-prefix.yaml similarity index 88% rename from spring-cloud-kubernetes-fabric8-config/src/test/resources/config-map-name-as-prefix.yaml rename to spring-cloud-kubernetes-fabric8-config/src/test/resources/named-config-map-with-prefix.yaml index b52095e4d5..cb7bd5af86 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/resources/config-map-name-as-prefix.yaml +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-config-map-with-prefix.yaml @@ -1,6 +1,6 @@ spring: application: - name: with-prefix + name: named-config-map-with-prefix cloud: kubernetes: config: diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-configmap-with-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-configmap-with-profile.yaml new file mode 100644 index 0000000000..da49651040 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-configmap-with-profile.yaml @@ -0,0 +1,17 @@ +spring: + application: + name: named-configmap-with-profile + cloud: + kubernetes: + config: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - name: configmap-one + useNameAsPrefix: false + - name: configmap-two + explicitPrefix: two + includeProfileSpecificSources: false + - name: configmap-three diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-prefix.yaml new file mode 100644 index 0000000000..e9105f9681 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-prefix.yaml @@ -0,0 +1,15 @@ +spring: + application: + name: named-secret-with-prefix + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + sources: + - name: secret-one + useNameAsPrefix: false + - name: secret-two + explicitPrefix: two + - name: secret-three diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-profile.yaml new file mode 100644 index 0000000000..8dc8c8b27a --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-profile.yaml @@ -0,0 +1,17 @@ +spring: + application: + name: named-secret-with-profile + cloud: + kubernetes: + secrets: + enableApi: true + useNameAsPrefix: true + namespace: spring-k8s + includeProfileSpecificSources: true + sources: + - name: secret-one + useNameAsPrefix: false + - name: secret-two + explicitPrefix: two + includeProfileSpecificSources: false + - name: secret-three diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/secret-with-labels-config.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/secret-with-labels-config.yaml new file mode 100644 index 0000000000..25d8779f70 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/secret-with-labels-config.yaml @@ -0,0 +1,10 @@ +spring: + application: + name: with-prefix + cloud: + kubernetes: + secrets: + enableApi: true + namespace: spring-k8s + sources: + - name: secret-two diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/resources/single-source-multiple-files.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/single-source-multiple-files.yaml new file mode 100644 index 0000000000..571e90a02d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-config/src/test/resources/single-source-multiple-files.yaml @@ -0,0 +1,9 @@ +spring: + application: + name: fruit + cloud: + kubernetes: + config: + namespace: spring-k8s + sources: + - name: my-configmap diff --git a/spring-cloud-kubernetes-fabric8-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/IstioAutoConfigurationClientNotPresentWhenKubernetesDisabled.java b/spring-cloud-kubernetes-fabric8-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/IstioAutoConfigurationClientNotPresentWhenKubernetesDisabled.java index 369923d408..5b15e9ae0d 100644 --- a/spring-cloud-kubernetes-fabric8-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/IstioAutoConfigurationClientNotPresentWhenKubernetesDisabled.java +++ b/spring-cloud-kubernetes-fabric8-istio/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/IstioAutoConfigurationClientNotPresentWhenKubernetesDisabled.java @@ -28,8 +28,7 @@ /** * @author wind57 */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class, - properties = { "spring.cloud.kubernetes.enabled=false" }) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = App.class) class IstioAutoConfigurationClientNotPresentWhenKubernetesDisabled { @Autowired diff --git a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInitiatorTest.java b/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInitiatorTest.java index e54e7e1c32..8388de2565 100644 --- a/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInitiatorTest.java +++ b/spring-cloud-kubernetes-fabric8-leader/src/test/java/org/springframework/cloud/kubernetes/fabric8/leader/LeaderInitiatorTest.java @@ -83,7 +83,9 @@ public void shouldStart() throws InterruptedException { assertThat(this.leaderInitiator.isRunning()).isTrue(); verify(this.mockFabric8LeaderRecordWatcher).start(); verify(this.mockFabric8PodReadinessWatcher).start(); - Thread.sleep(10); + + // TODO this tests needs to be reviewed not to use sleep + Thread.sleep(1000); verify(this.mockFabric8LeadershipController, atLeastOnce()).update(); } diff --git a/spring-cloud-kubernetes-integration-tests/kind-config.yaml b/spring-cloud-kubernetes-integration-tests/kind-config.yaml deleted file mode 100644 index 93efbb384b..0000000000 --- a/spring-cloud-kubernetes-integration-tests/kind-config.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# this config file contains all config fields with comments -# NOTE: this is not a particularly useful config file -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -containerdConfigPatches: -- |- - [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] - endpoint = ["http://kind-registry:5000"] - -# 1 control plane node and 3 workers -nodes: - - role: control-plane - image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 - kubeadmConfigPatches: - - | - kind: InitConfiguration - nodeRegistration: - kubeletExtraArgs: - node-labels: "ingress-ready=true" - extraPortMappings: - - containerPort: 80 - hostPort: 80 - protocol: TCP - - containerPort: 443 - hostPort: 443 - protocol: TCP - - role: worker - image: kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6 diff --git a/spring-cloud-kubernetes-integration-tests/permissions.yaml b/spring-cloud-kubernetes-integration-tests/permissions.yaml deleted file mode 100644 index 89f992559a..0000000000 --- a/spring-cloud-kubernetes-integration-tests/permissions.yaml +++ /dev/null @@ -1,68 +0,0 @@ ---- -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ServiceAccount - metadata: - labels: - app: integration-test - name: spring-cloud-kubernetes-serviceaccount - - apiVersion: rbac.authorization.k8s.io/v1 - kind: RoleBinding - metadata: - labels: - app: spring-cloud-kubernetes-core-k8s-client-it - name: spring-cloud-kubernetes-core-k8s-client-it:view - roleRef: - kind: Role - apiGroup: rbac.authorization.k8s.io - name: namespace-reader - subjects: - - kind: ServiceAccount - name: spring-cloud-kubernetes-serviceaccount - namespace: default - - apiVersion: rbac.authorization.k8s.io/v1 - kind: Role - metadata: - namespace: default - name: namespace-reader - rules: - - apiGroups: ["", "extensions", "apps"] - resources: ["configmaps", "pods", "services", "endpoints", "secrets"] - verbs: ["get", "list", "watch"] - - # needed for istio - - - apiVersion: v1 - kind: ServiceAccount - metadata: - labels: - app: istio-integration-test - name: spring-cloud-kubernetes-istio-serviceaccount - namespace: istio-test - - - apiVersion: rbac.authorization.k8s.io/v1 - kind: Role - metadata: - namespace: istio-test - name: istio-test - rules: - - apiGroups: [ "", "extensions", "apps" ] - resources: [ "configmaps", "pods", "services", "endpoints", "secrets" ] - verbs: [ "get", "list", "watch" ] - - - apiVersion: rbac.authorization.k8s.io/v1 - kind: RoleBinding - metadata: - labels: - app: spring-cloud-kubernetes-core-k8s-client-it - name: istio-test-rb - roleRef: - kind: Role - apiGroup: rbac.authorization.k8s.io - name: istio-test - subjects: - - kind: ServiceAccount - name: spring-cloud-kubernetes-istio-serviceaccount - namespace: istio-test diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml index be9bc23f30..2dd228f113 100644 --- a/spring-cloud-kubernetes-integration-tests/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/pom.xml @@ -13,31 +13,16 @@ pom Spring Cloud Kubernetes :: Integration Tests - Integration tests where SCK applications are run inside a Kubernetes - cluster - + Integration tests where SCK applications are run inside a Kubernetes cluster 17 - 1.18.2 - 1.4.0.Final - 3.12.12 - 3.2.2 - - - 32222 + 3.2.13 + 1.16.3 - - org.springframework.boot - spring-boot-maven-plugin - - org.apache.maven.plugins maven-deploy-plugin @@ -46,7 +31,6 @@ true - @@ -79,34 +63,6 @@
- - it - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - localhost - ${nodeport.value} - false - - ${project.build.outputDirectory} - - - - - - integration-test - verify - - - - - - - @@ -123,6 +79,24 @@ ${docker-java.version} test + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + org.testcontainers + k3s + ${testcontainers.version} + test + @@ -134,12 +108,14 @@ spring-cloud-kubernetes-fabric8-client-discovery spring-cloud-kubernetes-fabric8-client-loadbalancer - spring-cloud-kubernetes-discovery-client-it - spring-cloud-kubernetes-reactive-discovery-client-it + spring-cloud-kubernetes-discoveryclient-it spring-cloud-kubernetes-client-config-it spring-cloud-kubernetes-client-loadbalancer-it - spring-cloud-kubernetes-client-reactive-discovery-client-it + spring-cloud-kubernetes-client-reactive-discoveryclient-it spring-cloud-kubernetes-configuration-watcher-it spring-cloud-kubernetes-core-k8s-client-it - + spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + spring-cloud-kubernetes-fabric8-client-secrets-event-reload + spring-cloud-kubernetes-client-secrets-event-reload + diff --git a/spring-cloud-kubernetes-integration-tests/run.sh b/spring-cloud-kubernetes-integration-tests/run.sh deleted file mode 100755 index 7e4cf3e292..0000000000 --- a/spring-cloud-kubernetes-integration-tests/run.sh +++ /dev/null @@ -1,199 +0,0 @@ -#!/bin/bash - -# standard bash error handling -set -o errexit; -set -o pipefail; -set -o nounset; -# debug commands -set -x; - -# working dir to install binaries etc, cleaned up on exit -BIN_DIR="$(mktemp -d)" -# kind binary will be here -KIND="${BIN_DIR}/kind" - -ISTIO="${BIN_DIR}/istio" - -CURRENT_DIR="$(pwd)" - -MVN="${CURRENT_DIR}/../mvnw" - -PROJECT_VERSION=$($MVN help:evaluate -Dexpression=project.version -q -DforceStdout) - -ISTIO_VERSION="1.12.0" - -ALL_INTEGRATION_PROJECTS=( - "spring-cloud-kubernetes-core-k8s-client-it" - "spring-cloud-kubernetes-client-config-it" - "spring-cloud-kubernetes-configuration-watcher-it" - "spring-cloud-kubernetes-client-loadbalancer-it" - "spring-cloud-kubernetes-client-reactive-discovery-client-it" - "spring-cloud-kubernetes-discovery-client-it" - "spring-cloud-kubernetes-reactive-discovery-client-it" - "spring-cloud-kubernetes-fabric8-client-simple-core" - "spring-cloud-kubernetes-fabric8-client-configmap" - "spring-cloud-kubernetes-fabric8-istio-it" - "spring-cloud-kubernetes-fabric8-client-discovery" - "spring-cloud-kubernetes-fabric8-client-loadbalancer" -) -INTEGRATION_PROJECTS=(${INTEGRATION_PROJECTS:-${ALL_INTEGRATION_PROJECTS[@]}}) - -DEFAULT_PULLING_IMAGES=( - "jettech/kube-webhook-certgen:v1.2.2" - "rabbitmq:3-management" - "zookeeper:3.6.2" - "rodolpheche/wiremock:2.27.2" - "wurstmeister/kafka:2.13-2.6.0" - "istio/proxyv2:${ISTIO_VERSION}" - "istio/pilot:${ISTIO_VERSION}" -) -PULLING_IMAGES=(${PULLING_IMAGES:-${DEFAULT_PULLING_IMAGES[@]}}) - -LOADING_IMAGES=(${LOADING_IMAGES:-${DEFAULT_PULLING_IMAGES[@]}} "docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${PROJECT_VERSION}" - "docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${PROJECT_VERSION}") -# cleanup on exit (useful for running locally) - -cleanup() { - "${KIND}" delete cluster || true - rm -rf "${BIN_DIR}" -} -trap cleanup EXIT - -# util to install the latest kind version into ${BIN_DIR} -install_latest_kind() { - # clone kind into a tempdir within BIN_DIR - local tmp_dir - tmp_dir="$(TMPDIR="${BIN_DIR}" mktemp -d "${BIN_DIR}/kind-source.XXXXX")" - cd "${tmp_dir}" || exit - git clone https://github.com/kubernetes-sigs/kind && cd ./kind - make install INSTALL_DIR="${BIN_DIR}" -} - -# util to install a released kind version into ${BIN_DIR} -install_kind_release() { - VERSION="v0.11.1" - KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-linux-amd64" - if [[ "$OSTYPE" == "darwin"* ]]; then - KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-darwin-amd64" - elif [[ "$OSTYPE" == "cygwin" ]]; then - KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64" - elif [[ "$OSTYPE" == "msys" ]]; then - KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64" - elif [[ "$OSTYPE" == "win32" ]]; then - KIND_BINARY_URL="https://github.com/kubernetes-sigs/kind/releases/download/${VERSION}/kind-windows-amd64" - else - echo "Unknown OS, using linux binary" - fi - wget -O "${KIND}" "${KIND_BINARY_URL}" - chmod +x "${KIND}" -} - -# util to install a released istio version into ${BIN_DIR} -install_istio_release() { - ISTIO_BINARY_URL="https://github.com/istio/istio/releases/download/$ISTIO_VERSION/istio-$ISTIO_VERSION-linux-amd64.tar.gz" - if [[ "$OSTYPE" == "darwin"* ]]; then - ISTIO_BINARY_URL="https://github.com/istio/istio/releases/download/$ISTIO_VERSION/istio-$ISTIO_VERSION-osx-arm64.tar.gz" - else - echo "Unknown OS, using linux binary" - fi - # seems like wget can't do both --output-document and --directory-prefix? At least on my Mac - # this is the case. To be on the safe side, download, then rename - wget --directory-prefix "${ISTIO}" "${ISTIO_BINARY_URL}" - find "${ISTIO}" -type f -name "istio-*.tar.gz" -exec mv "{}" "${ISTIO}/istio.tar.gz" \; - tar -xf "$BIN_DIR/istio/istio.tar.gz" -C "$BIN_DIR/istio" - chmod +x "${ISTIO}/istio-$ISTIO_VERSION/bin/istioctl" - export PATH=$PATH:"$ISTIO/istio-$ISTIO_VERSION/bin" - - if ! [ -x "$(command -v istioctl)" ]; then - echo 'Problem installing istioctl, check the script' - exit 1 - fi -} - -enable_istio() { - kubectl create namespace istio-test - kubectl label namespace istio-test istio-injection=enabled - install_istio_release - # remove taint, otherwise istio will not start - kubectl taint node kind-control-plane node-role.kubernetes.io/master:NoSchedule- - - # for Mac M1 : https://github.com/istio/istio/issues/21094#issuecomment-956117650 - istioctl install --set profile=demo -y -} - -main() { - # get kind - install_kind_release - - # create a cluster - cd $CURRENT_DIR - - #TODO what happens if cluster is already there???? - "${KIND}" create cluster --config=kind-config.yaml -v 2147483647 - - # set KUBECONFIG to point to the cluster - kubectl cluster-info --context kind-kind - - # pulling necessary images for setting up the integration test environment - for i in "${PULLING_IMAGES[@]}"; do - echo "Pull images for prepping testing environment: $i" - docker pull $i - done - for i in "${LOADING_IMAGES[@]}"; do - echo "Loading images into Kind: $i" - "${KIND}" load docker-image $i - done - - # istio - install_istio_release - enable_istio - - #setup nginx ingress - kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml - sleep 5 # hold 5 sec so that the pods can be created - kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=420s - - - - # This creates the service account, role, and role binding necessary for Spring Cloud k8s apps - kubectl apply -f ./permissions.yaml - - # cd ${BIN_DIR} - # curl -L https://istio.io/downloadIstio | sh - - #"${ISTIOCTL}" install --set profile=demo - - # running tests.. - if [[ -z ${CIRCLECI+x} ]]; then - run_tests "${INTEGRATION_PROJECTS[@]}" - else - #This splits projects across all circleci instances, it returns a list of projects separated by a space - SPLIT_PROJECTS=$(printf "%s\n" "${INTEGRATION_PROJECTS[@]}" | circleci tests split) - SPLIT_PROJECTS=$(echo $SPLIT_PROJECTS | sed 's/ /,/g') - echo "split tests $SPLIT_PROJECTS" - #This splits the projects back into an array so we can iterate over them - IFS=',' read -ra PROJECTS <<< "$SPLIT_PROJECTS" - echo "${PROJECTS[@]}" - run_tests "${PROJECTS[@]}" - fi - - # teardown will happen automatically on exit -} - -run_tests() { - arr=("$@") - cd ../spring-cloud-kubernetes-test-support - ${MVN} clean install - cd ../spring-cloud-kubernetes-integration-tests - for p in "${arr[@]}"; do - echo "Running test: $p" - cd $p - ${MVN} spring-boot:build-image \ - -Dspring-boot.build-image.imageName=docker.io/springcloud/$p:${PROJECT_VERSION} -Dspring-boot.build-image.builder=paketobuildpacks/builder - "${KIND}" load docker-image docker.io/springcloud/$p:${PROJECT_VERSION} - # empty excludeITTests, so that integration tests will run - ${MVN} clean install -DexcludeITTests= - cd .. - done -} - -main diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml index 2e2d26537c..0d2559726e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml @@ -5,8 +5,9 @@ org.springframework.cloud spring-cloud-kubernetes-integration-tests -3.0.0-SNAPSHOT + 3.0.0-SNAPSHOT + 4.0.0 spring-cloud-kubernetes-client-config-it @@ -16,6 +17,10 @@ org.springframework.cloud spring-cloud-starter-kubernetes-client-config + + org.springframework.cloud + spring-cloud-starter-bootstrap + org.springframework.cloud spring-cloud-kubernetes-test-support @@ -47,6 +52,22 @@ test + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -60,6 +81,56 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + ${testsToRun} + + + + + + + + @@ -99,6 +170,7 @@ springcloud/${project.artifactId}:${project.version} + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml index e81b66d521..7ea719a7a6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml @@ -3,9 +3,3 @@ management: web: exposure: include: "*" -#logging: -# level: -# org: -# springframework: -# cloud: -# kubernetes: DEBUG diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml index 723f28c958..7df1beb56c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml @@ -1,7 +1,6 @@ spring: cloud: kubernetes: - enabled: true secrets: enable-api: true reload: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml index d4075ec06d..1a857f32ae 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml @@ -1,7 +1,4 @@ spring: application: name: spring-cloud-kubernetes-client-config-it - cloud: - kubernetes: - enabled: false diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java index 19a3949f00..4115452d32 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java @@ -16,11 +16,10 @@ package org.springframework.cloud.kubernetes.client.config.it; -import java.io.IOException; import java.time.Duration; import java.util.Map; +import java.util.Objects; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.apis.NetworkingV1Api; @@ -29,29 +28,34 @@ import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import org.testcontainers.shaded.org.awaitility.Awaitility; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; /** * @author Ryan Baxter */ -public class ConfigMapAndSecretIT { +class ConfigMapAndSecretIT { - private static final Log LOG = LogFactory.getLog(ConfigMapAndSecretIT.class); + private static final String PROPERTY_URL = "localhost:80/myProperty"; + + private static final String SECRET_URL = "localhost:80/mySecret"; private static final String SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-config-it-deployment"; @@ -61,15 +65,8 @@ public class ConfigMapAndSecretIT { private static final String NAMESPACE = "default"; - private static final String MYPROPERTY_URL = "http://localhost:80/client-config-it/myProperty"; - - private static final String MYSECRET_URL = "http://localhost:80/client-config-it/mySecret"; - private static final String APP_NAME = "spring-cloud-kubernetes-client-config-it"; - // though not obvious, we need this, even if it is "unused" - private static ApiClient client; - private static CoreV1Api api; private static AppsV1Api appsApi; @@ -78,17 +75,28 @@ public class ConfigMapAndSecretIT { private static K8SUtils k8SUtils; + private static final K3sContainer K3S = Commons.container(); + @BeforeAll - public static void setup() throws Exception { - client = createApiClient(); + static void setup() throws Exception { + K3S.start(); + Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); api = new CoreV1Api(); appsApi = new AppsV1Api(); networkingApi = new NetworkingV1Api(); k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); } @AfterEach - public void after() throws Exception { + void after() throws Exception { appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, "metadata.name=" + K8S_CONFIG_CLIENT_IT_NAME, null, null, null, null, null, null, null, null, null); api.deleteNamespacedService(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, NAMESPACE, null, null, null, null, null, null); @@ -97,56 +105,8 @@ public void after() throws Exception { api.deleteNamespacedSecret(APP_NAME, NAMESPACE, null, null, null, null, null, null); } - public void testConfigMapAndSecretRefresh() throws Exception { - - RestTemplate rest = new RestTemplateBuilder().build(); - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)) - .until(() -> rest.getForEntity(MYPROPERTY_URL, String.class).getStatusCode().is2xxSuccessful()); - - String myProperty = rest.getForObject(MYPROPERTY_URL, String.class); - assertThat(myProperty).isEqualTo("from-config-map"); - String mySecret = rest.getForObject(MYSECRET_URL, String.class); - assertThat(mySecret).isEqualTo("p455w0rd"); - - V1ConfigMap configMap = getConfigK8sClientItConfigMap(); - Map data = configMap.getData(); - data.replace("application.yaml", data.get("application.yaml").replace("from-config-map", "from-unit-test")); - configMap.data(data); - api.replaceNamespacedConfigMap(APP_NAME, NAMESPACE, configMap, null, null, null); - await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)) - .until(() -> rest.getForObject(MYPROPERTY_URL, String.class).equals("from-unit-test")); - myProperty = rest.getForObject(MYPROPERTY_URL, String.class); - assertThat(myProperty).isEqualTo("from-unit-test"); - - V1Secret secret = getConfigK8sClientItCSecret(); - Map secretData = secret.getData(); - secretData.replace("my.config.mySecret", "p455w1rd".getBytes()); - secret.setData(secretData); - api.replaceNamespacedSecret(APP_NAME, NAMESPACE, secret, null, null, null); - await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)) - .until(() -> rest.getForObject(MYSECRET_URL, String.class).equals("p455w1rd")); - mySecret = rest.getForObject(MYSECRET_URL, String.class); - assertThat(mySecret).isEqualTo("p455w1rd"); - - } - @Test - public void testConfigMapAndSecretWatchRefresh() throws Exception { + void testConfigMapAndSecretWatchRefresh() throws Exception { deployConfigK8sClientIt(); // Check to make sure the controller deployment is ready @@ -155,7 +115,7 @@ public void testConfigMapAndSecretWatchRefresh() throws Exception { } @Test - public void testConfigMapAndSecretPollingRefresh() throws Exception { + void testConfigMapAndSecretPollingRefresh() throws Exception { deployConfigK8sClientPollingIt(); // Check to make sure the controller deployment is ready @@ -163,13 +123,47 @@ public void testConfigMapAndSecretPollingRefresh() throws Exception { testConfigMapAndSecretRefresh(); } + void testConfigMapAndSecretRefresh() throws Exception { + + WebClient.Builder builder = builder(); + WebClient propertyClient = builder.baseUrl(PROPERTY_URL).build(); + + String property = propertyClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + assertThat(property).isEqualTo("from-config-map"); + + WebClient secretClient = builder.baseUrl(SECRET_URL).build(); + String secret = secretClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + assertThat(secret).isEqualTo("p455w0rd"); + + V1ConfigMap configMap = getConfigK8sClientItConfigMap(); + Map data = configMap.getData(); + data.replace("application.yaml", data.get("application.yaml").replace("from-config-map", "from-unit-test")); + configMap.data(data); + api.replaceNamespacedConfigMap(APP_NAME, NAMESPACE, configMap, null, null, null); + Awaitility.await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)) + .until(() -> propertyClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block() + .equals("from-unit-test")); + V1Secret v1Secret = getConfigK8sClientItCSecret(); + Map secretData = v1Secret.getData(); + secretData.replace("my.config.mySecret", "p455w1rd".getBytes()); + v1Secret.setData(secretData); + api.replaceNamespacedSecret(APP_NAME, NAMESPACE, v1Secret, null, null, null); + Awaitility.await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)).until(() -> secretClient + .method(HttpMethod.GET).retrieve().bodyToMono(String.class).block().equals("p455w1rd")); + } + private static void deployConfigK8sClientIt() throws Exception { k8SUtils.waitForDeploymentToBeDeleted(K8S_CONFIG_CLIENT_IT_NAME, NAMESPACE); api.createNamespacedSecret(NAMESPACE, getConfigK8sClientItCSecret(), null, null, null); api.createNamespacedConfigMap(NAMESPACE, getConfigK8sClientItConfigMap(), null, null, null); appsApi.createNamespacedDeployment(NAMESPACE, getConfigK8sClientItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getConfigK8sClientItService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getConfigK8sClientItIngress(), null, null, null); + + V1Ingress ingress = getConfigK8sClientItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); } private static void deployConfigK8sClientPollingIt() throws Exception { @@ -215,4 +209,12 @@ private static V1Secret getConfigK8sClientItCSecret() throws Exception { return (V1Secret) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-client-config-it-secret.yaml"); } + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml index d65600fc03..083811f404 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /client-config-it(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml index de824a2279..c779ad35b7 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml @@ -48,6 +48,27 @@ test + + org.springframework.boot + spring-boot-starter-webflux + + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -61,6 +82,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java index 0b75a1e4ab..728ee4a43c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java @@ -19,15 +19,18 @@ import java.util.List; import java.util.Map; +import reactor.netty.http.client.HttpClient; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; /** * @author Ryan Baxter @@ -37,6 +40,8 @@ @RestController public class KubernetesClientLoadBalancerApplicationIt { + private static final String URL = "http://servicea-wiremock/__admin/mappings"; + private final DiscoveryClient discoveryClient; public KubernetesClientLoadBalancerApplicationIt(DiscoveryClient discoveryClien) { @@ -49,13 +54,15 @@ public static void main(String[] args) { @Bean @LoadBalanced - RestTemplate restTemplate() { - return new RestTemplateBuilder().build(); + WebClient.Builder client() { + return WebClient.builder(); } - @GetMapping("/servicea") + @GetMapping("/loadbalancer-it/servicea") + @SuppressWarnings("unchecked") public Map greeting() { - return restTemplate().getForObject("http://servicea-wiremock/__admin/mappings", Map.class); + return (Map) client().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) + .baseUrl(URL).build().method(HttpMethod.GET).retrieve().bodyToMono(Map.class).block(); } @GetMapping("/services") diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java index e15157bd54..a02abd2276 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java @@ -16,12 +16,10 @@ package org.springframework.cloud.kubernetes.client.loadbalancer.it; -import java.io.IOException; import java.time.Duration; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.Objects; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; @@ -29,33 +27,34 @@ import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; /** * @author Ryan Baxter */ -public class LoadBalancerIT { +class LoadBalancerIT { - private static final Log LOG = LogFactory.getLog(LoadBalancerIT.class); - - private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment"; - - private static final String WIREMOCK_APP_NAME = "servicea-wiremock"; + private static final String SERVICE_URL = "localhost:80/loadbalancer-it/servicea"; private static final String SPRING_CLOUD_K8S_LOADBALANCER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-loadbalancer-it-deployment"; @@ -63,54 +62,56 @@ public class LoadBalancerIT { private static final String NAMESPACE = "default"; - private ApiClient client; + private static CoreV1Api api; - private CoreV1Api api; + private static AppsV1Api appsApi; - private AppsV1Api appsApi; + private static NetworkingV1Api networkingApi; - private NetworkingV1Api networkingApi; + private static K8SUtils k8SUtils; - private K8SUtils k8SUtils; + private static final K3sContainer K3S = Commons.container(); - @BeforeEach - public void setup() throws Exception { - this.client = createApiClient(); - this.api = new CoreV1Api(); - this.appsApi = new AppsV1Api(); - this.networkingApi = new NetworkingV1Api(); - this.k8SUtils = new K8SUtils(api, appsApi); - - deployWiremock(); + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + networkingApi = new NetworkingV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + } - // Check to make sure the wiremock deployment is ready - k8SUtils.waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, NAMESPACE); + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S); + k8SUtils.removeWiremockImage(); + } - // Check to see if endpoint is ready - k8SUtils.waitForEndpointReady(WIREMOCK_APP_NAME, NAMESPACE); + @BeforeEach + void setup() throws Exception { + k8SUtils.deployWiremock(NAMESPACE, false, K3S); + } + @AfterEach + void afterEach() throws Exception { + cleanup(); + k8SUtils.cleanUpWiremock(NAMESPACE); } @Test - public void testLoadBalancerServiceMode() throws Exception { - try { - deployLoadbalancerServiceIt(); - testLoadBalancer(); - } - finally { - cleanup(); - } + void testLoadBalancerServiceMode() throws Exception { + deployLoadbalancerServiceIt(); + testLoadBalancer(); } @Test - public void testLoadBalancerPodMode() throws Exception { - try { - deployLoadbalancerPodIt(); - testLoadBalancer(); - } - finally { - cleanup(); - } + void testLoadBalancerPodMode() throws Exception { + deployLoadbalancerPodIt(); + testLoadBalancer(); } private void cleanup() throws ApiException { @@ -125,50 +126,37 @@ private void cleanup() throws ApiException { private void testLoadBalancer() { // Check to make sure the controller deployment is ready k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_LOADBALANCER_DEPLOYMENT_NAME, NAMESPACE); - RestTemplate rest = new RestTemplateBuilder().build(); - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).ignoreExceptions() - .until(() -> rest.getForEntity("http://localhost:80/loadbalancer-it/servicea", String.class) - .getStatusCode().is2xxSuccessful()); - Map result = rest.getForObject("http://localhost:80/loadbalancer-it/servicea", Map.class); - assertThat(result.containsKey("mappings")).isTrue(); - assertThat(result.containsKey("meta")).isTrue(); - } + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl(SERVICE_URL).build(); - @AfterEach - public void after() throws Exception { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null); + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); + @SuppressWarnings("unchecked") + Map result = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); - api.deleteNamespacedService(WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null); - networkingApi.deleteNamespacedIngress("wiremock-ingress", NAMESPACE, null, null, null, null, null, null); + Assertions.assertThat(result.containsKey("mappings")).isTrue(); + Assertions.assertThat(result.containsKey("meta")).isTrue(); } private void deployLoadbalancerServiceIt() throws Exception { appsApi.createNamespacedDeployment(NAMESPACE, getLoadbalancerServiceItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getLoadbalancerItService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getLoadbalancerItIngress(), null, null, null); + deployIngress(); } private void deployLoadbalancerPodIt() throws Exception { appsApi.createNamespacedDeployment(NAMESPACE, getLoadbalancerPodItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getLoadbalancerItService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getLoadbalancerItIngress(), null, null, null); + deployIngress(); + } + + private void deployIngress() throws Exception { + V1Ingress ingress = getLoadbalancerItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); } private V1Deployment getLoadbalancerServiceItDeployment() throws Exception { @@ -189,12 +177,6 @@ private V1Deployment getLoadbalancerPodItDeployment() throws Exception { return deployment; } - private void deployWiremock() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getWireockDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null, null); - } - private V1Ingress getLoadbalancerItIngress() throws Exception { return (V1Ingress) K8SUtils .readYamlFromClasspath("spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml"); @@ -205,16 +187,12 @@ private V1Service getLoadbalancerItService() throws Exception { .readYamlFromClasspath("spring-cloud-kubernetes-client-loadbalancer-it-service.yaml"); } - private V1Ingress getWiremockIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml"); - } - - private V1Service getWiremockAppService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml"); + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); } - private V1Deployment getWireockDeployment() throws Exception { - return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml"); + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml index f63e9f038c..8e44f4e61e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /loadbalancer-it(/|$)(.*) + - path: /loadbalancer-it/ pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9f9..9d28eb6277 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: servicea-wiremock - image: rodolpheche/wiremock:2.27.2 + image: wiremock/wiremock:2.32.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml index 2d278b58d6..8786d59a7d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: wiremock-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /wiremock(/|$)(.*) + - path: /wiremock/ pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java deleted file mode 100644 index 9d7f339e33..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.client.reactive.discovery.it; - -import java.io.IOException; -import java.time.Duration; -import java.util.Arrays; -import java.util.Map; - -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.AppsV1Api; -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.apis.NetworkingV1Api; -import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.openapi.models.V1Ingress; -import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; - -/** - * @author Ryan Baxter - */ -public class ReactiveDiscoveryClientIT { - - private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientIT.class); - - private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment"; - - private static final String WIREMOCK_APP_NAME = "servicea-wiremock"; - - private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-reactive-discovery-it-deployment"; - - private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME = "spring-cloud-kubernetes-client-reactive-discovery-it"; - - private static final String NAMESPACE = "default"; - - private ApiClient client; - - private CoreV1Api api; - - private AppsV1Api appsApi; - - private NetworkingV1Api networkingApi; - - private K8SUtils k8SUtils; - - @BeforeEach - public void setup() throws Exception { - this.client = createApiClient(); - this.api = new CoreV1Api(); - this.appsApi = new AppsV1Api(); - this.networkingApi = new NetworkingV1Api(); - this.k8SUtils = new K8SUtils(api, appsApi); - - deployWiremock(); - - // Check to make sure the wiremock deployment is ready - k8SUtils.waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, NAMESPACE); - - // Check to see if endpoint is ready - k8SUtils.waitForEndpointReady(WIREMOCK_APP_NAME, NAMESPACE); - - } - - @AfterEach - public void after() throws Exception { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null); - - api.deleteNamespacedService(WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null); - networkingApi.deleteNamespacedIngress("wiremock-ingress", NAMESPACE, null, null, null, null, null, null); - - } - - @Test - public void testReactiveDiscoveryClient() throws Exception { - try { - deployReactiveDiscoveryIt(); - testLoadBalancer(); - testHealth(); - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - cleanup(); - } - } - - private void testHealth() { - RestTemplate rest = createRestTemplate(); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(1)) - .until(() -> rest - .getForEntity("http://localhost:80/reactive-discovery-it/actuator/health", String.class) - .getStatusCode().is2xxSuccessful()); - - Map health = rest.getForObject("http://localhost:80/reactive-discovery-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue(); - Map discoveryComposite = (Map) components.get("discoveryComposite"); - assertThat(discoveryComposite.get("status")).isEqualTo("UP"); - } - - private void cleanup() throws ApiException { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, null, null, null, null, null, - null, null, null, null); - api.deleteNamespacedService(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, NAMESPACE, null, null, null, null, - null, null); - networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); - } - - private void testLoadBalancer() throws Exception { - // Check to make sure the controller deployment is ready - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, NAMESPACE); - RestTemplate rest = createRestTemplate(); - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(1)) - .until(() -> rest.getForEntity("http://localhost:80/reactive-discovery-it/services", String.class) - .getStatusCode().is2xxSuccessful()); - String result = rest.getForObject("http://localhost:80/reactive-discovery-it/services", String.class); - assertThat(Arrays.stream(result.split(",")).anyMatch(s -> "servicea-wiremock".equalsIgnoreCase(s))).isTrue(); - - } - - private RestTemplate createRestTemplate() { - RestTemplate rest = new RestTemplateBuilder().build(); - - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - return rest; - } - - private void deployReactiveDiscoveryIt() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getReactiveDiscoveryItDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getReactiveDiscoveryService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getReactiveDiscoveryItIngress(), null, null, null); - } - - private V1Deployment getReactiveDiscoveryItDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); - return deployment; - } - - private V1Service getReactiveDiscoveryService() throws Exception { - return (V1Service) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml"); - } - - private V1Ingress getReactiveDiscoveryItIngress() throws Exception { - return (V1Ingress) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml"); - } - - private void deployWiremock() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getWiremockDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null, null); - } - - private V1Ingress getWiremockIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml"); - } - - private V1Service getWiremockAppService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml"); - } - - private V1Deployment getWiremockDeployment() throws Exception { - return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml deleted file mode 100644 index df3aad1597..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-client-reactive-discovery-it - name: spring-cloud-kubernetes-client-reactive-discovery-it -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-client-reactive-discovery-it - type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/deployment-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/deployment-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/deployment-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/service-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/service-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/service-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml similarity index 58% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml index 968ae912f1..b7c3fc58f4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - spring-cloud-kubernetes-client-reactive-discovery-client-it + spring-cloud-kubernetes-client-reactive-discoveryclient-it @@ -47,6 +47,22 @@ test + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -60,6 +76,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/skaffold.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/skaffold.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/skaffold.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml similarity index 67% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml index cd4028226c..978e0cded1 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml @@ -1,3 +1,7 @@ +spring: + webflux: + base-path: /reactive-discovery-it + management: endpoint: health: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java new file mode 100644 index 0000000000..766ba93775 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java @@ -0,0 +1,197 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.reactive.discovery.it; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1Service; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * @author Ryan Baxter + */ +class ReactiveDiscoveryClientIT { + + private static final String HEALTH_URL = "localhost:80/reactive-discovery-it/actuator/health"; + + private static final String SERVICES_URL = "localhost:80/reactive-discovery-it/services"; + + private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment"; + + private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME = "spring-cloud-kubernetes-client-reactive-discoveryclient-it"; + + private static final String NAMESPACE = "default"; + + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + networkingApi = new NetworkingV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S); + k8SUtils.removeWiremockImage(); + } + + @BeforeEach + void setup() throws Exception { + k8SUtils.deployWiremock(NAMESPACE, false, K3S); + } + + @AfterEach + void after() throws Exception { + k8SUtils.cleanUpWiremock(NAMESPACE); + cleanup(); + } + + @Test + void testReactiveDiscoveryClient() throws Exception { + deployReactiveDiscoveryIt(); + testLoadBalancer(); + testHealth(); + } + + @SuppressWarnings("unchecked") + private void testHealth() { + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl(HEALTH_URL).build(); + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); + @SuppressWarnings("unchecked") + Map health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + + Map components = (Map) health.get("components"); + + assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue(); + Map discoveryComposite = (Map) components.get("discoveryComposite"); + assertThat(discoveryComposite.get("status")).isEqualTo("UP"); + } + + private void cleanup() throws ApiException { + appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, + "metadata.name=" + SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, null, null, null, null, null, + null, null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, NAMESPACE, null, null, null, null, + null, null); + networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); + } + + private void testLoadBalancer() { + + // Check to make sure the controller deployment is ready + k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, NAMESPACE); + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl(SERVICES_URL).build(); + String servicesResponse = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block(); + + Assertions + .assertThat(Arrays.stream(servicesResponse.split(",")).anyMatch("servicea-wiremock"::equalsIgnoreCase)) + .isTrue(); + + } + + private void deployIngress(V1Ingress ingress) throws Exception { + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private void deployReactiveDiscoveryIt() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getReactiveDiscoveryItDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getReactiveDiscoveryService(), null, null, null); + deployIngress(getReactiveDiscoveryItIngress()); + } + + private V1Deployment getReactiveDiscoveryItDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) K8SUtils + .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml"); + String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" + + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private V1Service getReactiveDiscoveryService() throws Exception { + return (V1Service) K8SUtils + .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml"); + } + + private V1Ingress getReactiveDiscoveryItIngress() throws Exception { + return (V1Ingress) K8SUtils + .readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml new file mode 100644 index 0000000000..4d1ce28ef1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment +spec: + selector: + matchLabels: + app: spring-cloud-kubernetes-client-reactive-discoveryclient-it + template: + metadata: + labels: + app: spring-cloud-kubernetes-client-reactive-discoveryclient-it + spec: + serviceAccountName: spring-cloud-kubernetes-serviceaccount + containers: + - name: spring-cloud-kubernetes-client-reactive-discoveryclient-it + image: docker.io/springcloud/spring-cloud-kubernetes-client-reactive-discoveryclient-it + imagePullPolicy: IfNotPresent + readinessProbe: + httpGet: + port: 8080 + path: /reactive-discovery-it/actuator/health/readiness + livenessProbe: + httpGet: + port: 8080 + path: /reactive-discovery-it/actuator/health/liveness + ports: + - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml similarity index 65% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml index 305cc67bf1..6f361e0e41 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml @@ -3,16 +3,14 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /reactive-discovery-it(/|$)(.*) - pathType: Prefix + - path: /reactive-discovery-it + pathType: ImplementationSpecific backend: service: - name: spring-cloud-kubernetes-client-reactive-discovery-it + name: spring-cloud-kubernetes-client-reactive-discoveryclient-it port: number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml new file mode 100644 index 0000000000..8d2e27d520 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-client-reactive-discoveryclient-it + name: spring-cloud-kubernetes-client-reactive-discoveryclient-it +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: spring-cloud-kubernetes-client-reactive-discoveryclient-it + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/pom.xml new file mode 100644 index 0000000000..b4215801ed --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/pom.xml @@ -0,0 +1,130 @@ + + + + org.springframework.cloud + spring-cloud-kubernetes-integration-tests + 3.0.0-SNAPSHOT + + 4.0.0 + + spring-cloud-kubernetes-client-secrets-event-reload + + + + org.springframework.cloud + spring-cloud-starter-kubernetes-client-config + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + io.kubernetes + client-java + + + io.kubernetes + client-java-extended + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsApp.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsApp.java new file mode 100644 index 0000000000..72056a2300 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.secrets.event.reload; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * @author wind57 + */ + +@SpringBootApplication +@EnableConfigurationProperties(SecretsProperties.class) +public class SecretsApp { + + public static void main(String[] args) { + SpringApplication.run(SecretsApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsController.java new file mode 100644 index 0000000000..7d40247bb6 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsController.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.secrets.event.reload; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@RestController +public class SecretsController { + + private final SecretsProperties properties; + + public SecretsController(SecretsProperties properties) { + this.properties = properties; + } + + @GetMapping("/key") + public String key() { + return properties.getKey(); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsProperties.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsProperties.java new file mode 100644 index 0000000000..712eef589a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.secrets.event.reload; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author wind57 + */ +@ConfigurationProperties("from.properties") +public class SecretsProperties { + + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key1) { + this.key = key1; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/resources/application.yaml new file mode 100644 index 0000000000..4356bd6ac6 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/main/resources/application.yaml @@ -0,0 +1,20 @@ +logging: + level: + root: DEBUG + +spring: + application: + name: event-reload + config: + import: "kubernetes:" + cloud: + kubernetes: + reload: + enabled: true + strategy: shutdown + mode: event + monitoring-secrets: true + secrets: + enabled: true + enable-api: true + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java new file mode 100644 index 0000000000..a1b32bb58a --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/client/secrets/event/reload/SecretsEventReloadIT.java @@ -0,0 +1,169 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.client.secrets.event.reload; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; + +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1Service; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import org.testcontainers.shaded.org.awaitility.Awaitility; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * @author wind57 + */ +class SecretsEventReloadIT { + + private static final String PROPERTY_URL = "localhost:80/key"; + + private static final String SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-secrets-deployment-event-reload"; + + private static final String K8S_CONFIG_CLIENT_IT_SERVICE_NAME = "spring-cloud-kubernetes-client-secrets-event-reload"; + + private static final String NAMESPACE = "default"; + + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void setup() throws Exception { + K3S.start(); + Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + networkingApi = new NetworkingV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + } + + @AfterEach + void after() throws Exception { + appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, + "metadata.name=" + SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME, null, null, null, null, null, null, + null, null, null); + api.deleteNamespacedService(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, NAMESPACE, null, null, null, null, null, null); + networkingApi.deleteNamespacedIngress("spring-cloud-kubernetes-client-secrets-ingress-event-reload", NAMESPACE, + null, null, null, null, null, null); + api.deleteNamespacedSecret("event-reload", NAMESPACE, null, null, null, null, null, null); + } + + @Test + void testSecretReload() throws Exception { + deployConfigK8sClientIt(); + + // Check to make sure the controller deployment is ready + k8SUtils.waitForDeployment(SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME, NAMESPACE); + testSecretEventReload(); + } + + void testSecretEventReload() throws Exception { + + WebClient.Builder builder = builder(); + WebClient secretClient = builder.baseUrl(PROPERTY_URL).build(); + String secret = secretClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + assertThat(secret).isEqualTo("initial"); + + V1Secret v1Secret = getConfigK8sClientItCSecret(); + Map secretData = v1Secret.getData(); + secretData.replace("application.properties", "from.properties.key: after-change".getBytes()); + v1Secret.setData(secretData); + api.replaceNamespacedSecret("event-reload", NAMESPACE, v1Secret, null, null, null); + + Awaitility.await().timeout(Duration.ofSeconds(120)).pollInterval(Duration.ofSeconds(2)) + .until(() -> secretClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class) + .retryWhen(retrySpec()).block().equals("after-change")); + } + + private static void deployConfigK8sClientIt() throws Exception { + k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME, NAMESPACE); + api.createNamespacedSecret(NAMESPACE, getConfigK8sClientItCSecret(), null, null, null); + appsApi.createNamespacedDeployment(NAMESPACE, getConfigK8sClientItDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getConfigK8sClientItService(), null, null, null); + + V1Ingress ingress = getConfigK8sClientItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private static V1Deployment getConfigK8sClientItDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath("deployment.yaml"); + String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" + + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private static V1Service getConfigK8sClientItService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("service.yaml"); + } + + private static V1Ingress getConfigK8sClientItIngress() throws Exception { + return (V1Ingress) K8SUtils.readYamlFromClasspath("ingress.yaml"); + } + + private static V1Secret getConfigK8sClientItCSecret() throws Exception { + return (V1Secret) K8SUtils.readYamlFromClasspath("secret.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(60, Duration.ofSeconds(2)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/deployment.yaml similarity index 66% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/deployment.yaml index c6687cb382..74cdd1633f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/deployment.yaml @@ -1,20 +1,20 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: spring-cloud-kubernetes-client-reactive-discovery-it-deployment + name: spring-cloud-kubernetes-client-secrets-deployment-event-reload spec: selector: matchLabels: - app: spring-cloud-kubernetes-client-reactive-discovery-it + app: spring-cloud-kubernetes-client-secrets-event-reload template: metadata: labels: - app: spring-cloud-kubernetes-client-reactive-discovery-it + app: spring-cloud-kubernetes-client-secrets-event-reload spec: serviceAccountName: spring-cloud-kubernetes-serviceaccount containers: - - name: spring-cloud-kubernetes-client-reactive-discovery-it - image: docker.io/springcloud/spring-cloud-kubernetes-client-reactive-discovery-client-it + - name: spring-cloud-kubernetes-client-secrets-event-reload + image: docker.io/springcloud/spring-cloud-kubernetes-client-secrets-event-reload imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/ingress.yaml similarity index 58% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/ingress.yaml index 546618fe2a..7c74d61817 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/ingress.yaml @@ -1,18 +1,16 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: nginx-ingress + name: spring-cloud-kubernetes-client-secrets-ingress-event-reload namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /wiremock(/|$)(.*) + - path: / pathType: Prefix backend: service: - name: config-watcher-wiremock + name: spring-cloud-kubernetes-client-secrets-event-reload port: number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/secret.yaml new file mode 100644 index 0000000000..bf3e730da4 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: event-reload + namespace: default +data: + # from.properties.key=initial + application.properties: | + ZnJvbS5wcm9wZXJ0aWVzLmtleT1pbml0aWFsCg== diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/service.yaml new file mode 100644 index 0000000000..e1fb85db7b --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-secrets-event-reload/src/test/resources/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-client-secrets-event-reload + name: spring-cloud-kubernetes-client-secrets-event-reload +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: spring-cloud-kubernetes-client-secrets-event-reload + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml index aecd8f8bd3..c5e73072f5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-kubernetes-integration-tests -3.0.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 @@ -74,6 +74,29 @@ docker-java-transport-httpclient5 test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + @@ -87,6 +110,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java index 4033cee785..a3fb642fe8 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java @@ -38,15 +38,15 @@ public static void main(String[] args) { SpringApplication.run(ConfigWatcherTestApplication.class, args); } - @GetMapping("/") + @GetMapping("/it") public boolean index() { - log.warn("Current value: " + value); + log.info("Current value: " + value); return value; } @Override public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) { - log.warn("Received remote refresh event"); + log.info("Received remote refresh event"); this.value = true; } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/application.yaml index a14ad1f89e..bc8184729f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/application.yaml @@ -9,22 +9,27 @@ spring: autoconfigure: exclude: org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration management: - health: - probes: - enabled: true + endpoint: + health: + probes: + enabled: true --- spring: - profiles: bus-amqp cloud: bus: enabled: true stream: default-binder: rabbit + config: + activate: + on-profile: bus-amqp --- spring: - profiles: bus-kafka cloud: bus: enabled: true stream: - default-binder: kafka \ No newline at end of file + default-binder: kafka + config: + activate: + on-profile: bus-kafka diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/bootstrap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/bootstrap.yaml new file mode 100644 index 0000000000..a8b7721b96 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/resources/bootstrap.yaml @@ -0,0 +1,3 @@ +spring: + application: + name: spring-cloud-kubernetes-configuration-watcher-it diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java index 8c879b7d39..5e9af02178 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshIT.java @@ -20,19 +20,20 @@ import com.github.tomakehurst.wiremock.client.VerificationException; import com.github.tomakehurst.wiremock.client.WireMock; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.apis.NetworkingV1Api; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ConfigMapBuilder; import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -49,66 +50,84 @@ /** * @author Ryan Baxter */ -public class ActuatorRefreshIT { - - private static final String CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME = "config-watcher-wiremock-deployment"; - - private static final String CONFIG_WATCHER_WIREMOCK_APP_NAME = "config-watcher-wiremock"; +class ActuatorRefreshIT { private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-configuration-watcher-deployment"; private static final String SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME = "spring-cloud-kubernetes-configuration-watcher"; + private String configWatcherConfigMapName; + private static final String WIREMOCK_HOST = "localhost"; - private static final String WIREMOCK_PATH = "/wiremock"; + private static final String WIREMOCK_PATH = "/"; private static final int WIREMOCK_PORT = 80; private static final String NAMESPACE = "default"; - private ApiClient client; + private static CoreV1Api api; - private CoreV1Api api; + private static AppsV1Api appsApi; - private AppsV1Api appsApi; + private static K8SUtils k8SUtils; - private NetworkingV1Api networkingApi; + private static final K3sContainer K3S = Commons.container(); - private K8SUtils k8SUtils; + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + k8SUtils.removeWiremockImage(); + } @BeforeEach - public void setup() throws Exception { - this.client = createApiClient(); - this.api = new CoreV1Api(); - this.appsApi = new AppsV1Api(); - this.networkingApi = new NetworkingV1Api(); - this.k8SUtils = new K8SUtils(api, appsApi); - - deployWiremock(); + void setup() throws Exception { deployConfigWatcher(); + k8SUtils.deployWiremock(NAMESPACE, true, K3S); + } - // Check to make sure the wiremock deployment is ready - k8SUtils.waitForDeployment(CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, NAMESPACE); - // Check to see if endpoint is ready - k8SUtils.waitForEndpointReady(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE); - // Check to make sure the controller deployment is ready - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); + @AfterEach + void after() throws Exception { + + appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, + "metadata.name=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, null, null, null, null, null, null, + null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + + api.deleteNamespacedConfigMap(configWatcherConfigMapName, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedConfigMap("servicea-wiremock", NAMESPACE, null, null, null, null, null, null); + k8SUtils.cleanUpWiremock(NAMESPACE); } + /* + * this test loads uses two services: wiremock on port 8080 and configuration-watcher + * on port 8888. we deploy configuration-watcher first and configure it via a + * configmap with the same name. then, we mock the call to actuator/refresh endpoint + * and deploy a new configmap: "servicea-wiremock", this in turn will trigger that + * refresh that we capture and assert for. + */ @Test - public void testActuatorRefresh() throws Exception { - // Configure wiremock to point at the server + void testActuatorRefresh() throws Exception { WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit await().timeout(Duration.ofSeconds(60)).ignoreException(VerificationException.class) .until(() -> stubFor(post(urlEqualTo("/actuator/refresh")).willReturn(aResponse().withStatus(200))) .getResponse().wasConfigured()); // Create new configmap to trigger controller to signal app to refresh - V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_WATCHER_WIREMOCK_APP_NAME) + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName("servicea-wiremock") .addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "bar").build(); api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); @@ -119,67 +138,33 @@ public void testActuatorRefresh() throws Exception { verify(postRequestedFor(urlEqualTo("/actuator/refresh"))); } - @AfterEach - public void after() throws Exception { - - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, null, null, null, null, null, null, - null, null, null); - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, - null, null); - api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, - null); - api.deleteNamespacedService(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null); - networkingApi.deleteNamespacedIngress("nginx-ingress", NAMESPACE, null, null, null, null, null, null); - api.deleteNamespacedConfigMap(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, - null); - api.deleteNamespacedConfigMap(CONFIG_WATCHER_WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null); - // Check to make sure the controller deployment is deleted - k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); - k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, NAMESPACE); - } - private void deployConfigWatcher() throws Exception { - api.createNamespacedConfigMap(NAMESPACE, getConfigWatcherConfigMap(), null, null, null); + V1ConfigMap configMap = getConfigWatcherConfigMap(); + configWatcherConfigMapName = configMap.getMetadata().getName(); + api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null, null); + + // Check to make sure the controller deployment is ready + k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); } private V1Deployment getConfigWatcherDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath( + "config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml"); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); return deployment; } - private void deployWiremock() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getWiremockDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null, null); - } - private V1Service getConfigWatcherService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml"); + return (V1Service) K8SUtils + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); } private V1ConfigMap getConfigWatcherConfigMap() throws Exception { return (V1ConfigMap) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); - } - - private V1Ingress getWiremockIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml"); - } - - private V1Service getWiremockAppService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml"); - } - - private V1Deployment getWiremockDeployment() throws Exception { - return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml"); + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java index c928fbbd27..cd648a7bf2 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java @@ -16,10 +16,10 @@ package org.springframework.cloud.kubernetes.configuration.watcher; -import java.io.IOException; import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.apis.NetworkingV1Api; @@ -28,19 +28,23 @@ import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; -import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; @@ -48,9 +52,7 @@ /** * @author Kris Iyer */ -public class ActuatorRefreshKafkaIT { - - private final Log log = LogFactory.getLog(getClass()); +class ActuatorRefreshKafkaIT { private static final String CONFIG_WATCHER_IT_IMAGE = "spring-cloud-kubernetes-configuration-watcher-it"; @@ -70,23 +72,42 @@ public class ActuatorRefreshKafkaIT { private static final String ZOOKEEPER_DEPLOYMENT = "zookeeper"; - private ApiClient client; + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); - private CoreV1Api api; + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); - private AppsV1Api appsApi; + Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); - private NetworkingV1Api networkingApi; + Commons.validateImage(CONFIG_WATCHER_IT_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_IT_IMAGE, K3S); - private K8SUtils k8SUtils; + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + networkingApi = new NetworkingV1Api(); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.cleanUp(CONFIG_WATCHER_IT_IMAGE, K3S); + } @BeforeEach - public void setup() throws Exception { - this.client = createApiClient(); - this.api = new CoreV1Api(); - this.appsApi = new AppsV1Api(); - this.networkingApi = new NetworkingV1Api(); - this.k8SUtils = new K8SUtils(api, appsApi); + void setup() throws Exception { deployZookeeper(); deployKafka(); @@ -94,68 +115,23 @@ public void setup() throws Exception { deployConfigWatcher(); // Check to make sure the controller deployment is ready - k8SUtils.waitForDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE); - k8SUtils.waitForDeployment(KAFKA_BROKER, NAMESPACE); - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE); - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); - } - - @Test - public void testRefresh() throws Exception { - // Create new configmap to trigger controller to signal app to refresh - V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_WATCHER_IT_IMAGE) - .addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "hello world") - .build(); - api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); - RestTemplate rest = new RestTemplateBuilder().build(); - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - log.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)).until( - () -> rest.getForEntity("http://localhost:80/it", String.class).getStatusCode().is2xxSuccessful()); - // Wait a bit before we verify - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(90)).until(() -> { - Boolean value = rest.getForObject("http://localhost:80/it", Boolean.class); - log.info("Returned " + value + " from http://localhost:80/it"); - return value; - }); - - assertThat(rest.getForObject("http://localhost:80/it", Boolean.class)).isTrue(); + waitForDeployment(ZOOKEEPER_DEPLOYMENT); + waitForDeployment(KAFKA_BROKER); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME); } @AfterEach - public void after() throws Exception { - appsApi.deleteNamespacedDeployment(KAFKA_BROKER, NAMESPACE, null, null, null, null, null, null); - api.deleteNamespacedService(KAFKA_SERVICE, NAMESPACE, null, null, null, null, null, null); - appsApi.deleteNamespacedDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE, null, null, null, null, null, null); - api.deleteNamespacedService(ZOOKEEPER_SERVICE, NAMESPACE, null, null, null, null, null, null); - - api.deleteNamespacedService(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null, null, null, null); - api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, - null); + void after() throws Exception { - appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE, null, null, null, - null, null, null); - appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE, null, null, - null, null, null, null); + cleanUpKafka(); + cleanUpZookeeper(); + cleanUpServices(); + cleanUpDeployments(); networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); - api.deleteNamespacedConfigMap(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, - null); - api.deleteNamespacedConfigMap(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null, null, null, null); + cleanUpConfigMaps(); // Check to make sure the controller deployment is deleted k8SUtils.waitForDeploymentToBeDeleted(KAFKA_BROKER, NAMESPACE); @@ -164,10 +140,39 @@ public void after() throws Exception { k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE); } + @Test + void testRefresh() throws Exception { + // Create new configmap to trigger controller to signal app to refresh + V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_WATCHER_IT_IMAGE) + .addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "hello world") + .build(); + api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/it").build(); + + Boolean[] value = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { + value[0] = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class).retryWhen(retrySpec()) + .block(); + return value[0]; + }); + + Assertions.assertThat(value[0]).isTrue(); + } + + private void waitForDeployment(String deploymentName) { + await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS) + .until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE)); + } + private void deployTestApp() throws Exception { appsApi.createNamespacedDeployment(NAMESPACE, getItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getItAppService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getItIngress(), null, null, null); + + V1Ingress ingress = getItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); } private void deployConfigWatcher() throws Exception { @@ -177,72 +182,110 @@ private void deployConfigWatcher() throws Exception { } private void deployZookeeper() throws Exception { - System.out.println("deploy deployZookeeper"); api.createNamespacedService(NAMESPACE, getZookeeperService(), null, null, null); - System.out.println("created getZookeeperService"); - appsApi.createNamespacedDeployment(NAMESPACE, getZookeeperDeployment(), null, null, null); - System.out.println("created getZookeeperDeployment"); + V1Deployment deployment = getZookeeperDeployment(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "zookeeper", K3S); + appsApi.createNamespacedDeployment(NAMESPACE, deployment, null, null, null); } private void deployKafka() throws Exception { - System.out.println("deploy kafka"); api.createNamespacedService(NAMESPACE, getKafkaService(), null, null, null); - System.out.println("created getKafkaService"); + V1Deployment deployment = getKafkaDeployment(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "kafka", K3S); appsApi.createNamespacedDeployment(NAMESPACE, getKafkaDeployment(), null, null, null); - System.out.println("created getKafkaDeployment"); + } + + private void cleanUpKafka() throws Exception { + appsApi.deleteNamespacedDeployment(KAFKA_BROKER, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(KAFKA_SERVICE, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpZookeeper() throws Exception { + appsApi.deleteNamespacedDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(ZOOKEEPER_SERVICE, NAMESPACE, null, null, null, null, null, null); } private V1Deployment getConfigWatcherDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath( + "app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml"); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); return deployment; } private V1Deployment getItDeployment() throws Exception { - String urlString = "spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml"; + String urlString = "app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml"; V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); return deployment; } private V1Service getConfigWatcherService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml"); + return (V1Service) K8SUtils + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); } private V1ConfigMap getConfigWatcherConfigMap() throws Exception { return (V1ConfigMap) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); } private V1Service getItAppService() throws Exception { return (V1Service) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-service.yaml"); + .readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml"); } private V1Ingress getItIngress() throws Exception { return (V1Ingress) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml"); + .readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml"); } private V1Deployment getKafkaDeployment() throws Exception { - return (V1Deployment) K8SUtils.readYamlFromClasspath("kafka-deployment.yaml"); + return (V1Deployment) K8SUtils.readYamlFromClasspath("kafka/kafka-deployment.yaml"); } private V1Service getKafkaService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("kafka-service.yaml"); + return (V1Service) K8SUtils.readYamlFromClasspath("kafka/kafka-service.yaml"); } private V1Deployment getZookeeperDeployment() throws Exception { - return (V1Deployment) K8SUtils.readYamlFromClasspath("zookeeper-deployment.yaml"); + return (V1Deployment) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-deployment.yaml"); } private V1Service getZookeeperService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("zookeeper-service.yaml"); + return (V1Service) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-service.yaml"); + } + + private void cleanUpConfigMaps() throws Exception { + api.deleteNamespacedConfigMap(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + api.deleteNamespacedConfigMap(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null, null, null, null); + } + + private void cleanUpDeployments() throws Exception { + appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE, null, null, null, + null, null, null); + appsApi.deleteNamespacedDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE, null, null, + null, null, null, null); + } + + private void cleanUpServices() throws Exception { + api.deleteNamespacedService(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null, null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java index 25d1609dbf..753fb02986 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java @@ -16,10 +16,10 @@ package org.springframework.cloud.kubernetes.configuration.watcher; -import java.io.IOException; import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.apis.NetworkingV1Api; @@ -29,19 +29,23 @@ import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1ReplicationController; import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; -import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; @@ -49,11 +53,7 @@ /** * @author Ryan Baxter */ -public class ActuatorRefreshRabbitMQIT { - - private static final Log LOG = LogFactory.getLog(ActuatorRefreshRabbitMQIT.class); - - private Log log = LogFactory.getLog(getClass()); +class ActuatorRefreshRabbitMQIT { private static final String CONFIG_WATCHER_IT_IMAGE = "spring-cloud-kubernetes-configuration-watcher-it"; @@ -67,23 +67,42 @@ public class ActuatorRefreshRabbitMQIT { private static final String RABBIT_MQ_CONTROLLER_NAME = "rabbitmq-controller"; - private ApiClient client; + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; - private CoreV1Api api; + private static final K3sContainer K3S = Commons.container(); - private AppsV1Api appsApi; + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); - private NetworkingV1Api networkingApi; + Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); - private K8SUtils k8SUtils; + Commons.validateImage(CONFIG_WATCHER_IT_IMAGE, K3S); + Commons.loadSpringCloudKubernetesImage(CONFIG_WATCHER_IT_IMAGE, K3S); + + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + networkingApi = new NetworkingV1Api(); + k8SUtils.setUp(NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S); + Commons.cleanUp(CONFIG_WATCHER_IT_IMAGE, K3S); + } @BeforeEach - public void setup() throws Exception { - this.client = createApiClient(); - this.api = new CoreV1Api(); - this.appsApi = new AppsV1Api(); - this.networkingApi = new NetworkingV1Api(); - this.k8SUtils = new K8SUtils(api, appsApi); + void setup() throws Exception { deployRabbitMQ(); deployTestApp(); @@ -91,48 +110,33 @@ public void setup() throws Exception { // Check to make sure the controller deployment is ready k8SUtils.waitForReplicationController(RABBIT_MQ_CONTROLLER_NAME, NAMESPACE); - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE); - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME); + waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME); } @Test - public void testRefresh() throws Exception { + void testRefresh() throws Exception { // Create new configmap to trigger controller to signal app to refresh V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_WATCHER_IT_IMAGE) .addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "hello world") .build(); api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null); - RestTemplate rest = new RestTemplateBuilder().build(); - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)).until( - () -> rest.getForEntity("http://localhost:80/it", String.class).getStatusCode().is2xxSuccessful()); + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/it").build(); - // Wait a bit before we verify - await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(90)).until(() -> { - Boolean value = rest.getForObject("http://localhost:80/it", Boolean.class); - log.info("Returned " + value + " from http://localhost:80/it"); - return value; + Boolean[] value = new Boolean[1]; + await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> { + value[0] = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class).retryWhen(retrySpec()) + .block(); + return value[0]; }); - assertThat(rest.getForObject("http://localhost:80/it", Boolean.class)).isTrue(); + Assertions.assertThat(value[0]).isTrue(); } @AfterEach - public void after() throws Exception { + void after() throws Exception { api.deleteNamespacedService("rabbitmq-service", NAMESPACE, null, null, null, null, null, null); api.deleteNamespacedService(CONFIG_WATCHER_IT_IMAGE, NAMESPACE, null, null, null, null, null, null); api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null, @@ -148,10 +152,8 @@ public void after() throws Exception { null, null); } catch (Exception e) { - // swallowing this exception, the delete does actually happen, its a problem - // downstream from the k8s - // client - // see + // swallowing this exception, delete does actually happen, it's a problem + // downstream from the k8s client; see: // https://github.com/kubernetes-client/java/issues/86#issuecomment-411234259 } @@ -170,7 +172,10 @@ public void after() throws Exception { private void deployTestApp() throws Exception { appsApi.createNamespacedDeployment(NAMESPACE, getItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getItAppService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getItIngress(), null, null, null); + + V1Ingress ingress = getItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); } private void deployConfigWatcher() throws Exception { @@ -181,52 +186,68 @@ private void deployConfigWatcher() throws Exception { private void deployRabbitMQ() throws Exception { api.createNamespacedService(NAMESPACE, getRabbitMQService(), null, null, null); + String[] image = getRabbitMQReplicationController().getSpec().getTemplate().getSpec().getContainers().get(0) + .getImage().split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "rabbitmq", K3S); api.createNamespacedReplicationController(NAMESPACE, getRabbitMQReplicationController(), null, null, null); } private V1Deployment getConfigWatcherDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); + V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath( + "app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml"); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); return deployment; } private V1Deployment getItDeployment() throws Exception { - String urlString = "spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml"; + String urlString = "app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml"; V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); + String image = K8SUtils.getImageFromDeployment(deployment) + ":" + getPomVersion(); deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); return deployment; } private V1Service getItAppService() throws Exception { return (V1Service) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-service.yaml"); + .readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml"); } private V1Service getConfigWatcherService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml"); + return (V1Service) K8SUtils + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml"); } private V1ConfigMap getConfigWatcherConfigMap() throws Exception { return (V1ConfigMap) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); + .readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml"); } private V1Ingress getItIngress() throws Exception { return (V1Ingress) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml"); + .readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml"); } private V1ReplicationController getRabbitMQReplicationController() throws Exception { - return (V1ReplicationController) K8SUtils.readYamlFromClasspath("rabbitmq-controller.yaml"); + return (V1ReplicationController) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-controller.yaml"); } private V1Service getRabbitMQService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("rabbitmq-service.yaml"); + return (V1Service) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-service.yaml"); + } + + private void waitForDeployment(String deploymentName) { + await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS) + .until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE)); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml similarity index 77% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml index 2ca14ad9c9..222b748ab9 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /it(/|$)(.*) + - path: /it pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-configmap.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-controller.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-controller.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-controller.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-controller.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml deleted file mode 100644 index fe39b31c22..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: config-watcher-wiremock-deployment -spec: - selector: - matchLabels: - app: config-watcher-wiremock - template: - metadata: - labels: - app: config-watcher-wiremock - spec: - containers: - - name: config-watcher-wiremock - image: rodolpheche/wiremock:2.27.2 - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8080 - path: /__admin/mappings - livenessProbe: - httpGet: - port: 8080 - path: /__admin/mappings - ports: - - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml deleted file mode 100644 index 559c7645ad..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: config-watcher-wiremock - name: config-watcher-wiremock -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: config-watcher-wiremock - type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml index b52d3a6ff1..e7ef75a381 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml @@ -59,6 +59,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java index 019f2f6f74..241ba5180c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java @@ -16,40 +16,40 @@ package org.springframework.cloud.kubernetes.core.k8s.it; -import java.io.IOException; import java.time.Duration; import java.util.Map; +import java.util.Objects; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; import io.kubernetes.client.openapi.apis.NetworkingV1Api; import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; -import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import static org.awaitility.Awaitility.await; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; /** * @author Ryan Baxter */ -public class ActuatorEndpointIT { - - private static final Log LOG = LogFactory.getLog(ActuatorEndpointIT.class); +class ActuatorEndpointIT { private static final String SPRING_CLOUD_K8S_CLIENT_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-core-k8s-client-it-deployment"; @@ -59,23 +59,27 @@ public class ActuatorEndpointIT { private static final String NAMESPACE = "default"; - private static ApiClient client; - private static CoreV1Api api; private static AppsV1Api appsApi; + private static K8SUtils k8SUtils; + private static NetworkingV1Api networkingApi; - private static K8SUtils k8SUtils; + private static final K3sContainer K3S = Commons.container(); @BeforeAll - public static void setup() throws Exception { - client = createApiClient(); + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); + createApiClient(K3S.getKubeConfigYaml()); api = new CoreV1Api(); appsApi = new AppsV1Api(); networkingApi = new NetworkingV1Api(); k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); deployCoreK8sClientIt(); @@ -84,7 +88,8 @@ public static void setup() throws Exception { } @AfterAll - public static void after() throws Exception { + static void after() throws Exception { + Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S); appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, "metadata.name=" + K8S_CONFIG_CLIENT_IT_NAME, null, null, null, null, null, null, null, null, null); api.deleteNamespacedService(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, NAMESPACE, null, null, null, null, null, null); @@ -92,31 +97,18 @@ public static void after() throws Exception { } @Test - public void testHealth() { - RestTemplate rest = new RestTemplateBuilder().build(); - - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { - - } - }); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/health", String.class) - .getStatusCode().is2xxSuccessful()); - LOG.debug("Response from /health endpoint: " - + rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/health", String.class)); - Map health = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/health", - Map.class); + @SuppressWarnings("unchecked") + void testHealth() { + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/actuator/health").build(); + + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); + @SuppressWarnings("unchecked") + Map health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + Map components = (Map) health.get("components"); assertThat(components.containsKey("kubernetes")).isTrue(); Map kubernetes = (Map) components.get("kubernetes"); @@ -132,35 +124,24 @@ public void handleError(ClientHttpResponse clientHttpResponse) throws IOExceptio assertThat(details.containsKey("serviceAccount")).isTrue(); assertThat(components.containsKey("discoveryComposite")).isTrue(); - Map discoveryComposite = (Map) components.get("discoveryComposite"); + Map discoveryComposite = (Map) components.get("discoveryComposite"); assertThat(discoveryComposite.get("status")).isEqualTo("UP"); + } @Test - public void testInfo() { - RestTemplate rest = new RestTemplateBuilder().build(); - - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/info", String.class) - .getStatusCode().is2xxSuccessful()); - LOG.debug("Response from /info endpoint: " - + rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/info", String.class)); - Map info = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/info", Map.class); + @SuppressWarnings("unchecked") + void testInfo() { + + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/actuator/info").build(); + + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); + @SuppressWarnings("unchecked") + Map info = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + Map kubernetes = (Map) info.get("kubernetes"); assertThat(kubernetes.containsKey("hostIp")).isTrue(); assertThat(kubernetes.containsKey("inside")).isTrue(); @@ -174,7 +155,10 @@ public void handleError(ClientHttpResponse clientHttpResponse) { private static void deployCoreK8sClientIt() throws Exception { appsApi.createNamespacedDeployment(NAMESPACE, getCoreK8sClientItDeployment(), null, null, null); api.createNamespacedService(NAMESPACE, getCoreK8sClientItService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getCoreK8sClientItIngress(), null, null, null); + + V1Ingress ingress = getCoreK8sClientItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); } private static V1Deployment getCoreK8sClientItDeployment() throws Exception { @@ -194,4 +178,12 @@ private static V1Ingress getCoreK8sClientItIngress() throws Exception { return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml"); } + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml index 90b7b89f2d..770915a5bd 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /core-k8s-client-it(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml deleted file mode 100644 index 7eb08b8e3a..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - spring-cloud-kubernetes-integration-tests - org.springframework.cloud - 3.0.0-SNAPSHOT - - 4.0.0 - - spring-cloud-kubernetes-discoverclient-it - - - 1.8.0 - openjdk:8u222-slim - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-kubernetes-discoveryclient - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.cloud - spring-cloud-kubernetes-test-support - - - io.kubernetes - client-java - - - io.kubernetes - client-java-extended - - - com.github.docker-java - docker-java-core - test - - - com.github.docker-java - docker-java-transport-httpclient5 - test - - - - - - - - ../src/main/resources - true - - - src/main/resources - true - - - - - - - skaffold - - - - org.springframework.boot - spring-boot-maven-plugin - - - ${env.IMAGE} - - build-image - - - - package - - build-image - - - - - - - - - imagename - - - !env.IMAGE - - - - springcloud/${project.artifactId}:${project.version} - - - - - diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml deleted file mode 100644 index 1b70c818c6..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml +++ /dev/null @@ -1,13 +0,0 @@ -spring: - cloud: - kubernetes: - discovery: - discoveryServerUrl: http://spring-cloud-kubernetes-discoveryserver -management: - endpoint: - health: - show-details: always - endpoints: - web: - exposure: - include: "*" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java deleted file mode 100644 index 3f9b62ad2a..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.discoveryclient.it; - -import java.io.IOException; -import java.time.Duration; -import java.util.Arrays; -import java.util.Map; - -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.AppsV1Api; -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.apis.NetworkingV1Api; -import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.openapi.models.V1Ingress; -import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; - -/** - * @author Ryan Baxter - */ -public class DiscoveryClientIT { - - private static final Log LOG = LogFactory.getLog(DiscoveryClientIT.class); - - private static final String DISCOVERYSERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment"; - - private static final String DISCOVERYSERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver"; - - private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment"; - - private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it"; - - private static final String NAMESPACE = "default"; - - private static ApiClient client; - - private static CoreV1Api api; - - private static AppsV1Api appsApi; - - private static NetworkingV1Api networkingApi; - - private static K8SUtils k8SUtils; - - @BeforeAll - public static void setup() throws Exception { - client = createApiClient(); - api = new CoreV1Api(); - appsApi = new AppsV1Api(); - networkingApi = new NetworkingV1Api(); - k8SUtils = new K8SUtils(api, appsApi); - - deployDiscoveryServer(); - - // Check to make sure the discovery server deployment is ready - k8SUtils.waitForDeployment(DISCOVERYSERVER_DEPLOYMENT_NAME, NAMESPACE); - - // Check to see if endpoint is ready - k8SUtils.waitForEndpointReady(DISCOVERYSERVER_APP_NAME, NAMESPACE); - - } - - @Test - public void testDiscoveryClient() throws Exception { - try { - deployDiscoveryIt(); - testLoadBalancer(); - testHealth(); - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - cleanup(); - } - } - - private void cleanup() throws ApiException { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, null, null, null, null, null, null, - null, null, null); - api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME, NAMESPACE, null, null, null, null, null, - null); - networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); - } - - private void testLoadBalancer() throws Exception { - // Check to make sure the controller deployment is ready - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, NAMESPACE); - RestTemplate rest = createRestTemplate(); - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/services", String.class) - .getStatusCode().is2xxSuccessful()); - String[] result = rest.getForObject("http://localhost:80/discoveryclient-it/services", String[].class); - LOG.info("Services: " + Arrays.toString(result)); - assertThat(Arrays.stream(result).anyMatch(s -> "spring-cloud-kubernetes-discoveryserver".equalsIgnoreCase(s))) - .isTrue(); - - } - - private RestTemplate createRestTemplate() { - RestTemplate rest = new RestTemplateBuilder().build(); - - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) { - - } - }); - return rest; - } - - public void testHealth() { - RestTemplate rest = createRestTemplate(); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/actuator/health", String.class) - .getStatusCode().is2xxSuccessful()); - - Map health = rest.getForObject("http://localhost:80/discoveryclient-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - Map discoveryComposite = (Map) components.get("discoveryComposite"); - assertThat(discoveryComposite.get("status")).isEqualTo("UP"); - } - - @AfterAll - public static void after() throws Exception { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + DISCOVERYSERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, - null); - - api.deleteNamespacedService(DISCOVERYSERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null); - networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null); - - } - - private void deployDiscoveryIt() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryItIngress(), null, null, null); - } - - private V1Deployment getDiscoveryItDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) k8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); - return deployment; - } - - private static void deployDiscoveryServer() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryServerIngress(), null, null, null); - } - - private static V1Deployment getDiscoveryServerDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) k8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); - return deployment; - } - - private static V1Ingress getDiscoveryServerIngress() throws Exception { - return (V1Ingress) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-ingress.yaml"); - } - - private static V1Service getDiscoveryServerService() throws Exception { - return (V1Service) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-service.yaml"); - } - - private V1Ingress getDiscoveryItIngress() throws Exception { - return (V1Ingress) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-ingress.yaml"); - } - - private V1Service getDiscoveryService() throws Exception { - return (V1Service) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-service.yaml"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml deleted file mode 100644 index 45aebedf46..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: it-ingress - namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 -spec: - rules: - - http: - paths: - - path: /discoveryclient-it(/|$)(.*) - pathType: Prefix - backend: - service: - name: spring-cloud-kubernetes-discoveryclient-it - port: - number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml deleted file mode 100644 index d58cf1b482..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: discoveryserver-ingress - namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 -spec: - rules: - - http: - paths: - - path: /discoveryserver(/|$)(.*) - pathType: Prefix - backend: - service: - name: spring-cloud-kubernetes-discoveryserver - port: - number: 80 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/deployment-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/deployment-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/deployment-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/service-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/service-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/service-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml similarity index 62% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml index d03f64019c..0354115345 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml @@ -9,12 +9,12 @@ 4.0.0 - spring-cloud-kubernetes-reactive-discoveryclient-it + spring-cloud-kubernetes-discoveryclient-it org.springframework.boot - spring-boot-starter-webflux + spring-boot-starter-web org.springframework.cloud @@ -46,6 +46,12 @@ docker-java-transport-httpclient5 test + + org.springframework.boot + spring-boot-starter-webflux + test + + @@ -59,6 +65,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/skaffold.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/skaffold.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/skaffold.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml similarity index 80% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml index 1b70c818c6..ae3c741115 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml @@ -1,3 +1,7 @@ +server: + servlet: + context-path: /discoveryclient-it + spring: cloud: kubernetes: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java new file mode 100644 index 0000000000..119e1fb100 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java @@ -0,0 +1,237 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.discoveryclient.it; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1Service; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * @author Ryan Baxter + */ +class DiscoveryClientIT { + + private static final Log LOG = LogFactory.getLog(DiscoveryClientIT.class); + + private static final String DISCOVERY_SERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment"; + + private static final String DISCOVERY_SERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver"; + + private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment"; + + private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it"; + + private static final String NAMESPACE = "default"; + + private static CoreV1Api api; + + private static AppsV1Api appsApi; + + private static NetworkingV1Api networkingApi; + + private static K8SUtils k8SUtils; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + + Commons.validateImage(DISCOVERY_SERVER_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(DISCOVERY_SERVER_APP_NAME, K3S); + + Commons.validateImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); + + createApiClient(K3S.getKubeConfigYaml()); + api = new CoreV1Api(); + appsApi = new AppsV1Api(); + networkingApi = new NetworkingV1Api(); + k8SUtils = new K8SUtils(api, appsApi); + k8SUtils.setUp(NAMESPACE); + + deployDiscoveryServer(); + + // Check to make sure the discovery server deployment is ready + k8SUtils.waitForDeployment(DISCOVERY_SERVER_DEPLOYMENT_NAME, NAMESPACE); + + // Check to see if endpoint is ready + k8SUtils.waitForEndpointReady(DISCOVERY_SERVER_APP_NAME, NAMESPACE); + } + + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(DISCOVERY_SERVER_APP_NAME, K3S); + Commons.cleanUp(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S); + + appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, + "metadata.name=" + DISCOVERY_SERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, + null); + + api.deleteNamespacedService(DISCOVERY_SERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null); + networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null); + } + + @AfterEach + void afterEach() throws ApiException { + cleanup(); + } + + @Test + void testDiscoveryClient() throws Exception { + deployDiscoveryIt(); + testLoadBalancer(); + testHealth(); + } + + private void cleanup() throws ApiException { + appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, + "metadata.name=" + SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, null, null, null, null, null, + null, null, null, null); + api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, NAMESPACE, null, null, null, null, null, + null); + networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); + } + + private void testLoadBalancer() { + + // Check to make sure the controller deployment is ready + k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, NAMESPACE); + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/services").build(); + + String[] result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String[].class) + .retryWhen(retrySpec()).block(); + LOG.info("Services: " + Arrays.toString(result)); + assertThat(Arrays.stream(result).anyMatch("spring-cloud-kubernetes-discoveryserver"::equalsIgnoreCase)) + .isTrue(); + + } + + @SuppressWarnings("unchecked") + void testHealth() { + WebClient.Builder builder = builder(); + WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build(); + + ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class); + @SuppressWarnings("unchecked") + Map health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + + Map components = (Map) health.get("components"); + + Map discoveryComposite = (Map) components.get("discoveryComposite"); + assertThat(discoveryComposite.get("status")).isEqualTo("UP"); + } + + private void deployDiscoveryIt() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null); + + V1Ingress ingress = getDiscoveryItIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private V1Deployment getDiscoveryItDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) k8SUtils + .readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml"); + String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" + + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private static void deployDiscoveryServer() throws Exception { + appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null); + api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null); + + V1Ingress ingress = getDiscoveryServerIngress(); + networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null); + k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE); + } + + private static V1Deployment getDiscoveryServerDeployment() throws Exception { + V1Deployment deployment = (V1Deployment) k8SUtils + .readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-deployment.yaml"); + String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" + + getPomVersion(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); + return deployment; + } + + private static V1Ingress getDiscoveryServerIngress() throws Exception { + return (V1Ingress) K8SUtils + .readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-ingress.yaml"); + } + + private static V1Service getDiscoveryServerService() throws Exception { + return (V1Service) K8SUtils + .readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-service.yaml"); + } + + private V1Ingress getDiscoveryItIngress() throws Exception { + return (V1Ingress) K8SUtils + .readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml"); + } + + private V1Service getDiscoveryService() throws Exception { + return (V1Service) K8SUtils + .readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-service.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml similarity index 85% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml index d4f120a649..7e47e543e7 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml @@ -19,10 +19,10 @@ spec: readinessProbe: httpGet: port: 8080 - path: /actuator/health/readiness + path: /discoveryclient-it/actuator/health/readiness livenessProbe: httpGet: port: 8080 - path: /actuator/health/liveness + path: /discoveryclient-it/actuator/health/liveness ports: - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml similarity index 74% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml index 45aebedf46..dd39366a4c 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /discoveryclient-it(/|$)(.*) + - path: /discoveryclient-it pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml index d58cf1b482..d820d54e94 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: discoveryserver-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /discoveryserver(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/pom.xml new file mode 100644 index 0000000000..c0a6e6c82f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/pom.xml @@ -0,0 +1,107 @@ + + + + org.springframework.cloud + spring-cloud-kubernetes-integration-tests + 3.0.0-SNAPSHOT + + 4.0.0 + + spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + + + + + org.springframework.cloud + spring-cloud-kubernetes-fabric8-config + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapApp.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapApp.java new file mode 100644 index 0000000000..8594a99cc6 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.configmap.polling.reload; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * @author wind57 + */ + +@SpringBootApplication +@EnableConfigurationProperties(ConfigMapProperties.class) +public class ConfigMapApp { + + public static void main(String[] args) { + SpringApplication.run(ConfigMapApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapController.java new file mode 100644 index 0000000000..c59aa7fc93 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapController.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.configmap.polling.reload; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@RestController +public class ConfigMapController { + + private final ConfigMapProperties properties; + + public ConfigMapController(ConfigMapProperties properties) { + this.properties = properties; + } + + @GetMapping("/key") + public String key() { + return properties.getKey(); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapProperties.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapProperties.java new file mode 100644 index 0000000000..0b6f7e61d7 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.configmap.polling.reload; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author wind57 + */ +@ConfigurationProperties("from.properties") +public class ConfigMapProperties { + + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key1) { + this.key = key1; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/resources/application.yaml new file mode 100644 index 0000000000..baf0f12d35 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +logging: + level: + root: DEBUG + +spring: + application: + name: poll-reload + config: + import: "kubernetes:" + cloud: + kubernetes: + reload: + enabled: true + monitoring-config-maps: true + strategy: shutdown + mode: polling + period: 5000 + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapPollingReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapPollingReloadIT.java new file mode 100644 index 0000000000..0879ad9c91 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/polling/reload/ConfigMapPollingReloadIT.java @@ -0,0 +1,191 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.configmap.polling.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Map; +import java.util.Objects; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.base.HasMetadataOperation; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +class ConfigMapPollingReloadIT { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-configmap-polling-reload"; + + private static final String NAMESPACE = "default"; + + private static KubernetesClient client; + + private static String deploymentName; + + private static String serviceName; + + private static String ingressName; + + private static String configMapName; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); + client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); + deployManifests(); + } + + @AfterAll + static void after() throws Exception { + deleteManifests(); + Commons.cleanUp(IMAGE_NAME, K3S); + } + + @SuppressWarnings({ "raw", "unchecked" }) + @Test + void test() { + WebClient webClient = builder().baseUrl("localhost/key").build(); + String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + + // we first read the initial value from the configmap + Assertions.assertEquals("initial", result); + + // then deploy a new version of configmap + // since we poll and have reload in place, the new property must be visible + ConfigMap map = new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("poll-reload").build()) + .withData(Map.of("application.properties", "from.properties.key=after-change")).build(); + + // the weird cast comes from : + // https://github.com/fabric8io/kubernetes-client/issues/2445 + ((HasMetadataOperation) client.configMaps().inNamespace("default").withName("poll-reload")) + .createOrReplace(map); + + await().timeout(Duration.ofSeconds(30)).until(() -> webClient.method(HttpMethod.GET).retrieve() + .bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change")); + + } + + private static void deleteManifests() { + + try { + + client.configMaps().inNamespace(NAMESPACE).withName(configMapName).delete(); + client.apps().deployments().inNamespace(NAMESPACE).withName(deploymentName).delete(); + client.services().inNamespace(NAMESPACE).withName(serviceName).delete(); + client.network().v1().ingresses().inNamespace(NAMESPACE).withName(ingressName).delete(); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private static void deployManifests() { + + try { + + ConfigMap configMap = client.configMaps().load(getConfigMap()).get(); + configMapName = configMap.getMetadata().getName(); + client.configMaps().create(configMap); + + Deployment deployment = client.apps().deployments().load(getDeployment()).get(); + + String version = K8SUtils.getPomVersion(); + String currentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(currentImage + ":" + version); + + client.apps().deployments().inNamespace(NAMESPACE).create(deployment); + deploymentName = deployment.getMetadata().getName(); + + Service service = client.services().load(getService()).get(); + serviceName = service.getMetadata().getName(); + client.services().inNamespace(NAMESPACE).create(service); + + Ingress ingress = client.network().v1().ingresses().load(getIngress()).get(); + ingressName = ingress.getMetadata().getName(); + client.network().v1().ingresses().inNamespace(NAMESPACE).create(ingress); + + Fabric8Utils.waitForDeployment(client, + "spring-cloud-kubernetes-fabric8-client-configmap-deployment-polling-reload", NAMESPACE, 2, 600); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private static InputStream getService() { + return Fabric8Utils.inputStream("service.yaml"); + } + + private static InputStream getDeployment() { + return Fabric8Utils.inputStream("deployment.yaml"); + } + + private static InputStream getIngress() { + return Fabric8Utils.inputStream("ingress.yaml"); + } + + private static InputStream getConfigMap() { + return Fabric8Utils.inputStream("configmap.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/configmap.yaml new file mode 100644 index 0000000000..194603083c --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: poll-reload + namespace: default +data: + application.properties: | + from.properties.key=initial diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/deployment.yaml new file mode 100644 index 0000000000..39f4572c07 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-kubernetes-fabric8-client-configmap-deployment-polling-reload +spec: + selector: + matchLabels: + app: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + template: + metadata: + labels: + app: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + spec: + serviceAccountName: spring-cloud-kubernetes-serviceaccount + containers: + - name: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + image: docker.io/springcloud/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + imagePullPolicy: IfNotPresent + readinessProbe: + httpGet: + port: 8080 + path: /actuator/health/readiness + livenessProbe: + httpGet: + port: 8080 + path: /actuator/health/liveness + ports: + - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/ingress.yaml new file mode 100644 index 0000000000..fd53e216c1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spring-cloud-kubernetes-fabric8-client-configmap-ingress-polling-reload + namespace: default +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + port: + number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/service.yaml new file mode 100644 index 0000000000..2995e2b6b0 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap-polling-reload/src/test/resources/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + name: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: spring-cloud-kubernetes-fabric8-client-configmap-polling-reload + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml index 5a72036fac..d670bf6d4d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml new file mode 100644 index 0000000000..ace4cb6b1f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml @@ -0,0 +1,5 @@ +spring: + application: + name: my-configmap + config: + import: "kubernetes:" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java index 5ed7b928fe..9cca4695d4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java @@ -16,8 +16,9 @@ package org.springframework.cloud.kubernetes.fabric8.configmap; -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; +import java.util.Objects; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Service; @@ -30,17 +31,21 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; -public class Fabric8ConfigMapIT { +class Fabric8ConfigMapIT { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-configmap"; private static final String NAMESPACE = "default"; @@ -54,26 +59,30 @@ public class Fabric8ConfigMapIT { private static String configMapName; + private static final K3sContainer K3S = Commons.container(); + @BeforeAll - public static void setup() { - Config config = Config.autoConfigure(null); + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); deployManifests(); } @AfterAll - public static void after() { + static void after() throws Exception { deleteManifests(); + Commons.cleanUp(IMAGE_NAME, K3S); } @Test - public void test() { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/fabric8-configmap/key1").build(); + void test() { + WebClient client = builder().baseUrl("localhost/key1").build(); - String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class) - .retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) + String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) .block(); Assertions.assertEquals("value1", result); @@ -130,20 +139,28 @@ private static void deployManifests() { } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("fabric8-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("fabric8-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("fabric8-ingress.yaml"); } - private static FileInputStream getConfigMap() throws Exception { + private static InputStream getConfigMap() { return Fabric8Utils.inputStream("fabric8-configmap.yaml"); } + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml index 197f078b87..51dc769bb2 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: spring-cloud-kubernetes-fabric8-client-configmap-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /fabric8-configmap(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml index 6b9cd73c70..746b531b01 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml @@ -51,6 +51,54 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java index 82c75691eb..9f653aacf6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java @@ -16,9 +16,10 @@ package org.springframework.cloud.kubernetes.fabric8.configmap; -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; import java.util.List; +import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -30,23 +31,27 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; /** * @author wind57 */ -public class Fabric8DiscoveryIT { +class Fabric8DiscoveryIT { private static final String NAMESPACE = "default"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery"; + private static KubernetesClient client; private static String deploymentName; @@ -59,30 +64,38 @@ public class Fabric8DiscoveryIT { private static String mockDeploymentName; + private static String mockDeploymentImage; + + private static final K3sContainer K3S = Commons.container(); + @BeforeAll - public static void setup() { - Config config = Config.autoConfigure(null); + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); deployManifests(); deployMockManifests(); } @AfterAll - public static void after() { + static void after() throws Exception { deleteManifests(); + Commons.cleanUp(IMAGE_NAME, K3S); + Commons.cleanUpDownloadedImage(mockDeploymentImage); } @Test - public void test() { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/fabric8-discovery/services").build(); + void test() { + WebClient client = builder().baseUrl("localhost/services").build(); @SuppressWarnings("unchecked") List result = (List) client.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) - .block(); + .retryWhen(retrySpec()).block(); Assertions.assertEquals(result.size(), 3); Assertions.assertTrue(result.contains("kubernetes")); @@ -144,8 +157,12 @@ private static void deployMockManifests() { try { Deployment deployment = client.apps().deployments().load(getMockDeployment()).get(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "wiremock", K3S); client.apps().deployments().inNamespace(NAMESPACE).create(deployment); mockDeploymentName = deployment.getMetadata().getName(); + mockDeploymentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); Service service = client.services().load(getMockService()).get(); mockServiceName = service.getMetadata().getName(); @@ -160,24 +177,32 @@ private static void deployMockManifests() { } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("fabric8-discovery-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("fabric8-discovery-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("fabric8-discovery-ingress.yaml"); } - private static FileInputStream getMockService() throws Exception { - return Fabric8Utils.inputStream("fabric8-discovery-wiremock-service.yaml"); + private static InputStream getMockService() { + return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-service.yaml"); + } + + private static InputStream getMockDeployment() { + return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-deployment.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); } - private static FileInputStream getMockDeployment() throws Exception { - return Fabric8Utils.inputStream("fabric8-discovery-wiremock-deployment.yaml"); + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml index 0a46fe3cd6..334508bf16 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: spring-cloud-kubernetes-fabric8-client-discovery-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /fabric8-discovery(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml index 1dc89ac9f9..9d28eb6277 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: servicea-wiremock - image: rodolpheche/wiremock:2.27.2 + image: wiremock/wiremock:2.32.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml index af96c76550..4ab0f5c5fd 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml new file mode 100644 index 0000000000..c83b17d1a4 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml @@ -0,0 +1,3 @@ +spring: + webflux: + base-path: /loadbalancer-it diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java index 46b0d35c3d..90b1507bf1 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java @@ -16,9 +16,10 @@ package org.springframework.cloud.kubernetes.client.loadbalancer.it; -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; import java.util.Map; +import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -26,18 +27,22 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; import static org.assertj.core.api.Assertions.assertThat; @@ -48,6 +53,8 @@ public class Fabric8ClientLoadbalancerIT { private static final String NAMESPACE = "default"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-loadbalancer"; + private static KubernetesClient client; private static String deploymentName; @@ -62,33 +69,44 @@ public class Fabric8ClientLoadbalancerIT { private static String mockIngressName; - @BeforeEach - public void setup() { - Config config = Config.autoConfigure(null); + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); + } - deployMockManifests(); + @AfterAll + static void afterAll() throws Exception { + Commons.cleanUp(IMAGE_NAME, K3S); + } + @BeforeEach + void beforeEach() { + deployMockManifests(); } @AfterEach - public void after() { + void after() { deleteManifests(); } @Test - public void testLoadBalancerServiceMode() { + void testLoadBalancerServiceMode() { deployServiceManifests(); - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/loadbalancer-it/servicea").build(); + WebClient client = builder().baseUrl("localhost/loadbalancer-it/servicea").build(); @SuppressWarnings("unchecked") Map mapResult = (Map) client.method(HttpMethod.GET).retrieve() - .bodyToMono(Map.class).retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) - .block(); + .bodyToMono(Map.class).retryWhen(retrySpec()).block(); assertThat(mapResult.containsKey("mappings")).isTrue(); assertThat(mapResult.containsKey("meta")).isTrue(); @@ -100,14 +118,11 @@ public void testLoadBalancerPodMode() { deployPodManifests(); - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/loadbalancer-it/servicea").build(); + WebClient client = builder().baseUrl("localhost/loadbalancer-it/servicea").build(); @SuppressWarnings("unchecked") Map mapResult = (Map) client.method(HttpMethod.GET).retrieve() - .bodyToMono(Map.class).retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) - .block(); + .bodyToMono(Map.class).retryWhen(retrySpec()).block(); assertThat(mapResult.containsKey("mappings")).isTrue(); assertThat(mapResult.containsKey("meta")).isTrue(); @@ -200,6 +215,9 @@ private static void deployMockManifests() { try { Deployment deployment = client.apps().deployments().load(getMockDeployment()).get(); + String[] image = K8SUtils.getImageFromDeployment(deployment).split(":"); + Commons.pullImage(image[0], image[1], K3S); + Commons.loadImage(image[0], image[1], "wiremock", K3S); client.apps().deployments().inNamespace(NAMESPACE).create(deployment); mockDeploymentName = deployment.getMetadata().getName(); @@ -220,32 +238,40 @@ private static void deployMockManifests() { } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml"); } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-service.yaml"); } - private static FileInputStream getPodDeployment() throws Exception { + private static InputStream getPodDeployment() { return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml"); } - private static FileInputStream getServiceDeployment() throws Exception { + private static InputStream getServiceDeployment() { return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml"); } - private static FileInputStream getMockIngress() throws Exception { - return Fabric8Utils.inputStream("wiremock-ingress.yaml"); + private static InputStream getMockIngress() { + return Fabric8Utils.inputStream("wiremock/wiremock-ingress.yaml"); + } + + private static InputStream getMockService() { + return Fabric8Utils.inputStream("wiremock/wiremock-service.yaml"); + } + + private static InputStream getMockDeployment() { + return Fabric8Utils.inputStream("wiremock/wiremock-deployment.yaml"); } - private static FileInputStream getMockService() throws Exception { - return Fabric8Utils.inputStream("wiremock-service.yaml"); + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); } - private static FileInputStream getMockDeployment() throws Exception { - return Fabric8Utils.inputStream("wiremock-deployment.yaml"); + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml index 3c140c5b7b..17aafe66ed 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /loadbalancer-it(/|$)(.*) + - path: /loadbalancer-it pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml index 5ecbe185ad..a3285b693e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml @@ -22,10 +22,10 @@ spec: readinessProbe: httpGet: port: 8080 - path: /actuator/health/readiness + path: /loadbalancer-it/actuator/health/readiness livenessProbe: httpGet: port: 8080 - path: /actuator/health/liveness + path: /loadbalancer-it/actuator/health/liveness ports: - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml index c190331988..71d5120ab2 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml @@ -22,10 +22,10 @@ spec: readinessProbe: httpGet: port: 8080 - path: /actuator/health/readiness + path: /loadbalancer-it/actuator/health/readiness livenessProbe: httpGet: port: 8080 - path: /actuator/health/liveness + path: /loadbalancer-it/actuator/health/liveness ports: - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9f9..9d28eb6277 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: servicea-wiremock - image: rodolpheche/wiremock:2.27.2 + image: wiremock/wiremock:2.32.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml index 2d278b58d6..bd2d857a4d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: wiremock-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /wiremock(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/pom.xml new file mode 100644 index 0000000000..8b775e7c05 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/pom.xml @@ -0,0 +1,107 @@ + + + + org.springframework.cloud + spring-cloud-kubernetes-integration-tests + 3.0.0-SNAPSHOT + + 4.0.0 + + spring-cloud-kubernetes-fabric8-client-secrets-event-reload + + + + + org.springframework.cloud + spring-cloud-kubernetes-fabric8-config + + + org.springframework.cloud + spring-cloud-kubernetes-test-support + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-actuator + + + com.github.docker-java + docker-java-core + test + + + com.github.docker-java + docker-java-transport-httpclient5 + test + + + + + + + + ../src/main/resources + true + + + src/main/resources + true + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsApp.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsApp.java new file mode 100644 index 0000000000..cc49b3e4bc --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * @author wind57 + */ + +@SpringBootApplication +@EnableConfigurationProperties(SecretsProperties.class) +public class SecretsApp { + + public static void main(String[] args) { + SpringApplication.run(SecretsApp.class, args); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsController.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsController.java new file mode 100644 index 0000000000..5c3d3ddac7 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsController.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author wind57 + */ +@RestController +public class SecretsController { + + private final SecretsProperties properties; + + public SecretsController(SecretsProperties properties) { + this.properties = properties; + } + + @GetMapping("/key") + public String key() { + return properties.getKey(); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsProperties.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsProperties.java new file mode 100644 index 0000000000..c24bcb0dbf --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsProperties.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author wind57 + */ +@ConfigurationProperties("from.properties") +public class SecretsProperties { + + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key1) { + this.key = key1; + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/resources/application.yaml new file mode 100644 index 0000000000..d4b67d9cf2 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +logging: + level: + root: DEBUG + +spring: + application: + name: event-reload + config: + import: "kubernetes:" + cloud: + kubernetes: + reload: + enabled: true + monitoring-secrets: true + strategy: shutdown + mode: event + secrets: + enabled: true + enable-api: true + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java new file mode 100644 index 0000000000..6a84b0dfb5 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/java/org/springframework/cloud/kubernetes/fabric8/secrets/event/reload/SecretsEventsReloadIT.java @@ -0,0 +1,194 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8.secrets.event.reload; + +import java.io.InputStream; +import java.time.Duration; +import java.util.Base64; +import java.util.Map; +import java.util.Objects; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.base.HasMetadataOperation; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; +import reactor.netty.http.client.HttpClient; +import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; + +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; +import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; +import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.awaitility.Awaitility.await; + +/** + * @author wind57 + */ +class SecretsEventsReloadIT { + + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-secrets-event-reload"; + + private static final String NAMESPACE = "default"; + + private static KubernetesClient client; + + private static String deploymentName; + + private static String serviceName; + + private static String ingressName; + + private static String secretName; + + private static final K3sContainer K3S = Commons.container(); + + @BeforeAll + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); + client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); + deployManifests(); + } + + @AfterAll + static void after() throws Exception { + deleteManifests(); + Commons.cleanUp(IMAGE_NAME, K3S); + } + + @SuppressWarnings({ "raw", "unchecked" }) + @Test + void test() { + WebClient webClient = builder().baseUrl("localhost/key").build(); + String result = webClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) + .block(); + + // we first read the initial value from the secret + Assertions.assertEquals("initial", result); + + // then deploy a new version of the secret + // since we poll and have reload in place, the new property must be visible + Secret secret = new SecretBuilder() + .withMetadata(new ObjectMetaBuilder().withNamespace("default").withName("event-reload").build()) + .withData(Map.of("application.properties", + Base64.getEncoder().encodeToString("from.properties.key=after-change".getBytes()))) + .build(); + + // the weird cast comes from : + // https://github.com/fabric8io/kubernetes-client/issues/2445 + ((HasMetadataOperation) client.secrets().inNamespace("default").withName("event-reload")) + .createOrReplace(secret); + + await().timeout(Duration.ofSeconds(120)).until(() -> webClient.method(HttpMethod.GET).retrieve() + .bodyToMono(String.class).retryWhen(retrySpec()).block().equals("after-change")); + + } + + private static void deleteManifests() { + + try { + + client.secrets().inNamespace(NAMESPACE).withName(secretName).delete(); + client.apps().deployments().inNamespace(NAMESPACE).withName(deploymentName).delete(); + client.services().inNamespace(NAMESPACE).withName(serviceName).delete(); + client.network().v1().ingresses().inNamespace(NAMESPACE).withName(ingressName).delete(); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private static void deployManifests() { + + try { + + Secret configMap = client.secrets().load(getSecret()).get(); + secretName = configMap.getMetadata().getName(); + client.secrets().create(configMap); + + Deployment deployment = client.apps().deployments().load(getDeployment()).get(); + + String version = K8SUtils.getPomVersion(); + String currentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(currentImage + ":" + version); + + client.apps().deployments().inNamespace(NAMESPACE).create(deployment); + deploymentName = deployment.getMetadata().getName(); + + Service service = client.services().load(getService()).get(); + serviceName = service.getMetadata().getName(); + client.services().inNamespace(NAMESPACE).create(service); + + Ingress ingress = client.network().v1().ingresses().load(getIngress()).get(); + ingressName = ingress.getMetadata().getName(); + client.network().v1().ingresses().inNamespace(NAMESPACE).create(ingress); + + Fabric8Utils.waitForDeployment(client, + "spring-cloud-kubernetes-fabric8-client-secrets-deployment-event-reload", NAMESPACE, 2, 600); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private static InputStream getService() { + return Fabric8Utils.inputStream("service.yaml"); + } + + private static InputStream getDeployment() { + return Fabric8Utils.inputStream("deployment.yaml"); + } + + private static InputStream getIngress() { + return Fabric8Utils.inputStream("ingress.yaml"); + } + + private static InputStream getSecret() { + return Fabric8Utils.inputStream("secret.yaml"); + } + + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(120, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/deployment.yaml similarity index 57% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/deployment.yaml index e42d0c3955..9145122921 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/deployment.yaml @@ -1,20 +1,20 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: spring-cloud-kubernetes-discoveryclient-it-deployment + name: spring-cloud-kubernetes-fabric8-client-secrets-deployment-event-reload spec: selector: matchLabels: - app: spring-cloud-kubernetes-discoveryclient-it + app: spring-cloud-kubernetes-fabric8-client-secrets-event-reload template: metadata: labels: - app: spring-cloud-kubernetes-discoveryclient-it + app: spring-cloud-kubernetes-fabric8-client-secrets-event-reload spec: serviceAccountName: spring-cloud-kubernetes-serviceaccount containers: - - name: spring-cloud-kubernetes-discoveryclient-it - image: docker.io/springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it + - name: spring-cloud-kubernetes-fabric8-client-secrets-event-reload + image: docker.io/springcloud/spring-cloud-kubernetes-fabric8-client-secrets-event-reload imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/ingress.yaml new file mode 100644 index 0000000000..19e5d0df5c --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spring-cloud-kubernetes-fabric8-client-secrets-ingress-event-reload + namespace: default +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: spring-cloud-kubernetes-fabric8-client-secrets-event-reload + port: + number: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/secret.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/secret.yaml new file mode 100644 index 0000000000..bf3e730da4 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: event-reload + namespace: default +data: + # from.properties.key=initial + application.properties: | + ZnJvbS5wcm9wZXJ0aWVzLmtleT1pbml0aWFsCg== diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/service.yaml new file mode 100644 index 0000000000..239bc00bbb --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-secrets-event-reload/src/test/resources/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: spring-cloud-kubernetes-fabric8-client-secrets-event-reload + name: spring-cloud-kubernetes-fabric8-client-secrets-event-reload +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: spring-cloud-kubernetes-fabric8-client-secrets-event-reload + type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml index 04d16d988c..132ad6d159 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java index 3138beb406..9356e84745 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java @@ -14,8 +14,9 @@ * limitations under the License. */ -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; +import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -27,15 +28,17 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; /** * @author wind57 @@ -44,6 +47,8 @@ class SimpleCoreIT { private static final String NAMESPACE = "default"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-simple-core"; + private static KubernetesClient client; private static String deploymentName; @@ -52,26 +57,32 @@ class SimpleCoreIT { private static String ingressName; + private static final K3sContainer K3S = Commons.container(); + @BeforeAll - public static void setup() { - Config config = Config.autoConfigure(null); + static void beforeAll() throws Exception { + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUp(client, NAMESPACE); + deployManifests(); } @AfterAll - public static void after() { + static void after() throws Exception { deleteManifests(); + Commons.cleanUp(IMAGE_NAME, K3S); } @Test - public void test() { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/fabric8-client-simple-core/message").build(); + void test() { + WebClient client = builder().baseUrl("localhost/message").build(); - String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class) - .retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) + String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec()) .block(); // value must come from application-kubernetes.yml @@ -124,16 +135,24 @@ private static void deployManifests() { } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("simple-core-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("simple-core-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("simple-core-ingress.yaml"); } + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml index cd7de80bda..d0b52aa4ce 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /fabric8-client-simple-core(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml index 0201b5cbed..bef179e45e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java index 98fa5941f4..ae486ae7b0 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java @@ -16,9 +16,11 @@ package org.springframework.cloud.kubernetes.fabric8.istio; -import java.io.FileInputStream; +import java.io.File; +import java.io.InputStream; import java.time.Duration; import java.util.List; +import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -30,23 +32,38 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Container; +import org.testcontainers.k3s.K3sContainer; import reactor.netty.http.client.HttpClient; import reactor.util.retry.Retry; +import reactor.util.retry.RetryBackoffSpec; +import org.springframework.cloud.kubernetes.integration.tests.commons.Commons; import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils; import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClientResponseException; /** * @author wind57 */ -public class Fabric8IstioIT { +class Fabric8IstioIT { private static final String NAMESPACE = "istio-test"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-istio-it"; + + private static final String ISTIO_PROXY = "istio/proxyv2"; + + private static final String ISTIO_PILOT = "istio/pilot"; + + private static final String ISTIO_VERSION = "1.13.3"; + + private static final String LOCAL_ISTIO_BIN_PATH = "../../istio-cli/istio-" + ISTIO_VERSION + "/bin"; + + private static final String CONTAINER_ISTIO_BIN_PATH = "/tmp/istio/istio-bin/bin/"; + private static KubernetesClient client; private static String deploymentName; @@ -55,27 +72,59 @@ public class Fabric8IstioIT { private static String ingressName; + private static K3sContainer K3S; + @BeforeAll - public static void setup() { - Config config = Config.autoConfigure(null); + static void beforeAll() throws Exception { + // Path passed to K3S container must be absolute + String absolutePath = new File(LOCAL_ISTIO_BIN_PATH).getAbsolutePath(); + K3S = Commons.container().withFileSystemBind(absolutePath, CONTAINER_ISTIO_BIN_PATH); + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S); + + Commons.pullImage(ISTIO_PROXY, ISTIO_VERSION, K3S); + Commons.loadImage(ISTIO_PROXY, ISTIO_VERSION, "istioproxy", K3S); + Commons.pullImage(ISTIO_PILOT, ISTIO_VERSION, K3S); + Commons.loadImage(ISTIO_PILOT, ISTIO_VERSION, "istiopilot", K3S); + + processExecResult(K3S.execInContainer("sh", "-c", "kubectl create namespace istio-test")); + processExecResult( + K3S.execInContainer("sh", "-c", "kubectl label namespace istio-test istio-injection=enabled")); + + // for Mac M1 with aarch64 + if (System.getProperty("os.arch").equals("aarch64")) { + processExecResult(K3S.execInContainer("sh", "-c", CONTAINER_ISTIO_BIN_PATH + "istioctl" + + " --kubeconfig=/etc/rancher/k3s/k3s.yaml install --set hub=docker.io/querycapistio --set profile=minimal -y")); + } + else { + processExecResult(K3S.execInContainer("sh", "-c", CONTAINER_ISTIO_BIN_PATH + "istioctl" + + " --kubeconfig=/etc/rancher/k3s/k3s.yaml install --set profile=minimal -y")); + } + + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUpIstio(client, NAMESPACE); + deployManifests(); } @AfterAll - public static void after() { + static void afterAll() throws Exception { + Commons.cleanUp(IMAGE_NAME, K3S); + } + + @AfterAll + static void after() { deleteManifests(); } @Test - public void test() { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/fabric8-client-istio/profiles").build(); + void test() { + WebClient client = builder().baseUrl("localhost/profiles").build(); @SuppressWarnings("unchecked") - List result = client.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) + List result = client.method(HttpMethod.GET).retrieve().bodyToMono(List.class).retryWhen(retrySpec()) .block(); // istio profile is present @@ -92,6 +141,7 @@ private static void deleteManifests() { } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException(e); } @@ -118,26 +168,44 @@ private static void deployManifests() { ingressName = ingress.getMetadata().getName(); client.network().v1().ingresses().inNamespace(NAMESPACE).create(ingress); + Fabric8Utils.waitForIngress(client, ingressName, NAMESPACE); Fabric8Utils.waitForDeployment(client, "spring-cloud-kubernetes-fabric8-istio-it-deployment", NAMESPACE, 2, 600); } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException(e); } } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("istio-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("istio-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("istio-ingress.yaml"); } + private WebClient.Builder builder() { + return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())); + } + + private RetryBackoffSpec retrySpec() { + return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull); + } + + private static String processExecResult(Container.ExecResult execResult) { + if (execResult.getExitCode() != 0) { + throw new RuntimeException("stdout=" + execResult.getStdout() + "\n" + "stderr=" + execResult.getStderr()); + } + + return execResult.getStdout(); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml index 5def7c6cfd..f5e5d77642 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: istio-test - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /fabric8-client-istio(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..9e2848765f --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml deleted file mode 100644 index 7e9b54fb55..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - name: spring-cloud-kubernetes-reactive-discoveryclient-it-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - image: springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it:2.0.4-SNAPSHOT - imagePullPolicy: IfNotPresent - name: spring-cloud-kubernetes-reactive-discoveryclient-it - resources: {} -status: {} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml deleted file mode 100644 index 5b253f9390..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - name: spring-cloud-kubernetes-reactive-discoveryclient-it -spec: - ports: - - name: 80-8080 - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - type: ClusterIP -status: - loadBalancer: {} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml deleted file mode 100644 index 599b2ebfe6..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: skaffold/v2alpha3 -kind: Config -metadata: - name: spring-cloud-kubernetes-reactive-discoveryclient-it -build: - artifacts: - - image: springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it - jib: { - args: [ "-Pjib" ] - } -# custom: -# buildCommand: "../../mvnw clean install -Pskaffold" -# dependencies: -# paths: -# - src -# - pom.xml -deploy: - kubectl: - manifests: - - k8s/deployment-it.yaml - - k8s/service-it.yaml - - ../permissions.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java deleted file mode 100644 index 0974a80cb9..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.reactive.discoveryclient.it; - -import java.util.List; -import java.util.stream.Collectors; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Ryan Baxter - */ -@SpringBootApplication -@RestController -public class KubernetesReactiveDiscoveryClientApplicationIt { - - private final ReactiveDiscoveryClient discoveryClient; - - public KubernetesReactiveDiscoveryClientApplicationIt(ReactiveDiscoveryClient discoveryClient) { - this.discoveryClient = discoveryClient; - } - - public static void main(String[] args) { - SpringApplication.run(KubernetesReactiveDiscoveryClientApplicationIt.class, args); - } - - @GetMapping("/services") - public Mono> services() { - return discoveryClient.getServices().collect(Collectors.toList()); - } - - @GetMapping("/service/{serviceId}") - public Flux service(@PathVariable String serviceId) { - return discoveryClient.getInstances(serviceId); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java deleted file mode 100644 index cdfd25b7f3..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.kubernetes.reactive.discoveryclient.it; - -import java.io.IOException; -import java.time.Duration; -import java.util.Arrays; -import java.util.Map; - -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.AppsV1Api; -import io.kubernetes.client.openapi.apis.CoreV1Api; -import io.kubernetes.client.openapi.apis.NetworkingV1Api; -import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.openapi.models.V1Ingress; -import io.kubernetes.client.openapi.models.V1Service; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient; -import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; - -/** - * @author Ryan Baxter - */ -class ReactiveDiscoveryClientIT { - - private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientIT.class); - - private static final String DISCOVERYSERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment"; - - private static final String DISCOVERYSERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver"; - - private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment"; - - private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it"; - - private static final String NAMESPACE = "default"; - - private static ApiClient client; - - private static CoreV1Api api; - - private static AppsV1Api appsApi; - - private static NetworkingV1Api networkingApi; - - private static K8SUtils k8SUtils; - - @BeforeAll - public static void setup() throws Exception { - client = createApiClient(); - api = new CoreV1Api(); - appsApi = new AppsV1Api(); - networkingApi = new NetworkingV1Api(); - k8SUtils = new K8SUtils(api, appsApi); - - deployDiscoveryServer(); - - // Check to make sure the discovery server deployment is ready - k8SUtils.waitForDeployment(DISCOVERYSERVER_DEPLOYMENT_NAME, NAMESPACE); - - // Check to see if endpoint is ready - k8SUtils.waitForEndpointReady(DISCOVERYSERVER_APP_NAME, NAMESPACE); - - } - - @Test - public void testDiscoveryClient() throws Exception { - try { - deployDiscoveryIt(); - testLoadBalancer(); - testHealth(); - } - catch (Exception e) { - e.printStackTrace(); - } - finally { - cleanup(); - } - } - - private void cleanup() throws ApiException { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, null, null, null, null, null, null, - null, null, null); - api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME, NAMESPACE, null, null, null, null, null, - null); - networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null); - } - - private void testLoadBalancer() throws Exception { - // Check to make sure the controller deployment is ready - k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, NAMESPACE); - RestTemplate rest = createRestTemplate(); - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/services", String.class) - .getStatusCode().is2xxSuccessful()); - String[] result = rest.getForObject("http://localhost:80/discoveryclient-it/services", String[].class); - LOG.info("Services: " + Arrays.toString(result)); - assertThat(Arrays.stream(result).anyMatch(s -> "spring-cloud-kubernetes-discoveryserver".equalsIgnoreCase(s))) - .isTrue(); - - } - - private RestTemplate createRestTemplate() { - RestTemplate rest = new RestTemplateBuilder().build(); - - rest.setErrorHandler(new ResponseErrorHandler() { - @Override - public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException { - LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode()); - return clientHttpResponse.getRawStatusCode() != 503; - } - - @Override - public void handleError(ClientHttpResponse clientHttpResponse) throws IOException { - - } - }); - return rest; - } - - public void testHealth() { - RestTemplate rest = createRestTemplate(); - - // Sometimes the NGINX ingress takes a bit to catch up and realize the service is - // available and we get a 503, we just need to wait a bit - await().timeout(Duration.ofSeconds(60)) - .until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/actuator/health", String.class) - .getStatusCode().is2xxSuccessful()); - - Map health = rest.getForObject("http://localhost:80/discoveryclient-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - Map discoveryComposite = (Map) components.get("discoveryComposite"); - assertThat(discoveryComposite.get("status")).isEqualTo("UP"); - } - - @AfterAll - public static void after() throws Exception { - appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null, - "metadata.name=" + DISCOVERYSERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, - null); - - api.deleteNamespacedService(DISCOVERYSERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null); - networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null); - - } - - private void deployDiscoveryIt() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryItIngress(), null, null, null); - } - - private V1Deployment getDiscoveryItDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); - return deployment; - } - - private static void deployDiscoveryServer() throws Exception { - appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null); - api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null); - networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryServerIngress(), null, null, null); - } - - private static V1Deployment getDiscoveryServerDeployment() throws Exception { - V1Deployment deployment = (V1Deployment) K8SUtils - .readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-deployment.yaml"); - String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":" - + getPomVersion(); - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image); - return deployment; - } - - private V1Service getDiscoveryService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-service.yaml"); - } - - private static V1Ingress getDiscoveryServerIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-ingress.yaml"); - } - - private V1Ingress getDiscoveryItIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-ingress.yaml"); - } - - private static V1Service getDiscoveryServerService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-service.yaml"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml deleted file mode 100644 index 32b4bd7e37..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-discoveryclient-it - name: spring-cloud-kubernetes-discoveryclient-it -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-discoveryclient-it - type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml deleted file mode 100644 index 3672520dbd..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-discoveryserver-deployment -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-discoveryserver - template: - metadata: - labels: - app: spring-cloud-kubernetes-discoveryserver - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-discoveryserver - image: docker.io/springcloud/spring-cloud-kubernetes-discoveryserver - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8761 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8761 - path: /actuator/health/liveness - ports: - - containerPort: 8761 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml deleted file mode 100644 index ba99611826..0000000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-discoveryserver - name: spring-cloud-kubernetes-discoveryserver -spec: - ports: - - name: http - port: 80 - targetPort: 8761 - selector: - app: spring-cloud-kubernetes-discoveryserver - type: ClusterIP diff --git a/spring-cloud-kubernetes-test-support/pom.xml b/spring-cloud-kubernetes-test-support/pom.xml index 94fa3ba054..0a0379fdce 100644 --- a/spring-cloud-kubernetes-test-support/pom.xml +++ b/spring-cloud-kubernetes-test-support/pom.xml @@ -41,6 +41,18 @@ awaitility ${awaitility.version} + + + org.testcontainers + testcontainers + 1.16.3 + + + org.testcontainers + k3s + 1.16.3 + + diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java new file mode 100644 index 0000000000..a2fc45bbe4 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java @@ -0,0 +1,136 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.integration.tests.commons; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import com.github.dockerjava.api.model.Image; +import org.testcontainers.k3s.K3sContainer; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * A few commons things that can be re-used across clients. This is meant to be used for + * testing purposes only. + * + * @author wind57 + */ +public final class Commons { + + private Commons() { + throw new AssertionError("No instance provided"); + } + + /** + * Rancher version to use for test-containers. + */ + public static final String RANCHER = "rancher/k3s:v1.21.10-k3s1"; + + /** + * Command to use when starting rancher. Without "server" option, traefik is not + * installed + */ + public static final String RANCHER_COMMAND = "server"; + + /** + * Test containers exposed ports. + */ + public static final int[] EXPOSED_PORTS = new int[] { 80, 6443, 8080, 8888, 9092 }; + + /** + * Temporary folder where to load images. + */ + public static final String TEMP_FOLDER = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath(); + + private static final K3sContainer CONTAINER = new FixedPortsK3sContainer(DockerImageName.parse(Commons.RANCHER)) + .configureFixedPorts(EXPOSED_PORTS).withFileSystemBind(TEMP_FOLDER, TEMP_FOLDER) + .withCommand(Commons.RANCHER_COMMAND).withReuse(true); + + public static K3sContainer container() { + return CONTAINER; + } + + public static void loadSpringCloudKubernetesImage(String project, K3sContainer container) throws Exception { + loadImage("springcloud/" + project, getPomVersion(), project, container); + } + + public static void loadImage(String image, String tag, String tarName, K3sContainer container) throws Exception { + // save image + InputStream imageStream = container.getDockerClient().saveImageCmd(image).withTag(tag).exec(); + + Path imagePath = Paths.get(TEMP_FOLDER + "/" + tarName + ".tar"); + Files.deleteIfExists(imagePath); + Files.copy(imageStream, imagePath); + // import image with ctr. this works because TEMP_FOLDER is mounted in the + // container + container.execInContainer("ctr", "i", "import", TEMP_FOLDER + "/" + tarName + ".tar"); + } + + public static void cleanUp(String image, K3sContainer container) throws Exception { + container.execInContainer("crictl", "rmi", "docker.io/springcloud/" + image + ":" + getPomVersion()); + container.execInContainer("rm", TEMP_FOLDER + "/" + image + ".tar"); + } + + public static void cleanUpDownloadedImage(String image) throws Exception { + CONTAINER.execInContainer("crictl", "rmi", image); + } + + /** + * validates that the provided image does exist in the local docker registry. + */ + public static void validateImage(String image, K3sContainer container) { + List images = container.getDockerClient().listImagesCmd().exec(); + images.stream() + .filter(x -> Arrays.stream(x.getRepoTags() == null ? new String[] {} : x.getRepoTags()) + .anyMatch(y -> y.contains(image))) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Image : " + image + " not build locally. " + + "You need to build it first, and then run the test")); + } + + public static void pullImage(String image, String tag, K3sContainer container) throws InterruptedException { + container.getDockerClient().pullImageCmd(image).withTag(tag).start().awaitCompletion(); + } + + /** + * A K3sContainer, but with fixed port mappings. This is needed because of the nature + * of some integration tests. + * + * @author wind57 + */ + private static final class FixedPortsK3sContainer extends K3sContainer { + + private FixedPortsK3sContainer(DockerImageName dockerImageName) { + super(dockerImageName); + } + + private FixedPortsK3sContainer configureFixedPorts(int[] ports) { + for (int port : ports) { + super.addFixedExposedPort(port, port); + } + return this; + } + + } + +} diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java index eb95c71ef5..3fdd6341a7 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java @@ -16,13 +16,18 @@ package org.springframework.cloud.kubernetes.integration.tests.commons; -import java.io.File; -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; +import java.util.List; import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.api.model.LoadBalancerIngress; +import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.rbac.Role; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.fabric8.kubernetes.client.KubernetesClient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,10 +46,8 @@ private Fabric8Utils() { throw new AssertionError("no instance provided"); } - public static FileInputStream inputStream(String fileName) throws Exception { - ClassLoader classLoader = Fabric8Utils.class.getClassLoader(); - File file = new File(classLoader.getResource(fileName).getFile()); - return new FileInputStream(file); + public static InputStream inputStream(String fileName) { + return Fabric8Utils.class.getClassLoader().getResourceAsStream(fileName); } public static void waitForDeployment(KubernetesClient client, String deploymentName, String namespace, @@ -64,7 +67,7 @@ private static boolean isDeploymentReady(KubernetesClient client, String deploym Deployment deployment = client.apps().deployments().inNamespace(namespace).withName(deploymentName).get(); Integer availableReplicas = deployment.getStatus().getAvailableReplicas(); - LOG.info("Available replicas for " + deploymentName + ": " + availableReplicas); + LOG.info("Available replicas for " + deploymentName + ": " + ((availableReplicas == null) ? 0 : 1)); return availableReplicas != null && availableReplicas >= 1; } @@ -79,4 +82,77 @@ private static boolean isEndpointReady(KubernetesClient client, String endpointN return endpoint.getSubsets().get(0).getAddresses().size() >= 1; } + public static void setUp(KubernetesClient client, String namespace) throws Exception { + InputStream serviceAccountAsStream = inputStream("setup/service-account.yaml"); + InputStream roleBindingAsStream = inputStream("setup/role-binding.yaml"); + InputStream roleAsStream = inputStream("setup/role.yaml"); + + innerSetup(client, namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream); + } + + public static void setUpIstio(KubernetesClient client, String namespace) throws Exception { + InputStream serviceAccountAsStream = inputStream("istio/service-account.yaml"); + InputStream roleBindingAsStream = inputStream("istio/role-binding.yaml"); + InputStream roleAsStream = inputStream("istio/role.yaml"); + + innerSetup(client, namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream); + } + + public static void waitForIngress(KubernetesClient client, String ingressName, String namespace) { + + try { + await().pollInterval(Duration.ofSeconds(2)).atMost(180, TimeUnit.SECONDS).until(() -> { + Ingress ingress = client.network().v1().ingresses().inNamespace(namespace).withName(ingressName).get(); + + if (ingress == null) { + System.out.println("ingress : " + ingressName + " not ready yet present"); + return false; + } + + List loadBalancerIngress = ingress.getStatus().getLoadBalancer().getIngress(); + if (loadBalancerIngress == null || loadBalancerIngress.isEmpty()) { + System.out.println( + "ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)"); + return false; + } + + String ip = loadBalancerIngress.get(0).getIp(); + if (ip == null) { + System.out.println("ingress : " + ingressName + " not ready yet"); + return false; + } + + System.out.println("ingress : " + ingressName + " ready with ip : " + ip); + return true; + + }); + } + catch (Exception e) { + System.out.println("Error waiting for ingress"); + e.printStackTrace(); + } + + } + + private static void innerSetup(KubernetesClient client, String namespace, InputStream serviceAccountAsStream, + InputStream roleBindingAsStream, InputStream roleAsStream) { + ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).get(); + if (client.serviceAccounts().inNamespace(namespace).withName(serviceAccountFromStream.getMetadata().getName()) + .get() == null) { + client.serviceAccounts().inNamespace(namespace).create(serviceAccountFromStream); + } + + RoleBinding roleBindingFromStream = client.rbac().roleBindings().load(roleBindingAsStream).get(); + if (client.rbac().roleBindings().inNamespace(namespace).withName(roleBindingFromStream.getMetadata().getName()) + .get() == null) { + client.rbac().roleBindings().inNamespace(namespace).create(roleBindingFromStream); + } + + Role roleFromStream = client.rbac().roles().load(roleAsStream).get(); + if (client.rbac().roles().inNamespace(namespace).withName(roleFromStream.getMetadata().getName()) + .get() == null) { + client.rbac().roles().inNamespace(namespace).create(roleFromStream); + } + } + } diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java index 10feae86ae..088a14b7ca 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java @@ -17,37 +17,49 @@ package org.springframework.cloud.kubernetes.integration.tests.commons; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import io.fabric8.kubernetes.api.model.apps.Deployment; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.Configuration; import io.kubernetes.client.openapi.apis.AppsV1Api; import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.apis.NetworkingV1Api; +import io.kubernetes.client.openapi.apis.RbacAuthorizationV1Api; import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1DeploymentBuilder; import io.kubernetes.client.openapi.models.V1DeploymentList; import io.kubernetes.client.openapi.models.V1Endpoints; import io.kubernetes.client.openapi.models.V1EndpointsList; import io.kubernetes.client.openapi.models.V1EnvVar; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1LoadBalancerIngress; +import io.kubernetes.client.openapi.models.V1LoadBalancerStatus; import io.kubernetes.client.openapi.models.V1ReplicationController; import io.kubernetes.client.openapi.models.V1ReplicationControllerList; +import io.kubernetes.client.openapi.models.V1Role; +import io.kubernetes.client.openapi.models.V1RoleBinding; import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceAccount; import io.kubernetes.client.openapi.models.V1ServiceBuilder; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.Yaml; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.testcontainers.k3s.K3sContainer; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ReflectionUtils; @@ -64,12 +76,20 @@ public class K8SUtils { private static final String KUBERNETES_VERSION_FILE = "META-INF/springcloudkubernetes-version.txt"; + private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment"; + + private static final String WIREMOCK_APP_NAME = "servicea-wiremock"; + private final Log log = LogFactory.getLog(getClass()); private final CoreV1Api api; private final AppsV1Api appsApi; + private final NetworkingV1Api networkingApi; + + private final RbacAuthorizationV1Api rbacApi; + public static ApiClient createApiClient() throws IOException { return createApiClient(false, Duration.ofSeconds(15)); } @@ -97,9 +117,19 @@ public static ApiClient createApiClient(boolean debug, Duration readTimeout) thr return client; } + public static ApiClient createApiClient(String configFile) throws IOException { + ApiClient client = Config.fromConfig(new StringReader(configFile)); + client.setHttpClient(client.getHttpClient().newBuilder().readTimeout(Duration.ofSeconds(15)).build()); + client.setDebugging(false); + Configuration.setDefaultApiClient(client); + return client; + } + public K8SUtils(CoreV1Api api, AppsV1Api appsApi) { this.api = api; this.appsApi = appsApi; + this.networkingApi = new NetworkingV1Api(); + this.rbacApi = new RbacAuthorizationV1Api(); } public Object readYaml(String urlString) throws Exception { @@ -125,7 +155,8 @@ public Object readYaml(String urlString) throws Exception { public static Object readYamlFromClasspath(String fileName) throws Exception { ClassLoader classLoader = K8SUtils.class.getClassLoader(); - File file = new File(classLoader.getResource(fileName).getFile()); + String file = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream(fileName))).lines() + .collect(Collectors.joining("\n")); return Yaml.load(file); } @@ -185,7 +216,7 @@ public boolean isReplicationControllerReady(String name, String namespace) throw V1ReplicationController replicationController = controllerList.getItems().get(0); Integer availableReplicas = replicationController.getStatus().getAvailableReplicas(); - log.info("Available replicas for " + name + ": " + availableReplicas); + log.info("Available replicas for " + name + ": " + (availableReplicas == null ? 0 : availableReplicas)); return availableReplicas != null && availableReplicas >= 1; } @@ -195,6 +226,41 @@ public void waitForDeployment(String deploymentName, String namespace) { .until(() -> isDeploymentReady(deploymentName, namespace)); } + public void waitForIngress(String ingressName, String namespace) { + await().timeout(Duration.ofSeconds(90)).pollInterval(Duration.ofSeconds(3)).until(() -> { + try { + V1LoadBalancerStatus status = networkingApi + .readNamespacedIngress(ingressName, namespace, null, null, null).getStatus().getLoadBalancer(); + + if (status == null) { + log.info("ingress : " + ingressName + " not ready yet (loadbalancer not yet present)"); + return false; + } + + List loadBalancerIngress = status.getIngress(); + if (loadBalancerIngress == null) { + log.info("ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)"); + return false; + } + + String ip = loadBalancerIngress.get(0).getIp(); + if (ip == null) { + log.info("ingress : " + ingressName + " not ready yet"); + return false; + } + + log.info("ingress : " + ingressName + " ready with ip : " + ip); + return true; + } + catch (ApiException e) { + if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + throw new RuntimeException(e); + } + }); + } + public void waitForDeploymentToBeDeleted(String deploymentName, String namespace) { await().timeout(Duration.ofSeconds(90)).until(() -> { try { @@ -218,8 +284,151 @@ public boolean isDeploymentReady(String deploymentName, String namespace) throws } V1Deployment deployment = deployments.getItems().get(0); Integer availableReplicas = deployment.getStatus().getAvailableReplicas(); - log.info("Available replicas for " + deploymentName + ": " + availableReplicas); + log.info("Available replicas for " + deploymentName + ": " + + (availableReplicas == null ? 0 : availableReplicas)); return availableReplicas != null && availableReplicas >= 1; } + public void setUp(String namespace) throws Exception { + + V1ServiceAccount serviceAccount = getConfigK8sClientItServiceAccount(); + CheckedSupplier accountSupplier = () -> api + .readNamespacedServiceAccount(serviceAccount.getMetadata().getName(), namespace, null, null, null); + CheckedSupplier accountDefaulter = () -> api.createNamespacedServiceAccount(namespace, + serviceAccount, null, null, null); + notExistsHandler(accountSupplier, accountDefaulter); + + V1RoleBinding roleBinding = getConfigK8sClientItRoleBinding(); + notExistsHandler(() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null), + () -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null)); + + V1Role role = getConfigK8sClientItRole(); + notExistsHandler(() -> rbacApi.readNamespacedRole(role.getMetadata().getName(), namespace, null), + () -> rbacApi.createNamespacedRole(namespace, role, null, null, null)); + } + + public static V1ServiceAccount getConfigK8sClientItServiceAccount() throws Exception { + return (V1ServiceAccount) K8SUtils.readYamlFromClasspath("setup/service-account.yaml"); + } + + public static V1RoleBinding getConfigK8sClientItRoleBinding() throws Exception { + return (V1RoleBinding) K8SUtils.readYamlFromClasspath("setup/role-binding.yaml"); + } + + public static V1Role getConfigK8sClientItRole() throws Exception { + return (V1Role) K8SUtils.readYamlFromClasspath("setup/role.yaml"); + } + + public void deployWiremock(String namespace, boolean rootPath, K3sContainer container) throws Exception { + innerDeployWiremock(namespace, rootPath, container); + + // Check to make sure the wiremock deployment is ready + waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, namespace); + + // Check to see if endpoint is ready + waitForEndpointReady(WIREMOCK_APP_NAME, namespace); + } + + /** + * this removes wiremock related manifests, but keeps the image loaded in the + * container. As such can be used across tests. + */ + public void cleanUpWiremock(String namespace) throws Exception { + appsApi.deleteCollectionNamespacedDeployment(namespace, null, null, null, + "metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null); + + api.deleteNamespacedService(WIREMOCK_APP_NAME, namespace, null, null, null, null, null, null); + networkingApi.deleteNamespacedIngress("wiremock-ingress", namespace, null, null, null, null, null, null); + waitForDeploymentToBeDeleted(WIREMOCK_DEPLOYMENT_NAME, namespace); + } + + /** + * this one should be called once all tests in a suite are done, as it removes the + * image from a running container. + */ + public void removeWiremockImage() throws Exception { + V1Deployment wiremockDeployment = getWiremockDeployment(); + String wiremockImage = getImageFromDeployment(wiremockDeployment); + Commons.cleanUpDownloadedImage(wiremockImage); + } + + /** + * Gets the image from a Kubernetes Client deployment yaml. Assumes there is only one + * container defined in the deployment. + * @param deployment deployment yaml + * @return An array where the first item is the mage name and the second item is the + * tag + */ + public static String getImageFromDeployment(V1Deployment deployment) { + return deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + } + + /** + * Gets the image from a Fabric8 deployment yaml. Assumes there is only one container + * defined in the deployment. + * @param deployment deployment yaml + * @return An array where the first item is the mage name and the second item is the + * tag + */ + public static String getImageFromDeployment(Deployment deployment) { + return deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + } + + private void innerDeployWiremock(String namespace, boolean rootPath, K3sContainer container) throws Exception { + V1Deployment deployment = getWiremockDeployment(); + String[] image = getImageFromDeployment(deployment).split(":", 2); + Commons.pullImage(image[0], image[1], container); + Commons.loadImage(image[0], image[1], "wiremock", container); + appsApi.createNamespacedDeployment(namespace, getWiremockDeployment(), null, null, null); + api.createNamespacedService(namespace, getWiremockAppService(), null, null, null); + + V1Ingress ingress; + if (rootPath) { + ingress = getWiremockRootPathIngress(); + } + else { + ingress = getWiremockIngress(); + } + + networkingApi.createNamespacedIngress(namespace, ingress, null, null, null); + waitForIngress(ingress.getMetadata().getName(), namespace); + } + + private static V1Ingress getWiremockIngress() throws Exception { + return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-ingress.yaml"); + } + + private static V1Ingress getWiremockRootPathIngress() throws Exception { + return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-root-path-ingress.yaml"); + } + + private static V1Service getWiremockAppService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("wiremock/wiremock-service.yaml"); + } + + private static V1Deployment getWiremockDeployment() throws Exception { + return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock/wiremock-deployment.yaml"); + } + + private static void notExistsHandler(CheckedSupplier callee, CheckedSupplier defaulter) throws Exception { + try { + callee.get(); + } + catch (Exception exception) { + if (exception instanceof ApiException apiException) { + if (apiException.getCode() == 404) { + defaulter.get(); + return; + } + } + throw new RuntimeException(exception); + } + } + + private interface CheckedSupplier { + + T get() throws Exception; + + } + } diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml new file mode 100644 index 0000000000..cd1bff4b93 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: spring-cloud-kubernetes-core-k8s-client-it + name: istio-test-rb +roleRef: + kind: Role + apiGroup: rbac.authorization.k8s.io + name: istio-test +subjects: + - kind: ServiceAccount + name: spring-cloud-kubernetes-istio-serviceaccount + namespace: istio-test diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml new file mode 100644 index 0000000000..2765eee379 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: istio-test + name: istio-test +rules: + - apiGroups: [ "", "extensions", "apps" ] + resources: [ "configmaps", "pods", "services", "endpoints", "secrets" ] + verbs: [ "get", "list", "watch" ] diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml new file mode 100644 index 0000000000..2f4ac50c1e --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: istio-integration-test + name: spring-cloud-kubernetes-istio-serviceaccount + namespace: istio-test diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml new file mode 100644 index 0000000000..43d1e2a9e4 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: spring-cloud-kubernetes-core-k8s-client-it + name: spring-cloud-kubernetes-core-k8s-client-it:view +roleRef: + kind: Role + apiGroup: rbac.authorization.k8s.io + name: namespace-reader +subjects: + - kind: ServiceAccount + name: spring-cloud-kubernetes-serviceaccount + namespace: default diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml new file mode 100644 index 0000000000..83ba8006ee --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: namespace-reader +rules: + - apiGroups: ["", "extensions", "apps"] + resources: ["configmaps", "pods", "services", "endpoints", "secrets"] + verbs: ["get", "list", "watch"] diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml new file mode 100644 index 0000000000..e847e1d763 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: integration-test + name: spring-cloud-kubernetes-serviceaccount diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml similarity index 89% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9f9..8ee1912685 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml @@ -13,7 +13,8 @@ spec: spec: containers: - name: servicea-wiremock - image: rodolpheche/wiremock:2.27.2 + image: wiremock/wiremock:2.32.0 + args: ["--verbose"] imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml similarity index 74% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml index 61bb48a0b9..8786d59a7d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: wiremock-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /wiremock(/|$)(.*) + - path: /wiremock/ pathType: Prefix backend: service: @@ -17,4 +15,3 @@ spec: port: number: 8080 - diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml new file mode 100644 index 0000000000..bd2d857a4d --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wiremock-ingress + namespace: default +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: servicea-wiremock + port: + number: 8080 + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-service.yaml