From 0e1d03dfcb6710894cf3de482f7c71e0ca264aef Mon Sep 17 00:00:00 2001 From: erabii Date: Wed, 8 Jun 2022 17:15:54 +0300 Subject: [PATCH] labels, profiles, prefix, single yaml/properties for secrets and config maps (#1026) --- .../main/asciidoc/property-source-config.adoc | 30 +- ...bernetesClientConfigMapPropertySource.java | 16 +- .../config/KubernetesClientConfigUtils.java | 141 +++++- ...dConfigMapContextToSourceDataProvider.java | 63 +++ ...eledSecretContextToSourceDataProvider.java | 91 +--- ...dConfigMapContextToSourceDataProvider.java | 93 +--- ...amedSecretContextToSourceDataProvider.java | 58 +-- ...rapIncludeProfileSpecificSourcesTests.java | 43 -- ...ntConfigMapPropertySourceLocatorTests.java | 2 +- ...tesClientConfigMapPropertySourceTests.java | 3 +- ...ientSecretsPropertySourceLocatorTests.java | 2 +- ...netesClientSecretsPropertySourceTests.java | 13 +- ...igMapContextToSourceDataProviderTests.java | 453 ++++++++++++++++++ ...ecretContextToSourceDataProviderTests.java | 272 ++++++++--- ...igMapContextToSourceDataProviderTests.java | 300 +++++++----- ...ecretContextToSourceDataProviderTests.java | 247 ++++++++-- .../LabeledConfigMapWithPrefixApp.java | 35 ++ ...eledConfigMapWithPrefixBootstrapTests.java | 31 ++ ...ledConfigMapWithPrefixConfigDataTests.java | 62 +++ .../LabeledConfigMapWithPrefixTests.java | 113 +++++ .../LabeledConfigMapWithPrefixController.java | 64 +++ .../properties/Four.java | 34 ++ .../properties/One.java | 2 +- .../properties/Three.java | 34 ++ .../properties/Two.java | 2 +- .../LabeledConfigMapWithProfileApp.java | 36 ++ ...ledConfigMapWithProfileBootstrapTests.java | 33 ++ ...edConfigMapWithProfileConfigDataTests.java | 64 +++ .../LabeledConfigMapWithProfileTests.java | 75 +++ ...LabeledConfigMapWithProfileController.java | 46 ++ .../properties/Blue.java | 34 ++ .../properties/Green.java | 54 +++ .../LabeledSecretWithProfileApp.java | 33 ++ ...abeledSecretWithProfileBootstrapTests.java | 32 ++ ...beledSecretWithProfileConfigDataTests.java | 62 +++ .../LabeledSecretWithProfileTests.java | 87 ++++ .../LabeledSecretWithProfileController.java | 46 ++ .../properties/Blue.java | 34 ++ .../properties/Green.java | 54 +++ ...amedConfigMapWithPrefixBootstrapTests.java | 2 +- ...medConfigMapWithPrefixConfigDataTests.java | 2 +- .../NamedConfigMapWithPrefixTests.java | 3 +- .../NamedConfigMapWithProfileApp.java | 40 ++ ...medConfigMapWithProfileBootstrapTests.java | 30 ++ ...dConfigMapWithProfileConfigDataTests.java} | 25 +- .../NamedConfigMapWithProfileTests.java | 101 ++++ .../NamedConfigMapWithProfileController.java} | 18 +- .../properties/One.java | 34 ++ .../properties/Three.java | 4 +- .../properties/Two.java | 34 ++ .../NamedSecretWithLabelApp.java} | 12 +- .../NamedSecretWithProfileBootstrapTests.java | 32 ++ ...NamedSecretWithProfileConfigDataTests.java | 63 +++ .../NamedSecretWithProfileTests.java | 101 ++++ .../NamedSecretWithProfileController.java | 55 +++ .../properties/One.java | 34 ++ .../properties/Three.java | 34 ++ .../properties/Two.java | 34 ++ ...dConfigMapWithPrefixConfigurationStub.java | 100 ++++ ...ConfigMapWithProfileConfigurationStub.java | 135 ++++++ ...eledSecretWithPrefixConfigurationStub.java | 25 +- ...ledSecretWithProfileConfigurationStub.java | 134 ++++++ ...onfigMapWithProfileConfigurationStub.java} | 43 +- ...amedSecretWithPrefixConfigurationStub.java | 1 - ...medSecretWithProfileConfigurationStub.java | 105 ++++ ...ConfigFailFastEnabledButRetryDisabled.java | 3 +- ...igRetryDisabledButSecretsRetryEnabled.java | 3 +- .../configmap_retry/ConfigRetryEnabled.java | 3 +- ...ecretsFailFastEnabledButRetryDisabled.java | 3 +- ...etsRetryDisabledButConfigRetryEnabled.java | 3 +- .../secrets_retry/SecretsRetryEnabled.java | 3 +- .../test/resources/META-INF/spring.factories | 8 +- .../labeled-configmap-with-prefix.yaml | 21 + .../labeled-configmap-with-profile.yaml | 22 + .../labeled-secret-with-profile.yaml | 22 + ....yaml => named-configmap-with-prefix.yaml} | 0 .../named-configmap-with-profile.yaml | 17 + .../resources/named-secret-with-profile.yaml | 18 + ...tryableConfigMapPropertySourceLocator.java | 2 +- .../config/ConfigMapConfigProperties.java | 104 ++-- .../ConfigMapPropertySourceLocator.java | 8 +- .../commons/config/ConfigUtils.java | 115 ++++- .../LabeledConfigMapNormalizedSource.java | 99 ++++ .../config/LabeledSecretNormalizedSource.java | 13 +- .../commons/config/LabeledSourceData.java | 92 ++++ .../config/MultipleSourcesContainer.java | 35 ++ .../config/NamedSecretNormalizedSource.java | 14 +- .../commons/config/NamedSourceData.java | 82 ++++ .../commons/config/NormalizedSource.java | 2 +- .../commons/config/NormalizedSourceType.java | 7 +- .../config/SecretsConfigProperties.java | 42 +- ...e.java => SourceDataEntriesProcessor.java} | 11 +- .../config/StrippedSourceContainer.java | 28 ++ .../ConfigMapConfigPropertiesTests.java | 88 ++++ .../commons/config/ConfigUtilsTests.java | 21 +- ...LabeledConfigMapNormalizedSourceTests.java | 78 +++ .../LabeledSecretNormalizedSourceTests.java | 30 +- .../NamedSecretNormalizedSourceTests.java | 21 +- .../config/SecretsConfigPropertiesTests.java | 26 +- ...bernetesConfigServerAutoConfiguration.java | 2 +- .../KubernetesEnvironmentRepositoryTests.java | 57 ++- .../Fabric8ConfigMapPropertySource.java | 16 +- .../fabric8/config/Fabric8ConfigUtils.java | 124 ++++- ...dConfigMapContextToSourceDataProvider.java | 69 +++ ...eledSecretContextToSourceDataProvider.java | 95 +--- ...dConfigMapContextToSourceDataProvider.java | 77 +-- ...amedSecretContextToSourceDataProvider.java | 60 +-- ...strapFabric8SecretsPropertySourceTest.java | 3 - ...BasedConfigurationChangeDetectorTests.java | 19 +- ...c8ConfigMapPropertySourceLocatorTests.java | 4 +- .../Fabric8ConfigMapPropertySourceTests.java | 4 +- .../config/Fabric8ConfigUtilsTests.java | 254 +++++++++- ...ric8SecretsPropertySourceLocatorTests.java | 10 +- ...Fabric8SecretsPropertySourceMockTests.java | 15 +- ...igMapContextToSourceDataProviderTests.java | 414 ++++++++++++++++ ...ecretContextToSourceDataProviderTests.java | 203 +++++++- ...igMapContextToSourceDataProviderTests.java | 147 ++++-- ...ecretContextToSourceDataProviderTests.java | 147 +++++- .../LabeledConfigMapWithPrefixApp.java | 35 ++ ...eledConfigMapWithPrefixBootstrapTests.java | 42 ++ ...ledConfigMapWithPrefixConfigDataTests.java | 42 ++ .../LabeledConfigMapWithPrefixTests.java | 134 ++++++ .../LabeledConfigMapWithPrefixController.java | 64 +++ .../properties/Four.java | 34 ++ .../properties/One.java | 2 +- .../properties/Three.java | 34 ++ .../properties/Two.java | 2 +- .../LabeledConfigMapWithProfileApp.java | 33 ++ ...edConfigMapWithProfileBootstrapTests.java} | 18 +- ...edConfigMapWithProfileConfigDataTests.java | 45 ++ .../LabeledConfigMapWithProfileTests.java | 123 +++++ ...LabeledConfigMapWithProfileController.java | 46 ++ .../properties/Blue.java | 34 ++ .../properties/Green.java | 54 +++ ...abeledSecretWithPrefixConfigDataTests.java | 2 +- .../LabeledSecretWithProfileApp.java | 33 ++ ...abeledSecretWithProfileBootstrapTests.java | 44 ++ ...beledSecretWithProfileConfigDataTests.java | 44 ++ .../LabeledSecretWithProfileTests.java | 131 +++++ .../LabeledSecretWithProfileController.java | 46 ++ .../properties/Blue.java | 34 ++ .../properties/Green.java | 54 +++ ...ConfigFailFastEnabledButRetryDisabled.java | 2 +- ...igRetryDisabledButSecretsRetryEnabled.java | 10 +- .../locator_retry/ConfigRetryEnabled.java | 18 +- .../NamedConfigMapWithProfileApp.java} | 12 +- ...edConfigMapWithProfileBootstrapTests.java} | 22 +- ...edConfigMapWithProfileConfigDataTests.java | 44 ++ .../NamedConfigMapWithProfileTests.java | 128 +++++ .../NamedConfigMapWithProfileController.java | 55 +++ .../properties/One.java | 34 ++ .../properties/Three.java | 34 ++ .../properties/Two.java | 34 ++ .../NamedSecretWithProfileApp.java | 34 ++ .../NamedSecretWithProfileBootstrapTests.java | 43 ++ ...NamedSecretWithProfileConfigDataTests.java | 43 ++ .../NamedSecretWithProfileTests.java | 136 ++++++ .../NamedSecretWithProfileController.java} | 18 +- .../properties/One.java | 34 ++ .../properties/Three.java | 4 +- .../properties/Two.java | 34 ++ ...ecretsFailFastEnabledButRetryDisabled.java | 5 +- ...etsRetryDisabledButConfigRetryEnabled.java | 2 +- ...ecretsFailFastEnabledButRetryDisabled.java | 5 +- ...ecretsFailFastEnabledButRetryDisabled.java | 9 +- ...etsRetryDisabledButConfigRetryEnabled.java | 14 +- .../config/retry/SecretsRetryEnabled.java | 15 +- .../resources/application-secrets.properties | 2 +- .../labeled-configmap-with-prefix.yaml | 21 + .../labeled-configmap-with-profile.yaml | 22 + .../labeled-secret-with-profile.yaml | 22 + .../named-configmap-with-profile.yaml | 17 + .../resources/named-secret-with-profile.yaml | 17 + 173 files changed, 7734 insertions(+), 1145 deletions(-) create mode 100644 spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProvider.java delete mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProviderTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Four.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/{include_profile_specific_sources => labeled_config_map_with_prefix}/properties/One.java (94%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_prefix/properties/Three.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/{include_profile_specific_sources => labeled_config_map_with_prefix}/properties/Two.java (94%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Blue.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_config_map_with_profile/properties/Green.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileApp.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/LabeledSecretWithProfileTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Blue.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/labeled_secret_with_profile/properties/Green.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileApp.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/{KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests.java => applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java} (64%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileTests.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/{include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java => named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java} (73%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/One.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/{include_profile_specific_sources => named_config_map_with_profile}/properties/Three.java (90%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/properties/Two.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/{include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java => named_secret_with_profile/NamedSecretWithLabelApp.java} (78%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithProfileTests.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/controller/NamedSecretWithProfileController.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/One.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Three.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/properties/Two.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithPrefixConfigurationStub.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledConfigMapWithProfileConfigurationStub.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/LabeledSecretWithProfileConfigurationStub.java rename spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/{IncludeProfileSpecificSourcesConfigurationStub.java => NamedConfigMapWithProfileConfigurationStub.java} (66%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedSecretWithProfileConfigurationStub.java create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-prefix.yaml create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/labeled-configmap-with-profile.yaml create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-profile.yaml rename spring-cloud-kubernetes-client-config/src/test/resources/{named-config-map-with-prefix.yaml => named-configmap-with-prefix.yaml} (100%) create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-profile.yaml create mode 100644 spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-profile.yaml create mode 100644 spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSource.java create mode 100644 spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSourceData.java create mode 100644 spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java create mode 100644 spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java rename spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/{ConfigMapPropertySource.java => SourceDataEntriesProcessor.java} (89%) create mode 100644 spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/StrippedSourceContainer.java create mode 100644 spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSourceTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProvider.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProviderTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixApp.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixBootstrapTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/LabeledConfigMapWithPrefixTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/controller/LabeledConfigMapWithPrefixController.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Four.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{include_profile_specific_sources => labeled_config_map_with_prefix}/properties/One.java (89%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_prefix/properties/Three.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{include_profile_specific_sources => labeled_config_map_with_prefix}/properties/Two.java (89%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileApp.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{BootstrapConfigMapWithIncludeProfileSpecificSourcesTests.java => labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java} (61%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/controller/LabeledConfigMapWithProfileController.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Blue.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/properties/Green.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileApp.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileBootstrapTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/LabeledSecretWithProfileTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/controller/LabeledSecretWithProfileController.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Blue.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_profile/properties/Green.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java => named_config_map_with_profile/NamedConfigMapWithProfileApp.java} (64%) rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{ConfigDataConfigMapWithIncludeProfileSpecificSourcesTests.java => named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java} (55%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/controller/NamedConfigMapWithProfileController.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/One.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Three.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/properties/Two.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileApp.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileBootstrapTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileConfigDataTests.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/NamedSecretWithProfileTests.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java => named_secret_with_profile/controller/NamedSecretWithProfileController.java} (61%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/One.java rename spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/{include_profile_specific_sources => named_secret_with_profile}/properties/Three.java (85%) create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/properties/Two.java create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-prefix.yaml create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-profile.yaml create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-profile.yaml create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/named-configmap-with-profile.yaml create mode 100644 spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-profile.yaml diff --git a/docs/src/main/asciidoc/property-source-config.adoc b/docs/src/main/asciidoc/property-source-config.adoc index 4f0c5eb9..7d730970 100644 --- a/docs/src/main/asciidoc/property-source-config.adoc +++ b/docs/src/main/asciidoc/property-source-config.adoc @@ -23,7 +23,10 @@ The link:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spr 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 `application.properties` file under the following key: `spring.cloud.kubernetes.config.name`. @@ -135,6 +138,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 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 9f5620fc..d66f16d5 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 @@ package org.springframework.cloud.kubernetes.client.config; 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,10 +47,12 @@ public class KubernetesClientConfigMapPropertySource extends ConfigMapPropertySo .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).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 9bee82d0..857cb352 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 @@ public final class KubernetesClientConfigUtils { } /** - * 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 Map dataFromSecret(V1Secret secret, String namespace) { - LOG.debug("reading secret with name : " + secret.getMetadata().getName() + " in namespace : " + namespace); - Map data = secret.getData(); + 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(); + } - 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.processLabeledData(strippedSources, environment, labels, namespace, profiles, DECODE); - return result; } - private static String decoded(byte[] value) { - return new String(Base64.getDecoder().decode(Base64.getEncoder().encodeToString(value))).trim(); + /** + *
+	 *     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 MultipleSourcesContainer secretsDataByName(CoreV1Api 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, 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()); + } + + 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 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/LabeledConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/LabeledConfigMapContextToSourceDataProvider.java new file mode 100644 index 00000000..6d4381ff --- /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 85cef8cb..9cf72cbc 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,27 +16,13 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.HashMap; -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 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.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -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 @@ -46,8 +32,6 @@ import static org.springframework.cloud.kubernetes.commons.config.Constants.PROP */ final class LabeledSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - LabeledSecretContextToSourceDataProvider() { } @@ -67,75 +51,18 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier { - Map result = new HashMap<>(); LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource(); - Set propertySourceNames = new LinkedHashSet<>(); - Map labels = source.labels(); - String namespace = context.namespace(); - String sourceNameFromLabels = 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()) { - - for (V1Secret secret : secrets) { - // we support prefix per source, not per secret. This means that - // in theory - // we can still have clashes here, that we simply override. - // If there are more than one secret found per labels, and they - // have the same key on a - // property, but different values; one value will override the - // other, without any particular order. - result.putAll(dataFromSecret(secret, namespace)); - } - - String secretNames = secrets.stream().map(V1Secret::getMetadata).map(V1ObjectMeta::getName).sorted() - .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); - propertySourceNames.add(secretNames); - - if (source.prefix() != ConfigUtils.Prefix.DEFAULT) { - - String prefix; - if (source.prefix() == ConfigUtils.Prefix.KNOWN) { - prefix = source.prefix().prefixProvider().get(); - } - else { - // prefix is going to be all the secret names we found based - // on the labels - // concatenated with PROPERTY_SOURCE_NAME_SEPARATOR - prefix = secretNames; - } - - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), prefixContext); - } - - String names = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames); - return new SourceData(ConfigUtils.sourceName(source.target(), names, namespace), result); + 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); - } - - // if we could not find a secret with provided labels, we will compute a - // response with an empty Map - // and name that will use all the label names (not their values) - String propertySourceNameFromLabels = ConfigUtils.sourceName(source.target(), sourceNameFromLabels, - namespace); - return new SourceData(propertySourceNameFromLabels, 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 ddccb4dd..749e08a4 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,29 +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.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.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; - -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 @@ -48,18 +31,7 @@ import static org.springframework.cloud.kubernetes.commons.config.Constants.PROP */ final class NamedConfigMapContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedConfigMapContextToSourceDataProvider.class); - - private final BiFunction, Environment, Map> entriesProcessor; - - private NamedConfigMapContextToSourceDataProvider( - BiFunction, Environment, Map> entriesProcessor) { - this.entriesProcessor = Objects.requireNonNull(entriesProcessor); - } - - static NamedConfigMapContextToSourceDataProvider of( - BiFunction, Environment, Map> entriesProcessor) { - return new NamedConfigMapContextToSourceDataProvider(entriesProcessor); + NamedConfigMapContextToSourceDataProvider() { } @Override @@ -68,60 +40,15 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier { 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 = source.name().orElseThrow(); - 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 (source.prefix() != ConfigUtils.Prefix.DEFAULT) { - // since we are in a named source, calling get on the supplier is safe - String prefix = source.prefix().prefixProvider().get(); - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), 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(ConfigUtils.sourceName(source.target(), propertySourceTokens, namespace), result); + }.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/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-client-config/src/main/java/org/springframework/cloud/kubernetes/client/config/NamedSecretContextToSourceDataProvider.java index c3aac0e0..12469c99 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,24 +16,12 @@ package org.springframework.cloud.kubernetes.client.config; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Optional; 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.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -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 @@ -43,8 +31,6 @@ import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.on */ final class NamedSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedSecretContextToSourceDataProvider.class); - NamedSecretContextToSourceDataProvider() { } @@ -53,41 +39,15 @@ final class NamedSecretContextToSourceDataProvider implements Supplier { NamedSecretNormalizedSource source = (NamedSecretNormalizedSource) context.normalizedSource(); - Set propertySourceNames = new LinkedHashSet<>(); - propertySourceNames.add(source.name().orElseThrow()); - 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))); - - if (source.prefix() != ConfigUtils.Prefix.DEFAULT && !result.isEmpty()) { - // since we are in a named source, calling get on the supplier is safe - String prefix = source.prefix().prefixProvider().get(); - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), prefixContext); + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return KubernetesClientConfigUtils.secretsDataByName(context.client(), context.namespace(), + sourceNames, context.environment()); } - - } - catch (Exception e) { - String message = "Unable to read Secret with name '" + name + "' in namespace '" + namespace + "'"; - onException(source.failFast(), message, e); - } - - String propertySourceName = ConfigUtils.sourceName(source.target(), name, namespace); - return new SourceData(propertySourceName, result); - + }.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/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests.java deleted file mode 100644 index 27bae0c1..00000000 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests.java +++ /dev/null @@ -1,43 +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.config; - -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.client.config.applications.include_profile_specific_sources.IncludeProfileSpecificSourcesApp; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -/** - * The stub data for this test is in : IncludeProfileSpecificSourcesConfigurationStub - * - * @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", - "spring.cloud.bootstrap.enabled=true" }) -@AutoConfigureWebTestClient -@ActiveProfiles("dev") -class KubernetesClientConfigMapBootstrapIncludeProfileSpecificSourcesTests - extends KubernetesClientConfigMapIncludeProfileSpecificSourcesTests { - -} 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 4f636df0..9a045e30 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 @@ class KubernetesClientConfigMapPropertySourceLocatorTests { 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 49dce03c..75a6c2a1 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 @@ -182,8 +182,7 @@ class KubernetesClientConfigMapPropertySourceTests { 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"))); } 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 9e30dcf1..b897f987 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 @@ -174,7 +174,7 @@ class KubernetesClientSecretsPropertySourceLocatorTests { 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 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 b0cbfd96..2da5e54e 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 @@ class KubernetesClientSecretsPropertySourceTests { 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 @@ class KubernetesClientSecretsPropertySourceTests { 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 @@ class KubernetesClientSecretsPropertySourceTests { 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 @@ class KubernetesClientSecretsPropertySourceTests { 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 00000000..a8bea2e9 --- /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 808574a6..fcf22077 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 @@ -29,6 +29,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.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; @@ -87,18 +88,18 @@ class LabeledSecretContextToSourceDataProviderTests { @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()); @@ -117,16 +118,15 @@ class LabeledSecretContextToSourceDataProviderTests { @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()); @@ -144,22 +144,19 @@ class LabeledSecretContextToSourceDataProviderTests { @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()); @@ -175,16 +172,15 @@ class LabeledSecretContextToSourceDataProviderTests { @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()); @@ -204,17 +200,17 @@ class LabeledSecretContextToSourceDataProviderTests { @Test void testWithPrefix() { - V1SecretList SECRETS_LIST = new V1SecretList().addItemsItem(new V1SecretBuilder() - .withMetadata(new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("blue-secret").build()) - .addToData("what-color", "blue-color".getBytes()).build()); + 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(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dblue") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST)))); ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("me", false, false, null); - NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, prefix); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, prefix, + false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); @@ -236,26 +232,22 @@ class LabeledSecretContextToSourceDataProviderTests { */ @Test void testTwoSecretsWithPrefix() { - V1SecretList SECRETS_LIST = new V1SecretList() - .addItemsItem( - new V1SecretBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")) - .withNamespace(NAMESPACE).withResourceVersion("1").withName( - "blue-secret") - .build()) - .addToData("first", "blue".getBytes()).build()) - .addItemsItem(new V1SecretBuilder() - .withMetadata( - new V1ObjectMetaBuilder().withLabels(Map.of("color", "blue")).withNamespace(NAMESPACE) - .withResourceVersion("1").withName("another-blue-secret").build()) - .addToData("second", "blue".getBytes()).build()); + 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(); - stubFor(get("/api/v1/namespaces/default/secrets?labelSelector=color%3Dblue") - .willReturn(aResponse().withStatus(200).withBody(new JSON().serialize(SECRETS_LIST)))); - NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, ConfigUtils.Prefix.DELAYED); + NormalizedSource source = new LabeledSecretNormalizedSource(NAMESPACE, Map.of("color", "blue"), false, + ConfigUtils.Prefix.DELAYED, false); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); @@ -284,4 +276,172 @@ class LabeledSecretContextToSourceDataProviderTests { 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 891eab41..550e1762 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.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; @@ -34,12 +35,10 @@ 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.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 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries) - .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); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, new MockEnvironment()); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries) - .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); MockEnvironment environment = new MockEnvironment(); environment.setActiveProfiles("with-profile"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries) - .get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -172,29 +175,29 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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)))); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null); NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix, true); @@ -202,8 +205,7 @@ class NamedConfigMapContextToSourceDataProviderTests { environment.setActiveProfiles("with-profile"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries) - .get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -214,35 +216,35 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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)))); + ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("some", false, false, null); NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, prefix, true); @@ -250,11 +252,10 @@ class NamedConfigMapContextToSourceDataProviderTests { environment.setActiveProfiles("with-taste", "with-shape"); KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, environment); - KubernetesClientContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries) - .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"); @@ -262,25 +263,29 @@ class NamedConfigMapContextToSourceDataProviderTests { } - // when reading config maps and creating normalized sources, we will always be - // providing a name - // for the config map; even if one is not provided explicitly. + /** + *
+	 * 		proves that an implicit configmap is going to be generated and read, even if
+	 * 	    we did not provide one
+	 * 
+ */ @Test void matchWithName() { - V1ConfigMapList configMapList = new V1ConfigMapList() - .addItemsItem(new V1ConfigMapBuilder().withMetadata(new V1ObjectMetaBuilder().withName("application") - .withNamespace(NAMESPACE).withResourceVersion("1").build()).addToData("color", "red").build()); + 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)))); + 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) - .get(); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.application.default"); @@ -288,48 +293,95 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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) - .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 Map processEntries(Map map, Environment environment) { - return processAllEntries(map, environment); - } + NormalizedSource source = new NamedConfigMapNormalizedSource(RED_CONFIG_MAP_NAME, NAMESPACE, true, false); + KubernetesClientConfigContext context = new KubernetesClientConfigContext(api, source, NAMESPACE, + new MockEnvironment()); + KubernetesClientContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); + SourceData sourceData = data.apply(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 cff9117e..2907eff0 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,14 +26,17 @@ 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.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.SourceData; @@ -46,8 +49,12 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options 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()); @@ -66,25 +73,20 @@ class NamedSecretContextToSourceDataProviderTests { } /** - * - * /** 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()); @@ -103,29 +105,25 @@ class NamedSecretContextToSourceDataProviderTests { @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()); @@ -144,19 +142,16 @@ class NamedSecretContextToSourceDataProviderTests { @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()); @@ -167,22 +162,26 @@ class NamedSecretContextToSourceDataProviderTests { 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()); @@ -193,4 +192,150 @@ class NamedSecretContextToSourceDataProviderTests { Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } + /** + * 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(); + + 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(); + + 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 00000000..d80949f5 --- /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 00000000..7db7b3b4 --- /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 00000000..27302005 --- /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 00000000..647e3cb6 --- /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 00000000..5e16b04b --- /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 00000000..9e875860 --- /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 1e59b034..23bbec86 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 00000000..03ab598e --- /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 4dde04d3..3f0c2682 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 00000000..6135608b --- /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 00000000..770f618d --- /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 00000000..8f29fcfe --- /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 00000000..3f625741 --- /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 00000000..33cc1d11 --- /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 00000000..e1cad951 --- /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 00000000..59bc8289 --- /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_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 00000000..eae8cd69 --- /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 00000000..76ac5592 --- /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 00000000..f4d3a0c8 --- /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 00000000..6cf9ae37 --- /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 00000000..f342746d --- /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 00000000..7231da17 --- /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 00000000..04c7e5c5 --- /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/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 index 15952083..a7600c8e 100644 --- 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 @@ -22,7 +22,7 @@ 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-config-map-with-prefix", + 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 index 7e02e95e..725ff856 100644 --- 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 @@ -36,7 +36,7 @@ import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs. @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-config-map-with-prefix.yaml" }) + "spring.config.import=kubernetes:,classpath:./named-configmap-with-prefix.yaml" }) class NamedConfigMapWithPrefixConfigDataTests extends NamedConfigMapWithPrefixTests { private static MockedStatic clientUtilsMock; diff --git a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java index 8fac5019..881b6284 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_prefix/NamedConfigMapWithPrefixTests.java @@ -26,7 +26,8 @@ import org.springframework.beans.factory.annotation.Autowired; 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 */ 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 00000000..81db4b80 --- /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 00000000..9e569f5c --- /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/KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java similarity index 64% rename from spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests.java rename to spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java index c4032040..28fa10af 100644 --- a/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests.java +++ b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_config_map_with_profile/NamedConfigMapWithProfileConfigDataTests.java @@ -14,40 +14,33 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.client.config; +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.junit.jupiter.api.extension.ExtendWith; import org.mockito.MockedStatic; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.kubernetes.client.KubernetesClientUtils; -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 static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.mockito.Mockito.mockStatic; -import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub.stubData; +import static org.springframework.cloud.kubernetes.client.config.boostrap.stubs.NamedConfigMapWithProfileConfigurationStub.stubData; /** - * @author Ryan Baxter + * @author wind57 */ -@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = IncludeProfileSpecificSourcesApp.class, - properties = { "spring.application.name=include-profile-specific-sources", - "include.profile.specific.sources=true", "spring.main.cloud-platform=KUBERNETES", - "spring.config.import=kubernetes:,classpath:./include-profile-specific-sources.yaml" }) -@AutoConfigureWebTestClient -@ActiveProfiles("dev") -public class KubernetesClientConfigMapConfigDataIncludeProfileSpecificSourcesTests - extends KubernetesClientConfigMapIncludeProfileSpecificSourcesTests { + 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; 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 00000000..a997b4e0 --- /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 fd8e4db0..3a1bd2b1 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 00000000..ac8e2715 --- /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 e96da1eb..77d6656d 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 00000000..3d5bbb80 --- /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/include_profile_specific_sources/IncludeProfileSpecificSourcesApp.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/applications/named_secret_with_profile/NamedSecretWithLabelApp.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_secret_with_profile/NamedSecretWithLabelApp.java index d719c422..64c7643c 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_secret_with_profile/NamedSecretWithLabelApp.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_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.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_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 IncludeProfileSpecificSourcesApp { +public class NamedSecretWithLabelApp { public static void main(String[] args) { - SpringApplication.run(IncludeProfileSpecificSourcesApp.class, 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 00000000..dc6b22b7 --- /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 00000000..b0f0439d --- /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 00000000..20093f5d --- /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 00000000..93758cc0 --- /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 00000000..f2d558ef --- /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 00000000..d4418e32 --- /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 00000000..64a8f67e --- /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/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 00000000..c55e1916 --- /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 00000000..6feb5848 --- /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 index d795c0be..ea30c86b 100644 --- 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 @@ -86,25 +86,14 @@ public class LabeledSecretWithPrefixConfigurationStub { .addToData(Collections.singletonMap("property", "four".getBytes())).build(); // the actual stub for CoreV1Api calls - V1SecretList oneSecrets = new V1SecretList(); - oneSecrets.addItemsItem(one); - WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Da") - .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(oneSecrets)))); + V1SecretList secrets = new V1SecretList(); + secrets.addItemsItem(one); + secrets.addItemsItem(two); + secrets.addItemsItem(three); + secrets.addItemsItem(four); - V1SecretList twoSecrets = new V1SecretList(); - twoSecrets.addItemsItem(two); - WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Db") - .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(twoSecrets)))); - - V1SecretList threeSecrets = new V1SecretList(); - threeSecrets.addItemsItem(three); - WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Dc") - .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(threeSecrets)))); - - V1SecretList fourSecrets = new V1SecretList(); - fourSecrets.addItemsItem(four); - WireMock.stubFor(WireMock.get("/api/v1/namespaces/spring-k8s/secrets?labelSelector=letter%3Dd") - .willReturn(WireMock.aResponse().withStatus(200).withBody(new JSON().serialize(fourSecrets)))); + 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 00000000..8a7b98a7 --- /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/IncludeProfileSpecificSourcesConfigurationStub.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/boostrap/stubs/NamedConfigMapWithProfileConfigurationStub.java similarity index 66% 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 eac5df20..ea83ae13 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 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options */ @Order(0) @Configuration -@ConditionalOnProperty("include.profile.specific.sources") -public class IncludeProfileSpecificSourcesConfigurationStub { +@ConditionalOnProperty("named.config.map.with.profile.stub") +public class NamedConfigMapWithProfileConfigurationStub { @Bean public WireMockServer wireMock() { @@ -65,33 +65,36 @@ public class IncludeProfileSpecificSourcesConfigurationStub { } 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 two = new V1ConfigMapBuilder() - .withMetadata(new V1ObjectMetaBuilder().withName("config-map-two").withNamespace("spring-k8s") - .withResourceVersion("1").build()) - .addToData(Collections.singletonMap("two.property", "two")).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 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 two = new V1ConfigMapBuilder() + .withMetadata(new V1ObjectMetaBuilder().withName("configmap-two").withNamespace("spring-k8s").build()) + .addToData(Collections.singletonMap("property", "two")).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 index bfbde0d9..dc8ab3df 100644 --- 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 @@ -59,7 +59,6 @@ public class NamedSecretWithPrefixConfigurationStub { 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; } 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 00000000..6031358a --- /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/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/configmap_retry/ConfigFailFastEnabledButRetryDisabled.java index 67d19781..78c255d7 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 @@ -98,8 +98,7 @@ abstract class ConfigFailFastEnabledButRetryDisabled { 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 bedffd00..d71c7a04 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 @@ -110,8 +110,7 @@ abstract class ConfigRetryDisabledButSecretsRetryEnabled { // 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 dee9aed4..17c2f0f5 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 @@ -159,8 +159,7 @@ abstract class ConfigRetryEnabled { 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 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/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-client-config/src/test/java/org/springframework/cloud/kubernetes/client/config/secrets_retry/SecretsFailFastEnabledButRetryDisabled.java index 88c6a7ff..20e0c2b0 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 @@ -110,8 +110,7 @@ class SecretsFailFastEnabledButRetryDisabled { 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 6636cac9..6a04e30c 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 @@ -118,8 +118,7 @@ class SecretsRetryDisabledButConfigRetryEnabled { // 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 16687cd7..9d6e9ac8 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 @@ -167,8 +167,7 @@ class SecretsRetryEnabled { 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 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 9a1bd646..33ce8813 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,6 +1,10 @@ org.springframework.cloud.bootstrap.BootstrapConfiguration=\ -org.springframework.cloud.kubernetes.client.config.boostrap.stubs.IncludeProfileSpecificSourcesConfigurationStub, \ +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.NamedSecretWithPrefixConfigurationStub, \ +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.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 00000000..40bea776 --- /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 00000000..8aa88d2d --- /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-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/labeled-secret-with-profile.yaml new file mode 100644 index 00000000..89255f08 --- /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/named-config-map-with-prefix.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-prefix.yaml similarity index 100% rename from spring-cloud-kubernetes-client-config/src/test/resources/named-config-map-with-prefix.yaml rename to spring-cloud-kubernetes-client-config/src/test/resources/named-configmap-with-prefix.yaml 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 00000000..da496510 --- /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-profile.yaml b/spring-cloud-kubernetes-client-config/src/test/resources/named-secret-with-profile.yaml new file mode 100644 index 00000000..6d52b01f --- /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-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 index 75f383c7..84cb4f93 100644 --- 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 @@ -31,7 +31,7 @@ import org.springframework.retry.support.RetryTemplate; */ public class ConfigDataRetryableConfigMapPropertySourceLocator extends ConfigMapPropertySourceLocator { - private RetryTemplate retryTemplate; + private final RetryTemplate retryTemplate; private ConfigMapPropertySourceLocator configMapPropertySourceLocator; 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 9665e874..bcf0406a 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,13 +16,13 @@ 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; @@ -40,18 +40,18 @@ import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.ge 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; } @@ -76,28 +76,34 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { 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(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)); } - String name = getApplicationName(environment, this.name, "Config Map"); - 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()); } @@ -116,6 +122,16 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { */ 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 @@ -128,11 +144,6 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { */ protected Boolean includeProfileSpecificSources; - /** - * An explicit prefix to be used for properties. - */ - private String explicitPrefix; - public Source() { } @@ -157,6 +168,10 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { return useNameAsPrefix; } + public Boolean getUseNameAsPrefix() { + return useNameAsPrefix; + } + public void setUseNameAsPrefix(Boolean useNameAsPrefix) { this.useNameAsPrefix = useNameAsPrefix; } @@ -177,20 +192,47 @@ public class ConfigMapConfigProperties extends AbstractConfigProperties { 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; - ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, useNameAsPrefix, + 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 58011d4a..b9b8cb40 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 @@ -69,7 +69,7 @@ public abstract class ConfigMapPropertySourceLocator implements PropertySourceLo if (this.properties.isEnableApi()) { 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 abstract class ConfigMapPropertySourceLocator implements PropertySourceLo 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 f28f6880..3bf82e20 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,8 +16,16 @@ 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; @@ -125,11 +133,11 @@ public final class ConfigUtils { /** * 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); } /* @@ -141,7 +149,8 @@ public final class ConfigUtils { 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()); + 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); } @@ -149,6 +158,104 @@ public final class ConfigUtils { 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 { /** diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSource.java new file mode 100644 index 00000000..441a88c1 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSource.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.commons.config; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + * A config map source that is based on labels. + * + * @author wind57 + */ +public final class LabeledConfigMapNormalizedSource extends NormalizedSource { + + private final Map labels; + + private final ConfigUtils.Prefix prefix; + + private final boolean includeProfileSpecificSources; + + public LabeledConfigMapNormalizedSource(String namespace, Map labels, boolean failFast, + ConfigUtils.Prefix prefix, boolean includeProfileSpecificSources) { + super(null, namespace, failFast); + this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels)); + this.prefix = Objects.requireNonNull(prefix); + this.includeProfileSpecificSources = includeProfileSpecificSources; + } + + public LabeledConfigMapNormalizedSource(String namespace, Map labels, boolean failFast, + boolean includeProfileSpecificSources) { + super(null, namespace, failFast); + this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels)); + this.prefix = ConfigUtils.Prefix.DEFAULT; + this.includeProfileSpecificSources = includeProfileSpecificSources; + } + + /** + * will return an immutable Map. + */ + public Map labels() { + return labels; + } + + public ConfigUtils.Prefix prefix() { + return prefix; + } + + public boolean profileSpecificSources() { + return this.includeProfileSpecificSources; + } + + @Override + public NormalizedSourceType type() { + return NormalizedSourceType.LABELED_CONFIG_MAP; + } + + @Override + public String target() { + return "configmap"; + } + + @Override + public String toString() { + return "{ config map labels : '" + labels() + "', namespace : '" + namespace() + "'"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LabeledConfigMapNormalizedSource other = (LabeledConfigMapNormalizedSource) o; + return Objects.equals(labels(), other.labels()) && Objects.equals(namespace(), other.namespace()); + } + + @Override + public int hashCode() { + return Objects.hash(labels(), namespace()); + } + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java index 4f32097c..aecd0f35 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSource.java @@ -31,17 +31,22 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource { private final ConfigUtils.Prefix prefix; + private final boolean includeProfileSpecificSources; + public LabeledSecretNormalizedSource(String namespace, Map labels, boolean failFast, - ConfigUtils.Prefix prefix) { + ConfigUtils.Prefix prefix, boolean includeProfileSpecificSources) { super(null, namespace, failFast); this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels)); this.prefix = Objects.requireNonNull(prefix); + this.includeProfileSpecificSources = includeProfileSpecificSources; } - public LabeledSecretNormalizedSource(String namespace, Map labels, boolean failFast) { + public LabeledSecretNormalizedSource(String namespace, Map labels, boolean failFast, + boolean includeProfileSpecificSources) { super(null, namespace, failFast); this.labels = Collections.unmodifiableMap(Objects.requireNonNull(labels)); this.prefix = ConfigUtils.Prefix.DEFAULT; + this.includeProfileSpecificSources = includeProfileSpecificSources; } /** @@ -55,6 +60,10 @@ public final class LabeledSecretNormalizedSource extends NormalizedSource { return prefix; } + public boolean profileSpecificSources() { + return this.includeProfileSpecificSources; + } + @Override public NormalizedSourceType type() { return NormalizedSourceType.LABELED_SECRET; diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSourceData.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSourceData.java new file mode 100644 index 00000000..e8f19466 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/LabeledSourceData.java @@ -0,0 +1,92 @@ +/* + * 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.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; +import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; + +/** + * @author wind57 + * + * Base class when dealing with labeled sources that support profiles specific sources, + * prefix based properties and single file yaml/properties. + */ +public abstract class LabeledSourceData { + + public final SourceData compute(Map labels, ConfigUtils.Prefix prefix, String target, + boolean profileSources, boolean failFast, String namespace, String[] activeProfiles) { + + MultipleSourcesContainer data = MultipleSourcesContainer.empty(); + + try { + Set profiles = Set.of(); + if (profileSources) { + profiles = Arrays.stream(activeProfiles).collect(Collectors.toSet()); + } + data = dataSupplier(labels, profiles); + + // need this check because when there is no data, the name of the property + // source + // is using provided labels, + // unlike when the data is present: when we use secret names + if (data.names().isEmpty()) { + String names = labels.keySet().stream().sorted() + .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); + return SourceData.emptyRecord(ConfigUtils.sourceName(target, names, namespace)); + } + + if (prefix != ConfigUtils.Prefix.DEFAULT) { + + String prefixToUse; + if (prefix == ConfigUtils.Prefix.KNOWN) { + prefixToUse = prefix.prefixProvider().get(); + } + else { + prefixToUse = data.names().stream().sorted() + .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); + } + + PrefixContext prefixContext = new PrefixContext(data.data(), prefixToUse, namespace, data.names()); + return ConfigUtils.withPrefix(target, prefixContext); + } + } + catch (Exception e) { + onException(failFast, e); + } + + String names = data.names().stream().sorted().collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); + return new SourceData(ConfigUtils.sourceName(target, names, namespace), data.data()); + + } + + /** + * Implementation specific (fabric8 or k8s-native) way to get the data from then given + * source names. + * @param labels the ones that have been configured + * @param profiles profiles to taken into account when gathering source data. Can be + * empty. + * @return a container that holds the names of the source that were found and their + * data + */ + public abstract MultipleSourcesContainer dataSupplier(Map labels, Set profiles); + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.java new file mode 100644 index 00000000..f96661d4 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/MultipleSourcesContainer.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.commons.config; + +import java.util.Map; +import java.util.Set; + +/** + * @author wind57 + * + * Container that stores multiple sources, to be exact their names and their flattenned + * data. + */ +public final record MultipleSourcesContainer(Set names, Map data) { + + private static final MultipleSourcesContainer EMPTY = new MultipleSourcesContainer(Set.of(), Map.of()); + + public static MultipleSourcesContainer empty() { + return EMPTY; + } +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java index 690b0cbd..4366ed1b 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSource.java @@ -27,14 +27,24 @@ public final class NamedSecretNormalizedSource extends NormalizedSource { private final ConfigUtils.Prefix prefix; - public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix) { + private final boolean includeProfileSpecificSources; + + public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, ConfigUtils.Prefix prefix, + boolean includeProfileSpecificSources) { super(name, namespace, failFast); this.prefix = Objects.requireNonNull(prefix); + this.includeProfileSpecificSources = includeProfileSpecificSources; } - public NamedSecretNormalizedSource(String name, String namespace, boolean failFast) { + public NamedSecretNormalizedSource(String name, String namespace, boolean failFast, + boolean includeProfileSpecificSources) { super(name, namespace, failFast); this.prefix = ConfigUtils.Prefix.DEFAULT; + this.includeProfileSpecificSources = includeProfileSpecificSources; + } + + public boolean profileSpecificSources() { + return includeProfileSpecificSources; } public ConfigUtils.Prefix prefix() { diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java new file mode 100644 index 00000000..037c36ca --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NamedSourceData.java @@ -0,0 +1,82 @@ +/* + * 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.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.springframework.cloud.kubernetes.commons.config.ConfigUtils.onException; +import static org.springframework.cloud.kubernetes.commons.config.Constants.PROPERTY_SOURCE_NAME_SEPARATOR; + +/** + * @author wind57 + * + * Base class when dealing with named sources that support profiles specific sources, + * prefix based properties and single file yaml/properties. + */ +public abstract class NamedSourceData { + + public final SourceData compute(String initialSourceName, ConfigUtils.Prefix prefix, String target, + boolean profileSources, boolean failFast, String namespace, String[] activeProfiles) { + + Set sourceNames = new HashSet<>(); + sourceNames.add(initialSourceName); + + MultipleSourcesContainer data = MultipleSourcesContainer.empty(); + String currentSourceName; + + try { + if (profileSources) { + for (String activeProfile : activeProfiles) { + currentSourceName = initialSourceName + "-" + activeProfile; + sourceNames.add(currentSourceName); + } + } + + data = dataSupplier(sourceNames); + + if (data.names().isEmpty()) { + return new SourceData(ConfigUtils.sourceName(target, initialSourceName, namespace), Map.of()); + } + + if (prefix != ConfigUtils.Prefix.DEFAULT) { + // since we are in a named source, calling get on the supplier is safe + String prefixToUse = prefix.prefixProvider().get(); + PrefixContext prefixContext = new PrefixContext(data.data(), prefixToUse, namespace, data.names()); + return ConfigUtils.withPrefix(target, prefixContext); + } + + } + catch (Exception e) { + onException(failFast, e); + } + + String names = data.names().stream().sorted().collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); + return new SourceData(ConfigUtils.sourceName(target, names, namespace), data.data()); + } + + /** + * Implementation specific (fabric8 or k8s-native) way to get the data from then given + * source names. + * @param sourceNames the ones that have been configured + * @return an Entry that holds the names of the source that were found and their data + */ + public abstract MultipleSourcesContainer dataSupplier(Set sourceNames); + +} diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSource.java index 23303d1b..43c3d0fe 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSource.java @@ -24,7 +24,7 @@ import java.util.Optional; * * @author wind57 */ -public sealed abstract class NormalizedSource permits NamedSecretNormalizedSource,LabeledSecretNormalizedSource,NamedConfigMapNormalizedSource { +public sealed abstract class NormalizedSource permits NamedSecretNormalizedSource,LabeledSecretNormalizedSource,NamedConfigMapNormalizedSource,LabeledConfigMapNormalizedSource { private final String namespace; diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSourceType.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSourceType.java index 9c09eb36..aa47226a 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSourceType.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/NormalizedSourceType.java @@ -36,6 +36,11 @@ public enum NormalizedSourceType { /** * denotes the fact that this is a config map source based on name. */ - NAMED_CONFIG_MAP + NAMED_CONFIG_MAP, + + /** + * denotes the fact that this is a config map source based on labels. + */ + LABELED_CONFIG_MAP } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java index f9cd729c..5bafde32 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigProperties.java @@ -85,28 +85,26 @@ public class SecretsConfigProperties extends AbstractConfigProperties { } /** - * @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 - * SecretsPropertySource + * @return A list of Secret source(s) to use. */ public List determineSources(Environment environment) { if (this.sources.isEmpty()) { List result = new ArrayList<>(2); String name = getApplicationName(environment, this.name, "Secret"); - result.add(new NamedSecretNormalizedSource(name, this.namespace, this.failFast)); + result.add(new NamedSecretNormalizedSource(name, this.namespace, this.failFast, + this.includeProfileSpecificSources)); if (!labels.isEmpty()) { result.add(new LabeledSecretNormalizedSource(this.namespace, this.labels, this.failFast, - ConfigUtils.Prefix.DEFAULT)); + ConfigUtils.Prefix.DEFAULT, false)); } return result; } - return this.sources.stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels, this.failFast, - this.useNameAsPrefix, environment)).collect(Collectors.toList()); + return this.sources + .stream().flatMap(s -> s.normalize(this.name, this.namespace, this.labels, + this.includeProfileSpecificSources, this.failFast, this.useNameAsPrefix, environment)) + .collect(Collectors.toList()); } public static class Source { @@ -137,6 +135,12 @@ public class SecretsConfigProperties extends AbstractConfigProperties { */ private Boolean useNameAsPrefix; + /** + * Use profile name to append to a config map name. Can't be a primitive, we need + * to know if it was explicitly set or not + */ + protected Boolean includeProfileSpecificSources; + public Source() { } @@ -180,13 +184,21 @@ public class SecretsConfigProperties extends AbstractConfigProperties { this.useNameAsPrefix = useNameAsPrefix; } + public Boolean getIncludeProfileSpecificSources() { + return includeProfileSpecificSources; + } + + public void setIncludeProfileSpecificSources(Boolean includeProfileSpecificSources) { + this.includeProfileSpecificSources = includeProfileSpecificSources; + } + public boolean isEmpty() { return !StringUtils.hasLength(this.name) && !StringUtils.hasLength(this.namespace); } private Stream normalize(String defaultName, String defaultNamespace, - Map defaultLabels, boolean failFast, boolean defaultUseNameAsPrefix, - Environment environment) { + Map defaultLabels, boolean defaultIncludeProfileSpecificSources, boolean failFast, + boolean defaultUseNameAsPrefix, Environment environment) { Stream.Builder normalizedSources = Stream.builder(); @@ -199,13 +211,15 @@ public class SecretsConfigProperties extends AbstractConfigProperties { ConfigUtils.Prefix prefix = ConfigUtils.findPrefix(this.explicitPrefix, this.useNameAsPrefix, defaultUseNameAsPrefix, normalizedName); + boolean includeProfileSpecificSources = ConfigUtils.includeProfileSpecificSources( + defaultIncludeProfileSpecificSources, this.includeProfileSpecificSources); NormalizedSource namedBasedSource = new NamedSecretNormalizedSource(secretName, normalizedNamespace, - failFast, prefix); + failFast, prefix, includeProfileSpecificSources); normalizedSources.add(namedBasedSource); if (!normalizedLabels.isEmpty()) { NormalizedSource labeledBasedSource = new LabeledSecretNormalizedSource(normalizedNamespace, labels, - failFast, prefix); + failFast, prefix, includeProfileSpecificSources); normalizedSources.add(labeledBasedSource); } diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySource.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java similarity index 89% rename from spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySource.java rename to spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java index fc29f708..4052997e 100644 --- a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapPropertySource.java +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/SourceDataEntriesProcessor.java @@ -37,21 +37,22 @@ import static org.springframework.cloud.kubernetes.commons.config.PropertySource import static org.springframework.cloud.kubernetes.commons.config.PropertySourceUtils.yamlParserGenerator; /** - * A {@link MapPropertySource} that uses Kubernetes config maps. + * Processor that extracts data from an input, where input can be a single yaml/properties + * file. * * @author Ioannis Canellos * @author Ali Shahbour * @author Michael Moudatsos */ -public abstract class ConfigMapPropertySource extends MapPropertySource { +public class SourceDataEntriesProcessor extends MapPropertySource { - private static final Log LOG = LogFactory.getLog(ConfigMapPropertySource.class); + private static final Log LOG = LogFactory.getLog(SourceDataEntriesProcessor.class); - public ConfigMapPropertySource(SourceData sourceData) { + public SourceDataEntriesProcessor(SourceData sourceData) { super(sourceData.sourceName(), sourceData.sourceData()); } - protected static Map processAllEntries(Map input, Environment environment) { + public static Map processAllEntries(Map input, Environment environment) { Set> entrySet = input.entrySet(); if (entrySet.size() == 1) { diff --git a/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/StrippedSourceContainer.java b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/StrippedSourceContainer.java new file mode 100644 index 00000000..7044b904 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/main/java/org/springframework/cloud/kubernetes/commons/config/StrippedSourceContainer.java @@ -0,0 +1,28 @@ +/* + * 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.Map; + +/** + * @author wind57 + * + * Container for some of the source's fields, it holds its labels (nullable), name and + * data. + */ +public final record StrippedSourceContainer(Map labels, String name, Map data) { +} diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java index 45d17f9e..b0d100fd 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigMapConfigPropertiesTests.java @@ -18,7 +18,10 @@ package org.springframework.cloud.kubernetes.commons.config; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -336,4 +339,89 @@ class ConfigMapConfigPropertiesTests { Assertions.assertFalse(((NamedConfigMapNormalizedSource) sources.get(2)).profileSpecificSources()); } + /** + *
+	 * spring:
+	 *	cloud:
+	 *    kubernetes:
+	 *      config:
+	 *        useNameAsPrefix: false
+	 *        namespace: spring-k8s
+	 *        includeProfileSpecificSources: false
+	 *        sources:
+	 *          - labels:
+	 *              - name: first-label
+	 *                value: configmap-one
+	 *            useNameAsPrefix: false
+	 *            explicitPrefix: one
+	 *          - labels:
+	 *          	- name: second-label
+	 * 	          	  value: configmap-two
+	 * 	          includeProfileSpecificSources: true
+	 *            useNameAsPrefix: true
+	 *            explicitPrefix: two
+	 *          - labels:
+	 *          	- name: third-label
+	 * 	          	  value: configmap-three
+	 *            explicitPrefix: three
+	 *          - labels:
+	 * 	         	- name: fourth-label
+	 * 	           	  value: configmap-four
+	 * 
+ * + */ + @Test + void testLabelsMultipleCases() { + ConfigMapConfigProperties properties = new ConfigMapConfigProperties(); + properties.setUseNameAsPrefix(false); + properties.setNamespace("spring-k8s"); + properties.setIncludeProfileSpecificSources(false); + + ConfigMapConfigProperties.Source one = new ConfigMapConfigProperties.Source(); + one.setLabels(Map.of("first-label", "configmap-one")); + one.setUseNameAsPrefix(false); + one.setExplicitPrefix("one"); + + ConfigMapConfigProperties.Source two = new ConfigMapConfigProperties.Source(); + two.setLabels(Map.of("second-label", "configmap-two")); + two.setUseNameAsPrefix(true); + two.setExplicitPrefix("two"); + two.setIncludeProfileSpecificSources(true); + + ConfigMapConfigProperties.Source three = new ConfigMapConfigProperties.Source(); + three.setLabels(Map.of("third-label", "configmap-three")); + three.setExplicitPrefix("three"); + + ConfigMapConfigProperties.Source four = new ConfigMapConfigProperties.Source(); + four.setLabels(Map.of("fourth-label", "configmap-four")); + + properties.setSources(Arrays.asList(one, two, three, four)); + + List sources = properties.determineSources(new MockEnvironment()); + // we get 8 property sources, since "named" ones with "application" are + // duplicated. + // that's OK, since later in the code we get a LinkedHashSet out of them all, + // so they become 5 only. + Assertions.assertEquals(sources.size(), 8, "4 NormalizedSources are expected"); + + LabeledConfigMapNormalizedSource labeled1 = (LabeledConfigMapNormalizedSource) sources.get(1); + Assertions.assertEquals(labeled1.prefix().prefixProvider().get(), "one"); + Assertions.assertFalse(labeled1.profileSpecificSources()); + + LabeledConfigMapNormalizedSource labeled3 = (LabeledConfigMapNormalizedSource) sources.get(3); + Assertions.assertEquals(labeled3.prefix().prefixProvider().get(), "two"); + Assertions.assertTrue(labeled3.profileSpecificSources()); + + LabeledConfigMapNormalizedSource labeled5 = (LabeledConfigMapNormalizedSource) sources.get(5); + Assertions.assertEquals(labeled5.prefix().prefixProvider().get(), "three"); + Assertions.assertFalse(labeled5.profileSpecificSources()); + + LabeledConfigMapNormalizedSource labeled7 = (LabeledConfigMapNormalizedSource) sources.get(7); + Assertions.assertSame(labeled7.prefix(), ConfigUtils.Prefix.DEFAULT); + Assertions.assertFalse(labeled7.profileSpecificSources()); + + Set set = new LinkedHashSet<>(sources); + Assertions.assertEquals(5, set.size()); + } + } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java index c7843dee..b8c7db4f 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/ConfigUtilsTests.java @@ -138,11 +138,22 @@ class ConfigUtilsTests { SourceData result = ConfigUtils.withPrefix("configmap", context); - Assertions.assertEquals(result.sourceName().length(), 31); - Assertions.assertTrue(result.sourceName().contains("name2")); - Assertions.assertTrue(result.sourceName().contains("name1")); - Assertions.assertTrue(result.sourceName().contains("configmap")); - Assertions.assertTrue(result.sourceName().contains("namespace")); + Assertions.assertEquals(result.sourceName(), "configmap.name1.name2.namespace"); + + Assertions.assertEquals(result.sourceData().get("prefix.a"), "b"); + Assertions.assertEquals(result.sourceData().get("prefix.c"), "d"); + } + + /* + * source names should be reproducible all the time, this test asserts this. + */ + @Test + void testWithPrefixSortedName() { + PrefixContext context = new PrefixContext(Map.of("a", "b", "c", "d"), "prefix", "namespace", + Set.of("namec", "namea", "nameb")); + + SourceData result = ConfigUtils.withPrefix("configmap", context); + Assertions.assertEquals(result.sourceName(), "configmap.namea.nameb.namec.namespace"); Assertions.assertEquals(result.sourceData().get("prefix.a"), "b"); Assertions.assertEquals(result.sourceData().get("prefix.c"), "d"); diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSourceTests.java new file mode 100644 index 00000000..068716e0 --- /dev/null +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledConfigMapNormalizedSourceTests.java @@ -0,0 +1,78 @@ +/* + * 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.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author wind57 + */ +class LabeledConfigMapNormalizedSourceTests { + + private static final ConfigUtils.Prefix PREFIX = ConfigUtils.findPrefix("prefix", false, false, "prefix"); + + @Test + void testEqualsAndHashCode() { + LabeledConfigMapNormalizedSource left = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"), + false, false); + LabeledConfigMapNormalizedSource right = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"), + true, false); + + Assertions.assertEquals(left.hashCode(), right.hashCode()); + Assertions.assertEquals(left, right); + } + + @Test + void testType() { + LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"), + false, false); + Assertions.assertSame(source.type(), NormalizedSourceType.LABELED_CONFIG_MAP); + } + + @Test + void testTarget() { + LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("name", Map.of("key", "value"), + false, false); + Assertions.assertEquals(source.target(), "configmap"); + } + + @Test + void testConstructorFields() { + LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("namespace", + Map.of("key", "value"), false, PREFIX, true); + Assertions.assertEquals(source.labels(), Map.of("key", "value")); + Assertions.assertEquals(source.namespace().get(), "namespace"); + Assertions.assertFalse(source.failFast()); + Assertions.assertSame(PREFIX, source.prefix()); + Assertions.assertTrue(source.profileSpecificSources()); + } + + @Test + void testConstructorWithoutPrefixFields() { + LabeledConfigMapNormalizedSource source = new LabeledConfigMapNormalizedSource("namespace", + Map.of("key", "value"), true, true); + Assertions.assertEquals(source.labels(), Map.of("key", "value")); + Assertions.assertEquals(source.namespace().get(), "namespace"); + Assertions.assertTrue(source.failFast()); + Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix()); + Assertions.assertTrue(source.profileSpecificSources()); + } + +} diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java index 7b060320..9f0774f3 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/LabeledSecretNormalizedSourceTests.java @@ -31,8 +31,8 @@ class LabeledSecretNormalizedSourceTests { @Test void testEqualsAndHashCode() { - LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, false); - LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true); + LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, false, false); + LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, false); Assertions.assertEquals(left.hashCode(), right.hashCode()); Assertions.assertEquals(left, right); @@ -48,8 +48,10 @@ class LabeledSecretNormalizedSourceTests { ConfigUtils.Prefix knownLeft = ConfigUtils.findPrefix("left", false, false, "some"); ConfigUtils.Prefix knownRight = ConfigUtils.findPrefix("right", false, false, "some"); - LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, true, knownLeft); - LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, knownRight); + LabeledSecretNormalizedSource left = new LabeledSecretNormalizedSource("namespace", labels, true, knownLeft, + false); + LabeledSecretNormalizedSource right = new LabeledSecretNormalizedSource("namespace", labels, true, knownRight, + false); Assertions.assertEquals(left.hashCode(), right.hashCode()); Assertions.assertEquals(left, right); @@ -57,30 +59,40 @@ class LabeledSecretNormalizedSourceTests { @Test void testType() { - LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false); + LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false); Assertions.assertSame(source.type(), NormalizedSourceType.LABELED_SECRET); } @Test void testImmutableGetLabels() { - LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false); + LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false); Assertions.assertThrows(RuntimeException.class, () -> source.labels().put("c", "d")); } @Test void testTarget() { - LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false); + LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, false); Assertions.assertEquals(source.target(), "secret"); } @Test void testConstructorFields() { ConfigUtils.Prefix prefix = ConfigUtils.findPrefix("prefix", false, false, "some"); - LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, prefix); + LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, false, prefix, + true); Assertions.assertTrue(source.name().isEmpty()); Assertions.assertEquals(source.namespace().get(), "namespace"); Assertions.assertFalse(source.failFast()); - Assertions.assertSame(source.prefix(), prefix); + Assertions.assertTrue(source.profileSpecificSources()); + } + + @Test + void testConstructorWithoutPrefixFields() { + LabeledSecretNormalizedSource source = new LabeledSecretNormalizedSource("namespace", labels, true, true); + Assertions.assertEquals(source.namespace().get(), "namespace"); + Assertions.assertTrue(source.failFast()); + Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix()); + Assertions.assertTrue(source.profileSpecificSources()); } } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java index ee93ac57..9739d387 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/NamedSecretNormalizedSourceTests.java @@ -28,8 +28,8 @@ class NamedSecretNormalizedSourceTests { @Test void testEqualsAndHashCode() { - NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false); - NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true); + NamedSecretNormalizedSource left = new NamedSecretNormalizedSource("name", "namespace", false, false); + NamedSecretNormalizedSource right = new NamedSecretNormalizedSource("name", "namespace", true, false); Assertions.assertEquals(left.hashCode(), right.hashCode()); Assertions.assertEquals(left, right); @@ -37,23 +37,34 @@ class NamedSecretNormalizedSourceTests { @Test void testType() { - NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false); + NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, false); Assertions.assertSame(source.type(), NormalizedSourceType.NAMED_SECRET); } @Test void testTarget() { - NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false); + NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, false); Assertions.assertEquals(source.target(), "secret"); } @Test void testConstructorFields() { - NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, PREFIX); + NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", false, PREFIX, true); Assertions.assertEquals(source.name().get(), "name"); Assertions.assertEquals(source.namespace().get(), "namespace"); Assertions.assertFalse(source.failFast()); Assertions.assertSame(PREFIX, source.prefix()); + Assertions.assertTrue(source.profileSpecificSources()); + } + + @Test + void testConstructorWithoutPrefixFields() { + NamedSecretNormalizedSource source = new NamedSecretNormalizedSource("name", "namespace", true, true); + Assertions.assertEquals(source.name().get(), "name"); + Assertions.assertEquals(source.namespace().get(), "namespace"); + Assertions.assertTrue(source.failFast()); + Assertions.assertSame(ConfigUtils.Prefix.DEFAULT, source.prefix()); + Assertions.assertTrue(source.profileSpecificSources()); } } diff --git a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java index 194ea83a..ef689907 100644 --- a/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java +++ b/spring-cloud-kubernetes-commons/src/test/java/org/springframework/cloud/kubernetes/commons/config/SecretsConfigPropertiesTests.java @@ -313,6 +313,7 @@ class SecretsConfigPropertiesTests { * secrets: * useNameAsPrefix: false * namespace: spring-k8s + * includeProfileSpecificSources: false * sources: * - labels: * - name: first-label @@ -322,6 +323,7 @@ class SecretsConfigPropertiesTests { * - labels: * - name: second-label * value: secret-two + * includeProfileSpecificSources: true * useNameAsPrefix: true * explicitPrefix: two * - labels: @@ -339,6 +341,7 @@ class SecretsConfigPropertiesTests { SecretsConfigProperties properties = new SecretsConfigProperties(); properties.setUseNameAsPrefix(false); properties.setNamespace("spring-k8s"); + properties.setIncludeProfileSpecificSources(false); SecretsConfigProperties.Source one = new SecretsConfigProperties.Source(); one.setLabels(Map.of("first-label", "secret-one")); @@ -349,6 +352,7 @@ class SecretsConfigPropertiesTests { two.setLabels(Map.of("second-label", "secret-two")); two.setUseNameAsPrefix(true); two.setExplicitPrefix("two"); + two.setIncludeProfileSpecificSources(true); SecretsConfigProperties.Source three = new SecretsConfigProperties.Source(); three.setLabels(Map.of("third-label", "secret-three")); @@ -366,13 +370,21 @@ class SecretsConfigPropertiesTests { // so they become 5 only. Assertions.assertEquals(sources.size(), 8, "4 NormalizedSources are expected"); - Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(1)).prefix().prefixProvider().get(), - "one"); - Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(3)).prefix().prefixProvider().get(), - "two"); - Assertions.assertEquals(((LabeledSecretNormalizedSource) sources.get(5)).prefix().prefixProvider().get(), - "three"); - Assertions.assertSame(((LabeledSecretNormalizedSource) sources.get(7)).prefix(), ConfigUtils.Prefix.DEFAULT); + LabeledSecretNormalizedSource labeled1 = (LabeledSecretNormalizedSource) sources.get(1); + Assertions.assertEquals(labeled1.prefix().prefixProvider().get(), "one"); + Assertions.assertFalse(labeled1.profileSpecificSources()); + + LabeledSecretNormalizedSource labeled3 = (LabeledSecretNormalizedSource) sources.get(3); + Assertions.assertEquals(labeled3.prefix().prefixProvider().get(), "two"); + Assertions.assertTrue(labeled3.profileSpecificSources()); + + LabeledSecretNormalizedSource labeled5 = (LabeledSecretNormalizedSource) sources.get(5); + Assertions.assertEquals(labeled5.prefix().prefixProvider().get(), "three"); + Assertions.assertFalse(labeled5.profileSpecificSources()); + + LabeledSecretNormalizedSource labeled7 = (LabeledSecretNormalizedSource) sources.get(7); + Assertions.assertSame(labeled7.prefix(), ConfigUtils.Prefix.DEFAULT); + Assertions.assertFalse(labeled7.profileSpecificSources()); Set set = new LinkedHashSet<>(sources); Assertions.assertEquals(5, set.size()); 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 e8ef2982..66e48948 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 @@ -96,7 +96,7 @@ public class KubernetesConfigServerAutoConfiguration { 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/KubernetesEnvironmentRepositoryTests.java b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configserver/src/test/java/org/springframework/cloud/kubernetes/configserver/KubernetesEnvironmentRepositoryTests.java index ca297b49..bd3ce126 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 @@ import static org.mockito.Mockito.when; */ 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,17 +87,18 @@ 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, @@ -114,10 +114,10 @@ class KubernetesEnvironmentRepositoryTests { 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,7 +136,7 @@ class KubernetesEnvironmentRepositoryTests { 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 -> { @@ -166,7 +166,7 @@ class KubernetesEnvironmentRepositoryTests { 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 -> { @@ -201,7 +201,7 @@ class KubernetesEnvironmentRepositoryTests { 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,7 +216,7 @@ class KubernetesEnvironmentRepositoryTests { 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 -> { @@ -224,7 +224,7 @@ class KubernetesEnvironmentRepositoryTests { || propertySource.getName().equals("secret.application.default") || propertySource.getName().equals("configmap.stores.stores-dev.default") || propertySource.getName().equals("configmap.stores.dev") - || propertySource.getName().equals("secret.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); @@ -249,13 +249,10 @@ class KubernetesEnvironmentRepositoryTests { 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("secret.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-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 1bf44605..77083d9d 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 @@ package org.springframework.cloud.kubernetes.fabric8.config; 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 @@ import org.springframework.core.env.MapPropertySource; * @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,10 +52,12 @@ public final class Fabric8ConfigMapPropertySource extends ConfigMapPropertySourc .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).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 ac702c91..4bc66c5f 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.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; /** @@ -91,33 +94,110 @@ final class Fabric8ConfigUtils { } - /* - * 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(); - - if (configMap == null) { - LOG.warn("config-map with name : '" + name + "' not present in namespace : '" + namespace + "'"); - return Collections.emptyMap(); + 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(); } - return configMap.getData(); + List strippedSources = strippedSecrets(secrets); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, true); + } /** - * return decoded data from a secret within a namespace. + *
+	 *     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 Map dataFromSecret(Secret secret, String namespace) { - LOG.debug("reading secret with name : " + secret.getMetadata().getName() + " in namespace : " + namespace); - return secretData(secret.getData()); + 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(); + } + + List strippedSources = strippedConfigMaps(configMaps); + return ConfigUtils.processLabeledData(strippedSources, environment, labels, namespace, profiles, false); } - 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; + /** + *
+	 *     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 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 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/LabeledConfigMapContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProvider.java new file mode 100644 index 00000000..8058f041 --- /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 b927a918..efad27a7 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,27 +16,13 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.HashMap; -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 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.ConfigUtils; import org.springframework.cloud.kubernetes.commons.config.LabeledSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -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. @@ -45,8 +31,6 @@ import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigU */ final class LabeledSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - LabeledSecretContextToSourceDataProvider() { } @@ -66,76 +50,19 @@ final class LabeledSecretContextToSourceDataProvider implements Supplier { - LabeledSecretNormalizedSource source = ((LabeledSecretNormalizedSource) context.normalizedSource()); - Set propertySourceNames = new LinkedHashSet<>(); - Map labels = source.labels(); + LabeledSecretNormalizedSource source = (LabeledSecretNormalizedSource) context.normalizedSource(); - Map result = new HashMap<>(); - String namespace = context.namespace(); - String sourceNameFromLabels = 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()) { - - for (Secret secret : secrets) { - // we support prefix per source, not per secret. This means that - // in theory - // we can still have clashes here, that we simply override. - // If there are more than one secret found per labels, and they - // have the same key on a - // property, but different values; one value will override the - // other, without any - // particular order. - result.putAll(dataFromSecret(secret, namespace)); - } - - String secretNames = secrets.stream().map(Secret::getMetadata).map(ObjectMeta::getName).sorted() - .collect(Collectors.joining(PROPERTY_SOURCE_NAME_SEPARATOR)); - propertySourceNames.add(secretNames); - - if (source.prefix() != ConfigUtils.Prefix.DEFAULT) { - - String prefix; - if (source.prefix() == ConfigUtils.Prefix.KNOWN) { - prefix = source.prefix().prefixProvider().get(); - } - else { - // prefix is going to be all the secret names we found based - // on the labels - // concatenated with PROPERTY_SOURCE_NAME_SEPARATOR - prefix = secretNames; - } - - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), prefixContext); - } - - String names = String.join(PROPERTY_SOURCE_NAME_SEPARATOR, propertySourceNames); - return new SourceData(ConfigUtils.sourceName(source.target(), names, namespace), result); - - } - else { - LOG.info("No Secret(s) with labels '" + labels + "' in namespace '" + namespace + "' found."); + return new LabeledSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Map labels, Set profiles) { + return Fabric8ConfigUtils.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); - } - - // if we could not find a secret with provided labels, we will compute a - // response with an empty Map - // and name that will use all the label names (not their values) - String propertySourceNameFromLabels = ConfigUtils.sourceName(source.target(), sourceNameFromLabels, - namespace); - return new SourceData(propertySourceNameFromLabels, 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 5159aeae..a1c6e4e2 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,26 +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.Supplier; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.kubernetes.commons.config.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedConfigMapNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -import org.springframework.cloud.kubernetes.commons.config.SourceData; -import org.springframework.core.env.Environment; - -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 @@ -45,18 +31,7 @@ import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigU */ final class NamedConfigMapContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(NamedConfigMapContextToSourceDataProvider.class); - - private final BiFunction, Environment, Map> entriesProcessor; - - private NamedConfigMapContextToSourceDataProvider( - BiFunction, Environment, Map> entriesProcessor) { - this.entriesProcessor = Objects.requireNonNull(entriesProcessor); - } - - static NamedConfigMapContextToSourceDataProvider of( - BiFunction, Environment, Map> entriesProcessor) { - return new NamedConfigMapContextToSourceDataProvider(entriesProcessor); + NamedConfigMapContextToSourceDataProvider() { } /* @@ -72,47 +47,15 @@ final class NamedConfigMapContextToSourceDataProvider implements Supplier { NamedConfigMapNormalizedSource source = (NamedConfigMapNormalizedSource) context.normalizedSource(); - String namespace = context.namespace(); - String initialConfigMapName = source.name().orElseThrow(); - 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())); - } - } + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return Fabric8ConfigUtils.configMapsDataByName(context.client(), context.namespace(), sourceNames, + context.environment()); } - - if (source.prefix() != ConfigUtils.Prefix.DEFAULT) { - // since we are in a named source, calling get on the supplier is safe - String prefix = source.prefix().prefixProvider().get(); - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), prefixContext); - } - - } - 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(ConfigUtils.sourceName(source.target(), names, namespace), 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/NamedSecretContextToSourceDataProvider.java b/spring-cloud-kubernetes-fabric8-config/src/main/java/org/springframework/cloud/kubernetes/fabric8/config/NamedSecretContextToSourceDataProvider.java index b01e4cff..2d3d793c 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,23 +16,12 @@ package org.springframework.cloud.kubernetes.fabric8.config; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; 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.ConfigUtils; +import org.springframework.cloud.kubernetes.commons.config.MultipleSourcesContainer; import org.springframework.cloud.kubernetes.commons.config.NamedSecretNormalizedSource; -import org.springframework.cloud.kubernetes.commons.config.PrefixContext; -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. @@ -41,8 +30,6 @@ import static org.springframework.cloud.kubernetes.fabric8.config.Fabric8ConfigU */ final class NamedSecretContextToSourceDataProvider implements Supplier { - private static final Log LOG = LogFactory.getLog(LabeledSecretContextToSourceDataProvider.class); - NamedSecretContextToSourceDataProvider() { } @@ -51,44 +38,15 @@ final class NamedSecretContextToSourceDataProvider implements Supplier { NamedSecretNormalizedSource source = (NamedSecretNormalizedSource) context.normalizedSource(); - Set propertySourceNames = new LinkedHashSet<>(); - propertySourceNames.add(source.name().orElseThrow()); - 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"); + return new NamedSourceData() { + @Override + public MultipleSourcesContainer dataSupplier(Set sourceNames) { + return Fabric8ConfigUtils.secretsDataByName(context.client(), context.namespace(), sourceNames, + context.environment()); } - else { - result = dataFromSecret(secret, namespace); - - if (source.prefix() != ConfigUtils.Prefix.DEFAULT) { - // since we are in a named source, calling get on the supplier is - // safe - String prefix = source.prefix().prefixProvider().get(); - PrefixContext prefixContext = new PrefixContext(result, prefix, namespace, propertySourceNames); - return ConfigUtils.withPrefix(source.target(), prefixContext); - } - } - - } - catch (Exception e) { - String message = "Unable to read Secret with name '" + secretName + "' in namespace '" + namespace - + "'"; - onException(source.failFast(), message, e); - } - - String sourceName = ConfigUtils.sourceName(source.target(), 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/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 index d57fd42e..fb75d005 100644 --- 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 @@ -19,14 +19,11 @@ 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.cloud.bootstrap.enabled=true" }) @TestPropertySource("classpath:/application-secrets.properties") 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 043b202c..7db69d96 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 java.util.Map; 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; @@ -36,36 +39,38 @@ import org.springframework.cloud.kubernetes.fabric8.config.reload.EventBasedConf 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()); + Fabric8ConfigContext context = new Fabric8ConfigContext(k8sClient, source, "default", env); Fabric8ConfigMapPropertySource fabric8ConfigMapPropertySource = new Fabric8ConfigMapPropertySource(context); env.getPropertySources().addFirst(new BootstrapPropertySource<>(fabric8ConfigMapPropertySource)); 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 adcbf26c..5e958a3c 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 @@ -52,7 +52,7 @@ class Fabric8ConfigMapPropertySourceLocatorTests { 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(); @@ -65,7 +65,7 @@ class Fabric8ConfigMapPropertySourceLocatorTests { 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 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 c2aa4d35..1b826ad2 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 @@ -50,13 +50,13 @@ class Fabric8ConfigMapPropertySourceTests { 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); 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 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 bc8df7a2..0b779d46 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 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; * @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 @@ public class Fabric8ConfigUtilsTests { .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 c5dfde9a..fc975aa6 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 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; * @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 class Fabric8SecretsPropertySourceLocatorTests { 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 da916d1a..d4c03b27 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 @@ class Fabric8SecretsPropertySourceMockTests { 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 @@ class Fabric8SecretsPropertySourceMockTests { 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 @@ class Fabric8SecretsPropertySourceMockTests { 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/LabeledConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/LabeledConfigMapContextToSourceDataProviderTests.java new file mode 100644 index 00000000..1c967145 --- /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 88db0bda..c9099d5d 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 @@ -93,7 +93,7 @@ class LabeledSecretContextToSourceDataProviderTests { 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()); @@ -126,7 +126,7 @@ class LabeledSecretContextToSourceDataProviderTests { 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()); @@ -151,7 +151,7 @@ class LabeledSecretContextToSourceDataProviderTests { 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()); @@ -177,7 +177,7 @@ class LabeledSecretContextToSourceDataProviderTests { 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()); @@ -204,7 +204,7 @@ class LabeledSecretContextToSourceDataProviderTests { ConfigUtils.Prefix mePrefix = ConfigUtils.findPrefix("me", false, false, "irrelevant"); NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, - Collections.singletonMap("color", "blue"), true, mePrefix); + Collections.singletonMap("color", "blue"), true, mePrefix, false); Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); @@ -238,19 +238,14 @@ class LabeledSecretContextToSourceDataProviderTests { mockClient.secrets().inNamespace(NAMESPACE).create(anotherBlue); NormalizedSource normalizedSource = new LabeledSecretNormalizedSource(NAMESPACE, - Collections.singletonMap("color", "blue"), true, ConfigUtils.Prefix.DELAYED); + 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); - // 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")); + Assertions.assertEquals(sourceData.sourceName(), "secret.another-blue-secret.blue-secret.default"); Map properties = sourceData.sourceData(); Assertions.assertEquals(2, properties.size()); @@ -267,4 +262,188 @@ class LabeledSecretContextToSourceDataProviderTests { 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/NamedConfigMapContextToSourceDataProviderTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/NamedConfigMapContextToSourceDataProviderTests.java index 6e21f425..31896e46 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.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -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; /** @@ -51,6 +49,8 @@ class NamedConfigMapContextToSourceDataProviderTests { 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() { @@ -70,13 +70,16 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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); @@ -84,7 +87,7 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.blue.default"); @@ -93,13 +96,16 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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); @@ -107,23 +113,26 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).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(); @@ -138,7 +147,7 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -149,16 +158,19 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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(); @@ -174,7 +186,7 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.red.red-with-profile.default"); @@ -185,16 +197,18 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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(); @@ -214,10 +228,10 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, env); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).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"); @@ -225,9 +239,12 @@ class NamedConfigMapContextToSourceDataProviderTests { } - // when reading config maps and creating normalized sources, we will always be - // providing a name - // for the config map; even if one is not provided explicitly. + /** + *
+	 * 		proves that an implicit configmap is going to be generated and read, even if
+	 * 	    we did not provide one
+	 * 
+ */ @Test void matchWithName() { ConfigMap configMap = new ConfigMapBuilder().withNewMetadata().withName("application").endMetadata() @@ -239,7 +256,7 @@ class NamedConfigMapContextToSourceDataProviderTests { Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, new MockEnvironment()); - Fabric8ContextToSourceData data = NamedConfigMapContextToSourceDataProvider.of(Dummy::processEntries).get(); + Fabric8ContextToSourceData data = new NamedConfigMapContextToSourceDataProvider().get(); SourceData sourceData = data.apply(context); Assertions.assertEquals(sourceData.sourceName(), "configmap.application.default"); @@ -247,42 +264,80 @@ class NamedConfigMapContextToSourceDataProviderTests { } /** - * 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).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(); - private Dummy() { - super(SourceData.emptyRecord("dummy-name")); - } + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); - private static Map processEntries(Map map, Environment environment) { - return processAllEntries(map, environment); - } + 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")); + } + + /** + *
+	 *     - 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(); + + mockClient.configMaps().inNamespace(NAMESPACE).create(configMap); + MockEnvironment environment = new MockEnvironment(); + environment.setActiveProfiles("k8s"); + + NormalizedSource normalizedSource = new NamedConfigMapNormalizedSource("one", NAMESPACE, true, true); + Fabric8ConfigContext context = new Fabric8ConfigContext(mockClient, normalizedSource, NAMESPACE, environment); + + 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 75fc21a1..bcfa758c 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,6 +30,7 @@ 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.SourceData; @@ -47,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() { @@ -76,7 +79,7 @@ class NamedSecretContextToSourceDataProviderTests { 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()); @@ -102,13 +105,13 @@ class NamedSecretContextToSourceDataProviderTests { .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()); @@ -132,7 +135,7 @@ class NamedSecretContextToSourceDataProviderTests { 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()); @@ -158,7 +161,7 @@ class NamedSecretContextToSourceDataProviderTests { 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()); @@ -169,4 +172,138 @@ class NamedSecretContextToSourceDataProviderTests { Assertions.assertEquals(sourceData.sourceData(), Map.of("color", "really-red")); } + /** + * 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"); + + 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-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/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 00000000..2d564ccf --- /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 00000000..2e5cca40 --- /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 00000000..15c13eaf --- /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 00000000..18de2b43 --- /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 00000000..3a250a84 --- /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 00000000..7b74ea90 --- /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 af88a5c0..d993255b 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 00000000..fb2d925e --- /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 c14b1b0f..160d8bdd 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 00000000..ca4b2d1c --- /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/BootstrapConfigMapWithIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java similarity index 61% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapWithIncludeProfileSpecificSourcesTests.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java index 6ed645bd..bda9a6da 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/BootstrapConfigMapWithIncludeProfileSpecificSourcesTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_config_map_with_profile/LabeledConfigMapWithProfileBootstrapTests.java @@ -14,36 +14,30 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config; +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.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.include_profile_specific_sources.IncludeProfileSpecificSourcesApp; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author wind57 */ -@ExtendWith(SpringExtension.class) +@ActiveProfiles({ "k8s", "prod" }) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = IncludeProfileSpecificSourcesApp.class, - properties = { "spring.cloud.bootstrap.name=include-profile-specific-sources", + classes = LabeledConfigMapWithProfileApp.class, + properties = { "spring.cloud.bootstrap.name=labeled-configmap-with-profile", "spring.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true" }) -@AutoConfigureWebTestClient @EnableKubernetesMockClient(crud = true, https = false) -@ActiveProfiles("dev") -class BootstrapConfigMapWithIncludeProfileSpecificSourcesTests extends ConfigMapWithIncludeProfileSpecificSourcesTests { +class LabeledConfigMapWithProfileBootstrapTests extends LabeledConfigMapWithProfileTests { private static KubernetesClient mockClient; @BeforeAll - public static void setUpBeforeClass() { + 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 00000000..4a49edac --- /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 00000000..a3915d0c --- /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 00000000..6f9818d1 --- /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 00000000..40ad4330 --- /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 00000000..0c34cbe1 --- /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/LabeledSecretWithPrefixConfigDataTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/labeled_secret_with_prefix/LabeledSecretWithPrefixConfigDataTests.java index e9d120c9..d21095aa 100644 --- 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 @@ -26,7 +26,7 @@ import org.springframework.boot.test.context.SpringBootTest; * @author wind57 */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = LabeledSecretWithPrefixApp.class, - properties = { "spring.application.name=named-secret-with-prefix", "spring.main.cloud-platform=KUBERNETES", + 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 { 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 00000000..29b5e311 --- /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 00000000..972e78e6 --- /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 00000000..c2e50549 --- /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 00000000..f2b8a22c --- /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 00000000..8464aec5 --- /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 00000000..7f1b9aac --- /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 00000000..1d08459d --- /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/ConfigFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigFailFastEnabledButRetryDisabled.java index 26a2a98c..ec78e9b9 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 @@ -75,7 +75,7 @@ abstract class ConfigFailFastEnabledButRetryDisabled { 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 30f1d57c..39335f31 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,6 +16,7 @@ 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.KubernetesMockServer; @@ -37,7 +38,7 @@ import static org.mockito.Mockito.verify; */ 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; @@ -55,6 +56,9 @@ abstract class ConfigRetryDisabledButSecretsRetryEnabled { 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"); + + // return empty secret list to not fail context creation + mockServer.expect().withPath(API).andReturn(200, new ConfigMapListBuilder().build()).always(); } @Autowired @@ -69,12 +73,12 @@ abstract class ConfigRetryDisabledButSecretsRetryEnabled { * defined. ConfigMapPropertySourceLocator should not retry even Spring Retry is * enabled. */ - + mockServer.clearExpectations(); mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); assertRetryBean(context); assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name 'application' in namespace 'default'"); + .hasMessageContaining("v1/namespaces/default/configmaps. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once 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/ConfigRetryEnabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/locator_retry/ConfigRetryEnabled.java index b347f75f..e2e27b0c 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,6 +20,7 @@ import java.util.HashMap; 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.KubernetesMockServer; @@ -41,7 +42,7 @@ import static org.mockito.Mockito.verify; */ 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; @@ -60,6 +61,9 @@ abstract class ConfigRetryEnabled { 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"); + + // needed so that initial call, before our test method kicks in, succeeds + mockServer.expect().withPath(API).andReturn(200, new ConfigMapListBuilder().build()).once(); } @Test @@ -69,8 +73,9 @@ abstract class ConfigRetryEnabled { 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(() -> psl.locate(new MockEnvironment())); @@ -91,8 +96,9 @@ abstract class ConfigRetryEnabled { // 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(() -> psl.locate(new MockEnvironment())); @@ -111,7 +117,7 @@ abstract class ConfigRetryEnabled { mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(5); assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read ConfigMap with name 'application' in namespace 'default'"); + .hasMessageContaining("api/v1/namespaces/default/configmaps. Message: Internal Server Error"); // verify retried 5 times until failure 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_profile/NamedConfigMapWithProfileApp.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_profile/NamedConfigMapWithProfileApp.java index 992de0d8..4ad63b1c 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_profile/NamedConfigMapWithProfileApp.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_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.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_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 IncludeProfileSpecificSourcesApp { +public class NamedConfigMapWithProfileApp { public static void main(String[] args) { - SpringApplication.run(IncludeProfileSpecificSourcesApp.class, args); + SpringApplication.run(NamedConfigMapWithProfileApp.class, args); } } diff --git a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapWithIncludeProfileSpecificSourcesTests.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java similarity index 55% rename from spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapWithIncludeProfileSpecificSourcesTests.java rename to spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java index 06b932a8..b97290ce 100644 --- a/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/ConfigDataConfigMapWithIncludeProfileSpecificSourcesTests.java +++ b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_config_map_with_profile/NamedConfigMapWithProfileBootstrapTests.java @@ -14,38 +14,30 @@ * limitations under the License. */ -package org.springframework.cloud.kubernetes.fabric8.config; +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.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.include_profile_specific_sources.IncludeProfileSpecificSourcesApp; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author wind57 */ -@ExtendWith(SpringExtension.class) +@ActiveProfiles("k8s") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = IncludeProfileSpecificSourcesApp.class, - properties = { "spring.application.name=include-profile-specific-sources", - "spring.main.cloud-platform=KUBERNETES", - "spring.config.import=kubernetes:,classpath:./include-profile-specific-sources.yaml" }) -@AutoConfigureWebTestClient + 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) -@ActiveProfiles("dev") -class ConfigDataConfigMapWithIncludeProfileSpecificSourcesTests - extends ConfigMapWithIncludeProfileSpecificSourcesTests { +class NamedConfigMapWithProfileBootstrapTests extends NamedConfigMapWithProfileTests { private static KubernetesClient mockClient; @BeforeAll - public static void setUpBeforeClass() { + 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 00000000..f4a49810 --- /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 00000000..ec19f47f --- /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 00000000..8f353d88 --- /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 00000000..2fbc67f0 --- /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 00000000..7a192e5e --- /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 00000000..7cf2943a --- /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/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 00000000..28c9f2aa --- /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 00000000..1c11e0da --- /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 00000000..f4cc3ba0 --- /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 00000000..9c0e8a21 --- /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/include_profile_specific_sources/controller/IncludeProfileSpecificSourcesController.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/named_secret_with_profile/controller/NamedSecretWithProfileController.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_secret_with_profile/controller/NamedSecretWithProfileController.java index e6aa453c..e241ab48 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_secret_with_profile/controller/NamedSecretWithProfileController.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_secret_with_profile.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_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 IncludeProfileSpecificSourcesController { +public class NamedSecretWithProfileController { private final One one; @@ -31,23 +31,23 @@ public class IncludeProfileSpecificSourcesController { private final Three three; - public IncludeProfileSpecificSourcesController(One one, Two two, Three three) { + public NamedSecretWithProfileController(One one, Two two, Three three) { this.one = one; this.two = two; this.three = three; } - @GetMapping("/profile-specific/one") + @GetMapping("/named-secret/profile/one") public String one() { return one.getProperty(); } - @GetMapping("/profile-specific/two") + @GetMapping("/named-secret/profile/two") public String two() { return two.getProperty(); } - @GetMapping("/profile-specific/three") + @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 00000000..9290f45b --- /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/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_profile/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_profile/properties/Three.java index 239f4374..5a8c79cd 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_profile/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_profile.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/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 00000000..3c94d154 --- /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/retry/BootstrapSecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/BootstrapSecretsFailFastEnabledButRetryDisabled.java index 387eef35..78d38dfa 100644 --- 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 @@ -34,7 +34,8 @@ import org.springframework.cloud.kubernetes.fabric8.config.Fabric8SecretsPropert 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.main.cloud-platform=KUBERNETES", "spring.cloud.bootstrap.enabled=true", + "spring.cloud.kubernetes.config.enabled=false" }, classes = Application.class) @EnableKubernetesMockClient class BootstrapSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEnabledButRetryDisabled { @@ -52,7 +53,7 @@ class BootstrapSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEna private Fabric8SecretsPropertySourceLocator propertySourceLocator; @BeforeEach - public void 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 index 8c11049d..45cf342f 100644 --- 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 @@ -56,7 +56,7 @@ class BootstrapSecretsRetryDisabledButConfigRetryEnabled extends SecretsRetryDis private Fabric8SecretsPropertySourceLocator propertySourceLocator; @BeforeEach - public void 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/ConfigDataSecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/ConfigDataSecretsFailFastEnabledButRetryDisabled.java index 9b4140a1..8b23556a 100644 --- 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 @@ -36,7 +36,8 @@ import static org.mockito.Mockito.spy; 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.main.cloud-platform=KUBERNETES", "spring.config.import=kubernetes:", + "spring.cloud.kubernetes.config.enabled=false" }, classes = Application.class) @EnableKubernetesMockClient class ConfigDataSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEnabledButRetryDisabled { @@ -54,7 +55,7 @@ class ConfigDataSecretsFailFastEnabledButRetryDisabled extends SecretsFailFastEn private Fabric8SecretsPropertySourceLocator propertySourceLocator; @BeforeEach - public void 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/SecretsFailFastEnabledButRetryDisabled.java b/spring-cloud-kubernetes-fabric8-config/src/test/java/org/springframework/cloud/kubernetes/fabric8/config/retry/SecretsFailFastEnabledButRetryDisabled.java index 3a3c63b1..4ed82085 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 @@ -38,8 +38,6 @@ import static org.mockito.Mockito.verify; */ abstract class SecretsFailFastEnabledButRetryDisabled { - private static final String API = "/api/v1/namespaces/default/secrets/my-secret"; - private static final String LIST_API = "/api/v1/namespaces/default/secrets"; private static KubernetesMockServer mockServer; @@ -69,12 +67,13 @@ abstract class SecretsFailFastEnabledButRetryDisabled { @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(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once 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 70ccd2d0..322785ac 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,6 +16,7 @@ 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; @@ -37,9 +38,9 @@ import static org.mockito.Mockito.verify; */ 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; @@ -60,7 +61,8 @@ abstract class SecretsRetryDisabledButConfigRetryEnabled { 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(); } @Autowired @@ -74,12 +76,12 @@ abstract class SecretsRetryDisabledButConfigRetryEnabled { * defined. SecretsPropertySourceLocator should not retry even Spring Retry is * enabled. */ - - mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").once(); + mockServer.clearExpectations(); + mockServer.expect().withPath(SECRET_API).andReturn(500, "Internal Server Error").once(); assertRetryBean(context); assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .hasMessageContaining("v1/namespaces/default/secrets. Message: Internal Server Error"); // verify that propertySourceLocator.locate is called only once verify(verifiablePsl, times(1)).locate(any()); 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 8e1cd9e9..26909120 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 @@ -43,7 +43,7 @@ import static org.mockito.Mockito.verify; */ 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"; @@ -63,7 +63,7 @@ abstract class SecretsRetryEnabled { 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(); } @Test @@ -73,9 +73,9 @@ abstract class SecretsRetryEnabled { 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(() -> psl.locate(new MockEnvironment())); @@ -95,9 +95,10 @@ abstract class SecretsRetryEnabled { // 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(() -> psl.locate(new MockEnvironment())); @@ -115,7 +116,7 @@ abstract class SecretsRetryEnabled { mockServer.expect().withPath(API).andReturn(500, "Internal Server Error").times(5); assertThatThrownBy(() -> psl.locate(new MockEnvironment())).isInstanceOf(IllegalStateException.class) - .hasMessage("Unable to read Secret with name 'my-secret' in namespace 'default'"); + .hasMessageContaining("api/v1/namespaces/default/secrets. Message: Internal Server Error."); // verify retried 5 times until failure verify(verifiablePsl, times(5)).locate(any()); 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 62735073..bd4cea9d 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/labeled-configmap-with-prefix.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-configmap-with-prefix.yaml new file mode 100644 index 00000000..40bea776 --- /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 00000000..8aa88d2d --- /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-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/labeled-secret-with-profile.yaml new file mode 100644 index 00000000..89255f08 --- /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/named-configmap-with-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-configmap-with-profile.yaml new file mode 100644 index 00000000..da496510 --- /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-profile.yaml b/spring-cloud-kubernetes-fabric8-config/src/test/resources/named-secret-with-profile.yaml new file mode 100644 index 00000000..8dc8c8b2 --- /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