diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index b554d57c771f82258d30f20cd129eca4f55eb66f..54775001fee442756defcbb005b29ebc6102c263 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -111,6 +111,18 @@ spec: value: {{ .Values.zufiManager.address }} - name: grpc_client_zufi-manager_negotiationType value: {{ (.Values.zufiManager).grpcClientNegotiationType | default "TLS" }} + {{- if ((.Values.ozgcloud).feature).postfach }} + - name: ozgcloud_feature_postfach + value: {{ .Values.ozgcloud.feature.postfach | quote }} + {{- end }} + {{- if ((.Values.ozgcloud).feature).benutzerRollen }} + - name: ozgcloud_feature_benutzerRollen + value: {{ .Values.ozgcloud.feature.benutzerRollen | quote }} + {{- end }} + {{- if ((.Values.ozgcloud).feature).organisationsEinheiten }} + - name: ozgcloud_feature_organisationsEinheiten + value: {{ .Values.ozgcloud.feature.organisationsEinheiten | quote }} + {{- end }} envFrom: {{- if (.Values.database).useExternal }} - secretRef: diff --git a/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java b/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..75ab7ba16c52807514442a6a48be9e2d80c54086 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/common/FeatureToggleProperties.java @@ -0,0 +1,18 @@ +package de.ozgcloud.admin.common; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Configuration +@ConfigurationProperties(prefix = "ozgcloud.feature") +public class FeatureToggleProperties { + + private boolean postfach; + private boolean benutzerRollen; + private boolean organisationsEinheiten; +} diff --git a/src/main/java/de/ozgcloud/admin/environment/Environment.java b/src/main/java/de/ozgcloud/admin/environment/Environment.java index 0ee031564ac123327cd663d982e98a2e2ec1ea87..79c6775f9a86126394ff10142cf70babcebd14af 100644 --- a/src/main/java/de/ozgcloud/admin/environment/Environment.java +++ b/src/main/java/de/ozgcloud/admin/environment/Environment.java @@ -34,4 +34,5 @@ public class Environment { private String authServer; private String clientId; private String realm; + private Features features; } diff --git a/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java b/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java index cd1c0417bdf97f51ba49b41f1ec23edadd4d4560..ea3d58e1ee4a5fb027b610a407121a2d70a650e0 100644 --- a/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java +++ b/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java @@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import de.ozgcloud.admin.RootController; +import de.ozgcloud.admin.common.FeatureToggleProperties; import lombok.RequiredArgsConstructor; @RestController("ozgCloudEnvironmentController") @@ -43,6 +44,9 @@ public class EnvironmentController { private final OAuth2Properties oAuthProperties; + private final FeatureToggleProperties featureToggleProperties; + private final FeaturesMapper featuresMapper; + @GetMapping public Environment getEnvironment() { return Environment.builder() @@ -51,6 +55,7 @@ public class EnvironmentController { .authServer(oAuthProperties.getAuthServerUrl()) .realm(oAuthProperties.getRealm()) .clientId(oAuthProperties.getResource()) + .features(featuresMapper.fromFeatureToggleProperties(featureToggleProperties)) .build(); } } diff --git a/src/main/java/de/ozgcloud/admin/environment/Features.java b/src/main/java/de/ozgcloud/admin/environment/Features.java new file mode 100644 index 0000000000000000000000000000000000000000..1f63a6e88be1cd2415b720916e60730bc3b3a41e --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/environment/Features.java @@ -0,0 +1,12 @@ +package de.ozgcloud.admin.environment; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class Features { + + private final boolean postfach; + private final boolean benutzerRollen; +} diff --git a/src/main/java/de/ozgcloud/admin/environment/FeaturesMapper.java b/src/main/java/de/ozgcloud/admin/environment/FeaturesMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..2a7e9d4c3b0614824eb0d35fb4dd004e876327a0 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/environment/FeaturesMapper.java @@ -0,0 +1,12 @@ +package de.ozgcloud.admin.environment; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; + +import de.ozgcloud.admin.common.FeatureToggleProperties; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +interface FeaturesMapper { + + Features fromFeatureToggleProperties(FeatureToggleProperties featureToggleProperties); +} diff --git a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessor.java b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessor.java index 41dd7c0d08898f2b8e4a4036627521d030b198bb..1cb98fd649ac08564bb8f9da24e5419c0e930016 100644 --- a/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessor.java +++ b/src/main/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessor.java @@ -30,14 +30,20 @@ import org.springframework.hateoas.server.RepresentationModelProcessor; import org.springframework.stereotype.Component; import de.ozgcloud.admin.Root; +import de.ozgcloud.admin.common.FeatureToggleProperties; +import lombok.RequiredArgsConstructor; @Component -class OrganisationsEinheitRootProcessor implements RepresentationModelProcessor<EntityModel<Root>> { +@RequiredArgsConstructor +class OrganisationsEinheitRootProcessor implements RepresentationModelProcessor<EntityModel<Root>> { static final String REL_ORGANISATIONS_EINHEITEN = "organisationsEinheiten"; + private final FeatureToggleProperties featureToggleProperties; + @Override public EntityModel<Root> process(EntityModel<Root> model) { - return model.add(linkTo(methodOn(OrganisationsEinheitController.class).getAll()).withRel(REL_ORGANISATIONS_EINHEITEN)); + return model.addIf(featureToggleProperties.isOrganisationsEinheiten(), + () -> linkTo(methodOn(OrganisationsEinheitController.class).getAll()).withRel(REL_ORGANISATIONS_EINHEITEN)); } } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 691b5933ad9270b73fe54d3f0779c5b692e49389..43f589362f0a68ab411c97d57e27b0ca9d8546a1 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -3,4 +3,8 @@ ozgcloud: administration: sync: organisationseinheiten: - cron: "* */5 * * * *" \ No newline at end of file + cron: "* */5 * * * *" + feature: + postfach: true + benutzerRollen: true + organisationsEinheiten: true \ No newline at end of file diff --git a/src/main/resources/application-e2e.yaml b/src/main/resources/application-e2e.yaml index 864d289ed86348cab1d4997844729b83f6a793ba..1ca78e907b35e39a905f46a0ed082b64be7dd6b2 100644 --- a/src/main/resources/application-e2e.yaml +++ b/src/main/resources/application-e2e.yaml @@ -8,4 +8,10 @@ server: management: server: - port: 8081 \ No newline at end of file + port: 8081 + +ozgcloud: + feature: + postfach: true + benutzer-rollen: true + organisations-einheiten: true \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index b39daf33653aa0db748c56b8a2353f4614a84dde..99608d21a21751a91617a7f5f1d43cac1db83adc 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -17,6 +17,10 @@ ozgcloud: api: user: administrationApiUser password: administrationApiUser + feature: + postfach: true + benutzer-rollen: true + organisations-einheiten: true grpc: client: diff --git a/src/test/helm/deployment_features_test.yaml b/src/test/helm/deployment_features_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b0f29d2d6c6ed3f1e0cf54027dc940e6c43619d7 --- /dev/null +++ b/src/test/helm/deployment_features_test.yaml @@ -0,0 +1,78 @@ +# +# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den +# Ministerpräsidenten des Landes Schleswig-Holstein +# Staatskanzlei +# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung +# +# Lizenziert unter der EUPL, Version 1.2 oder - sobald +# diese von der Europäischen Kommission genehmigt wurden - +# Folgeversionen der EUPL ("Lizenz"); +# Sie dürfen dieses Werk ausschließlich gemäß +# dieser Lizenz nutzen. +# Eine Kopie der Lizenz finden Sie hier: +# +# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 +# +# Sofern nicht durch anwendbare Rechtsvorschriften +# gefordert oder in schriftlicher Form vereinbart, wird +# die unter der Lizenz verbreitete Software "so wie sie +# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - +# ausdrücklich oder stillschweigend - verbreitet. +# Die sprachspezifischen Genehmigungen und Beschränkungen +# unter der Lizenz sind dem Lizenztext zu entnehmen. +# + +suite: test feature properties +release: + name: administration + namespace: sh-helm-test +templates: + - templates/deployment.yaml +set: + ozgcloud: + bundesland: sh + bezeichner: helm + sso: + serverUrl: https://sso.company.local + imagePullSecret: image-pull-secret +tests: + - it: should not contain feature properties if values are missing + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_postfach + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_benutzerRollen + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_organisationsEinheiten + any: true + - it: should set (optional) feature properties + set: + ozgcloud: + feature: + postfach: true + benutzerRollen: true + organisationsEinheiten: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_postfach + value: "true" + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_benutzerRollen + value: "true" + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_organisationsEinheiten + value: "true" \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java b/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java index 92c63c3900e746fbac7be80a46206c593dcbb4c4..f9337f4197236106c70df9639e0620b08f4157a7 100644 --- a/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java +++ b/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java @@ -40,6 +40,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import de.ozgcloud.admin.RootController; +import de.ozgcloud.admin.common.FeatureToggleProperties; import lombok.SneakyThrows; class EnvironmentControllerTest { @@ -52,6 +53,10 @@ class EnvironmentControllerTest { private ProductionProperties environmentProperties; @Mock private OAuth2Properties oAuth2Properties; + @Mock + private FeatureToggleProperties featureToggleProperties; + @Mock + private FeaturesMapper featuresMapper; private MockMvc mockMvc; @@ -72,29 +77,15 @@ class EnvironmentControllerTest { response.andExpect(status().isOk()); } - @Nested - class TestBody { - - @Test - @SneakyThrows - void shouldCallIsProduction() { - when(environmentProperties.isProduction()).thenReturn(true); - - doRequest(); - - verify(environmentProperties).isProduction(); - } - - @ParameterizedTest - @ValueSource(booleans = { true, false }) - @SneakyThrows - void shouldContainProduction(boolean production) { - when(environmentProperties.isProduction()).thenReturn(production); + @ParameterizedTest + @ValueSource(booleans = { true, false }) + @SneakyThrows + void shouldContainProduction(boolean production) { + when(environmentProperties.isProduction()).thenReturn(production); - var response = doRequest(); + var response = doRequest(); - response.andExpect(jsonPath("$.production").value(production)); - } + response.andExpect(jsonPath("$.production").value(production)); } @Test @@ -138,6 +129,35 @@ class EnvironmentControllerTest { response.andExpect(jsonPath("$.clientId").value(clientId)); } + @Test + void shouldCallFeaturesMapper() { + doRequest(); + + verify(featuresMapper).fromFeatureToggleProperties(featureToggleProperties); + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldContainPostfach(boolean postfach) { + when(featuresMapper.fromFeatureToggleProperties(featureToggleProperties)).thenReturn(Features.builder().postfach(postfach).build()); + + var response = doRequest(); + + response.andExpect(jsonPath("$.features.postfach").value(postfach)); + } + + @SneakyThrows + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldContainBenutzerRollen(boolean benutzerRollen) { + when(featuresMapper.fromFeatureToggleProperties(featureToggleProperties)).thenReturn(Features.builder().benutzerRollen(benutzerRollen).build()); + + var response = doRequest(); + + response.andExpect(jsonPath("$.features.benutzerRollen").value(benutzerRollen)); + } + @SneakyThrows private ResultActions doRequest() { return mockMvc.perform(get(EnvironmentController.PATH)); diff --git a/src/test/java/de/ozgcloud/admin/environment/FeaturesMapperTest.java b/src/test/java/de/ozgcloud/admin/environment/FeaturesMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..30742c8a4a0cf0e14822d100f2f7e4e4faff7ddf --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/environment/FeaturesMapperTest.java @@ -0,0 +1,41 @@ +package de.ozgcloud.admin.environment; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mapstruct.factory.Mappers; + +import de.ozgcloud.admin.common.FeatureToggleProperties; + +class FeaturesMapperTest { + + private FeaturesMapper mapper = Mappers.getMapper(FeaturesMapper.class); + + @Nested + class TestFromFeatureToggleProperties { + + private final FeatureToggleProperties props = new FeatureToggleProperties(); + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldMapPostfach(boolean postfach) { + props.setPostfach(postfach); + + var features = mapper.fromFeatureToggleProperties(props); + + assertThat(features.isPostfach()).isEqualTo(postfach); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void shouldMapBenutzerRollen(boolean benutzerRollen) { + props.setBenutzerRollen(benutzerRollen); + + var features = mapper.fromFeatureToggleProperties(props); + + assertThat(features.isBenutzerRollen()).isEqualTo(benutzerRollen); + } + } +} diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessorTest.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessorTest.java index 6fbea1e4216267ce9ded626a10926f013d10339c..f147d68e37d851e374f7376a2e84c34374c1f271 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessorTest.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitRootProcessorTest.java @@ -24,19 +24,24 @@ package de.ozgcloud.admin.organisationseinheit; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import de.ozgcloud.admin.RootTestFactory; +import de.ozgcloud.admin.common.FeatureToggleProperties; class OrganisationsEinheitRootProcessorTest { @InjectMocks private OrganisationsEinheitRootProcessor organisationsEinheitRootProcessor; + @Mock + private FeatureToggleProperties featureToggleProperties; @Nested class TestProcess { @@ -45,14 +50,27 @@ class OrganisationsEinheitRootProcessorTest { class OrganisationsEinheitenLinkRelation { @Test - void shouldExists() { + void shouldExistsIfFeatureEnabled() { + givenFeatureIsEnabled(); + var model = organisationsEinheitRootProcessor.process(EntityModel.of(RootTestFactory.create())); assertThat(model.getLink(OrganisationsEinheitRootProcessor.REL_ORGANISATIONS_EINHEITEN)).isNotEmpty(); } + @Test + void shouldNotExistIfFeatureDisabled() { + givenFeatureIsDisabled(); + + var model = organisationsEinheitRootProcessor.process(EntityModel.of(RootTestFactory.create())); + + assertThat(model.getLink(OrganisationsEinheitRootProcessor.REL_ORGANISATIONS_EINHEITEN)).isEmpty(); + } + @Test void shouldHaveHref() { + givenFeatureIsEnabled(); + var model = organisationsEinheitRootProcessor.process(EntityModel.of(RootTestFactory.create())); assertThat(model.getLink(OrganisationsEinheitRootProcessor.REL_ORGANISATIONS_EINHEITEN)) @@ -60,6 +78,14 @@ class OrganisationsEinheitRootProcessorTest { .extracting(Link::getHref) .isEqualTo(OrganisationsEinheitController.PATH); } + + private void givenFeatureIsEnabled() { + when(featureToggleProperties.isOrganisationsEinheiten()).thenReturn(true); + } + + private void givenFeatureIsDisabled() { + when(featureToggleProperties.isOrganisationsEinheiten()).thenReturn(false); + } } }