From d6bdaf4ffa6e37415a26bddb42749ae2034534e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Mon, 18 May 2026 13:24:09 +0200 Subject: [PATCH 1/4] Add default wrapper workflow params on custom deletion --- .../openapi-component_provisioner-v1.0.0.yaml | 2 + .../facade/ProvisionResultsApiFacade.java | 111 +++++++++++++----- .../server/services/ProvisionService.java | 40 ++++++- .../resources/application-local.env.template | 1 + src/main/resources/application.yml | 5 +- .../facade/ProvisionResultsApiFacadeTest.java | 24 ++-- .../server/services/ProvisionServiceTest.java | 4 +- 7 files changed, 136 insertions(+), 51 deletions(-) diff --git a/openapi/openapi-component_provisioner-v1.0.0.yaml b/openapi/openapi-component_provisioner-v1.0.0.yaml index 0f6843a..e7b4780 100644 --- a/openapi/openapi-component_provisioner-v1.0.0.yaml +++ b/openapi/openapi-component_provisioner-v1.0.0.yaml @@ -446,6 +446,8 @@ components: enum: - success - error + - deletion_success + - deletion_error example: error RestErrorMessage: properties: diff --git a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java index d95c4c9..7ae4c06 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java @@ -37,8 +37,11 @@ public class ProvisionResultsApiFacade { private final ApplicationAuthenticationProvider applicationAuthenticationProvider; - @Value("${component-provisioner.support.create-incident-workflow-id:WORKFLOW}") - private String workflowId; + @Value("${component-provisioner.awx.workflows.create-incident-workflow-id:WORKFLOW}") + private String createIncidentWorkflowId; + + @Value("${component-provisioner.awx.workflows.deletion-wrapper-workflow-id:WORKFLOW}") + private String deletionWrapperWorkflowId; public ProvisionResultsApiFacade(AwxService awxService, ComponentCatalogService componentCatalogService, @@ -63,8 +66,10 @@ public AwxResponse requestDeletion( log.debug("Processing deletion. ProjectKey: {}, componentId: {}", projectKey, componentId); - String deletionWorkflow = getDeletionWorkflow(projectKey, componentId); - validate(projectKey, componentId, deletionWorkflow, createIncidentAction); + String deletionWorkflowId = getDeletionWorkflowId(projectKey, componentId); + String deletionWorkflowName = getDeletionWorkflowName(projectKey, componentId); + String deletionWorkflowTimeoutSeconds = getDeletionWorkflowTimeoutSeconds(projectKey, componentId); + validate(projectKey, componentId, deletionWorkflowId, deletionWorkflowName, createIncidentAction); addSystemParametersToAction(projectKey, createIncidentAction); var accessToken = authenticationProvider.getAccessToken(); @@ -83,7 +88,12 @@ public AwxResponse requestDeletion( var catalogItemId = provisionService.composeCatalogItemId(projectComponent); setDeletingState(projectKey, componentId, catalogItemId); - AwxResponse awxResponse = triggerDeletion(projectKey, componentId, deletionWorkflow, createIncidentAction); + var triggerDeletionWrapperWorkflow = Strings.isNotBlank(deletionWorkflowId) || Strings.isNotBlank(deletionWorkflowName); + if (triggerDeletionWrapperWorkflow) { + addDeletionWrapperWorkflowParameters(catalogItemId, deletionWorkflowId, deletionWorkflowName, deletionWorkflowTimeoutSeconds, createIncidentAction); + } + + AwxResponse awxResponse = triggerDeletion(projectKey, componentId, triggerDeletionWrapperWorkflow, createIncidentAction); log.debug("AWX response: {}", awxResponse); return awxResponse; @@ -94,7 +104,7 @@ public boolean isInDeletingState(ProjectComponentExtendedInfo projectComponent) return ProjectComponentStatus.DELETING.name().equals(projectComponent.getStatus()); } - public AwxResponse triggerAwxWorkflow(String projectKey, String componentId, CreateIncidentAction createIncidentAction) { + public AwxResponse triggerAwxIncidentWorkflow(String projectKey, String componentId, CreateIncidentAction createIncidentAction) { var workflowJobLaunch = buildAwxWorkflowJobLaunch(projectKey, componentId, createIncidentAction); var result = awxService.triggerWorkflowJob(ActionType.CREATE_INCIDENT.getValue(), workflowJobLaunch); @@ -110,8 +120,8 @@ public AwxResponse triggerAwxWorkflow(String projectKey, String componentId, Cre .build(); } - public AwxResponse triggerAwxDeletionWorkflow(String projectKey, String componentId, String deletionWorkflow, CreateIncidentAction createIncidentAction) { - var workflowJobLaunch = buildAwxDeletionWorkflowJobLaunch(projectKey, componentId, deletionWorkflow, createIncidentAction); + public AwxResponse triggerAwxDeletionWorkflow(String projectKey, String componentId, CreateIncidentAction createIncidentAction) { + var workflowJobLaunch = buildAwxDeletionWorkflowJobLaunch(projectKey, componentId, createIncidentAction); var result = awxService.triggerWorkflowJob(ActionType.DELETE.getValue(), workflowJobLaunch); @@ -177,11 +187,9 @@ private String resolveCatalogItemId(String accessToken, } public void deleteProvisioningStatus(String projectKey, String componentId) { - provisionService.deleteProvisioningStatus(projectKey, componentId); } - public void validate(String projectKey, String status, String catalogItemId, String catalogItemSlug) { validate(projectKey, status); @@ -207,21 +215,21 @@ public void validate(String projectKey, String status) { } } - public void validate(String projectKey, String componentId, String deletionWorkflow, CreateIncidentAction createIncidentAction) { + public void validate(String projectKey, String componentId, String deletionWorkflowId, String deletionWorkflowName, CreateIncidentAction createIncidentAction) { var isDeployed = getParameterString(createIncidentAction, "is_deployed"); var changeNumber = getParameterString(createIncidentAction, "change_number"); var reason = getParameterString(createIncidentAction, "reason"); var mainParamsAreEmpty = StringUtils.isBlank(projectKey) || StringUtils.isBlank(componentId); - var extraParamsAreEmtpy = StringUtils.isBlank(isDeployed) + var extraParamsAreEmpty = StringUtils.isBlank(isDeployed) || StringUtils.isBlank(changeNumber) || StringUtils.isBlank(reason); if (mainParamsAreEmpty) { throw new InvalidRestEntityException("project_key, component_id are required."); } - if (StringUtils.isBlank(deletionWorkflow) && extraParamsAreEmtpy) { - throw new InvalidRestEntityException("Without deletion_workflow, params is_deployed, change_number and reason are required."); + if (StringUtils.isBlank(deletionWorkflowId) && StringUtils.isBlank(deletionWorkflowName) && extraParamsAreEmpty) { + throw new InvalidRestEntityException("The component has no deletion_workflow nor deletion_workflow_name configured, so params is_deployed, change_number and reason are required in the request."); } } @@ -253,6 +261,49 @@ private void addSendOnDeletionParameters(String projectKey, String componentId, .forEach(action::addParametersItem); } + private void addDeletionWrapperWorkflowParameters(String catalogItemId, + String customDeletionWorkflowId, + String customDeletionWorkflowName, + String deletionWorkflowTimeoutSeconds, + CreateIncidentAction action) { + action.addParametersItem(CreateIncidentParameter.builder() + .name("access_token") + .value(authenticationProvider.getAccessToken()) + .type(ParameterType.STRING.getValue()) + .build() + ); + action.addParametersItem(CreateIncidentParameter.builder() + .name("catalog_item_id") + .value(catalogItemId) + .type(ParameterType.STRING.getValue()) + .build() + ); + if (Strings.isNotBlank(customDeletionWorkflowId)) { + action.addParametersItem(CreateIncidentParameter.builder() + .name("deletion_workflow_id") + .value(customDeletionWorkflowId) + .type(ParameterType.STRING.getValue()) + .build() + ); + } + if (Strings.isNotBlank(customDeletionWorkflowName)) { + action.addParametersItem(CreateIncidentParameter.builder() + .name("deletion_workflow_name") + .value(customDeletionWorkflowName) + .type(ParameterType.STRING.getValue()) + .build() + ); + } + if (Strings.isNotBlank(deletionWorkflowTimeoutSeconds)) { + action.addParametersItem(CreateIncidentParameter.builder() + .name("deletion_workflow_timeout_seconds") + .value(deletionWorkflowTimeoutSeconds) + .type(ParameterType.STRING.getValue()) + .build() + ); + } + } + private void addCallerParameter(CreateIncidentAction action) { var caller = authenticationProvider.getUserPrincipalName(); action.addParametersItem(CreateIncidentParameter.builder() @@ -275,23 +326,30 @@ public String getParameterString(CreateIncidentAction createIncidentAction, Stri .orElse(Strings.EMPTY); } - public String getDeletionWorkflow(String projectKey, String componentId) { - return provisionService.getDeletionWorkflow(projectKey, componentId); + public String getDeletionWorkflowId(String projectKey, String componentId) { + return provisionService.getDeletionWorkflowId(projectKey, componentId); + } + + public String getDeletionWorkflowName(String projectKey, String componentId) { + return provisionService.getDeletionWorkflowName(projectKey, componentId); + } + + public String getDeletionWorkflowTimeoutSeconds(String projectKey, String componentId) { + return provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId); } private AwxWorkflowJobLaunch buildAwxWorkflowJobLaunch(String projectKey, String componentId, CreateIncidentAction createIncidentAction) { - addDefaultParameters(projectKey, componentId, workflowId, createIncidentAction); + addDefaultParameters(projectKey, componentId, createIncidentWorkflowId, createIncidentAction); return entitiesMapper.asAwxWorkflowJobLaunch(createIncidentAction); } private AwxWorkflowJobLaunch buildAwxDeletionWorkflowJobLaunch(String projectKey, String componentId, - String deletionWorkflow, CreateIncidentAction createIncidentAction) { - addDefaultParameters(projectKey, componentId, deletionWorkflow, createIncidentAction); + addDefaultParameters(projectKey, componentId, deletionWrapperWorkflowId, createIncidentAction); addSendOnDeletionParameters(projectKey, componentId, createIncidentAction); return entitiesMapper.asAwxWorkflowJobLaunch(createIncidentAction); @@ -339,20 +397,15 @@ private void setDeletingState(String projectKey, String componentId, String cata private AwxResponse triggerDeletion( String projectKey, String componentId, - String deletionWorkflow, + boolean triggerWrapperDeletionWorkflow, CreateIncidentAction action) { - if (StringUtils.isBlank(deletionWorkflow)) { + if (!triggerWrapperDeletionWorkflow) { log.debug("Workflow not found for deletion. Creating incident via AWX"); - return triggerAwxWorkflow(projectKey, componentId, action); + return triggerAwxIncidentWorkflow(projectKey, componentId, action); } - log.debug("Workflow found for deletion. Triggering deletion workflow"); - return triggerAwxDeletionWorkflow( - projectKey, - componentId, - deletionWorkflow, - action - ); + log.debug("Workflow found for deletion. Triggering wrapper for custom deletion workflow"); + return triggerAwxDeletionWorkflow(projectKey, componentId, action); } } diff --git a/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java b/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java index e3c6afe..bce6c6d 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java @@ -28,7 +28,9 @@ @Slf4j public class ProvisionService { - private static final String DELETION_WORKFLOW = "deletion_workflow"; + private static final String DELETION_WORKFLOW_ID = "deletion_workflow"; + private static final String DELETION_WORKFLOW_NAME = "deletion_workflow_name"; + private static final String DELETION_WORKFLOW_TIMEOUT = "deletion_workflow_timeout_seconds"; private final ApiClientsBuilder apiClientsBuilder; private final ComponentCatalogService componentCatalogService; @@ -97,14 +99,40 @@ public List getDeletionParameters(String projectKey, St return extractDeletionParameters(catalogItem, projectComponent, ActionType.PROVISION.getValue()); } - public String getDeletionWorkflow(String projectKey, String componentId) { + public String getDeletionWorkflowId(String projectKey, String componentId) { var projectComponent = componentCatalogService.getProjectComponentById(authenticationProvider.getAccessToken(), projectKey, componentId); var parameterMap = getProjectComponentParameterMap(projectComponent); - if (parameterMap.containsKey(DELETION_WORKFLOW)) { - var deletionWorkflow = parameterMap.get(DELETION_WORKFLOW); - assert deletionWorkflow.getValues() != null; - return deletionWorkflow.getValues().getFirst(); + if (parameterMap.containsKey(DELETION_WORKFLOW_ID)) { + var deletionWorkflowId = parameterMap.get(DELETION_WORKFLOW_ID); + assert deletionWorkflowId.getValues() != null; + return deletionWorkflowId.getValues().getFirst(); + } + + return ""; + } + + public String getDeletionWorkflowName(String projectKey, String componentId) { + var projectComponent = componentCatalogService.getProjectComponentById(authenticationProvider.getAccessToken(), projectKey, componentId); + var parameterMap = getProjectComponentParameterMap(projectComponent); + + if (parameterMap.containsKey(DELETION_WORKFLOW_NAME)) { + var deletionWorkflowName = parameterMap.get(DELETION_WORKFLOW_NAME); + assert deletionWorkflowName.getValues() != null; + return deletionWorkflowName.getValues().getFirst(); + } + + return ""; + } + + public String getDeletionWorkflowTimeoutSeconds(String projectKey, String componentId) { + var projectComponent = componentCatalogService.getProjectComponentById(authenticationProvider.getAccessToken(), projectKey, componentId); + var parameterMap = getProjectComponentParameterMap(projectComponent); + + if (parameterMap.containsKey(DELETION_WORKFLOW_TIMEOUT)) { + var deletionWorkflowName = parameterMap.get(DELETION_WORKFLOW_TIMEOUT); + assert deletionWorkflowName.getValues() != null; + return deletionWorkflowName.getValues().getFirst(); } return ""; diff --git a/src/main/resources/application-local.env.template b/src/main/resources/application-local.env.template index 737d8a9..3bb25b9 100644 --- a/src/main/resources/application-local.env.template +++ b/src/main/resources/application-local.env.template @@ -25,6 +25,7 @@ COMPONENT_CATALOG_PROVISIONER_PASSWORD= SENSITIVE_PARAMETERS_BLACKLIST=access_token,password,secret,token CREATE_INCIDENT_WORKFLOW_ID=3361 +DELETION_WRAPPER_WORKFLOW_ID=3823 PROJECTS_INFO_SERVICE_BASE_REST_URL="https://projects-info-service-devstack-dev.apps.example.com/v1" CACHING_PROJECTS_INFO_SERVICE_CACHE_EVICTION_INTERVAL=5s diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1371de4..5d2e3ee 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -68,6 +68,9 @@ component-provisioner: base-rest-url: ${AWX_SERVICE_BASE_REST_URL} username: ${AWX_SERVICE_USERNAME} password: ${AWX_SERVICE_PASSWORD} + workflows: + create-incident-workflow-id: ${CREATE_INCIDENT_WORKFLOW_ID} + deletion-wrapper-workflow-id: ${DELETION_WRAPPER_WORKFLOW_ID} component-catalog: service: base-rest-url: ${COMPONENT_CATALOG_SERVICE_BASE_REST_URL} @@ -83,8 +86,6 @@ component-provisioner: client_secret: ${DEVSTACK_MARKETPLACE_API_CLIENT_SECRET} scope: ${ODS_API_SERVICE_SERVICE_PARAMS_SCOPE} override: ${ODS_API_SERVICE_SERVICE_PARAMS_OVERRIDE} - support: - create-incident-workflow-id: ${CREATE_INCIDENT_WORKFLOW_ID} parameters: blacklist: ${SENSITIVE_PARAMETERS_BLACKLIST} diff --git a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java index 1c17dd5..fa3794f 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java @@ -70,7 +70,7 @@ void setUp() { } @Test - void givenAProjectKeyAndAComponentId_whenTriggerAwxWorkflowIsCalled_thenMapsResponseCorrectly() { + void givenAProjectKeyAndAComponentId_whenTriggerAwxIncidentWorkflowIsCalled_thenMapsResponseCorrectly() { // given var action = CreateIncidentActionMother.of(); var launch = new AwxWorkflowJobLaunch(); @@ -82,7 +82,7 @@ void givenAProjectKeyAndAComponentId_whenTriggerAwxWorkflowIsCalled_thenMapsResp when(entitiesMapper.asProvisionActionResponse(any())).thenReturn(response); // when - var result = facade.triggerAwxWorkflow("PRJ", "CID", action); + var result = facade.triggerAwxIncidentWorkflow("PRJ", "CID", action); // then assertNotNull(result, "Result should not be null"); @@ -171,8 +171,8 @@ void givenAMissingMainParams_whenValidateIsCalled_thenThrowsInvalidRestEntityExc var action = CreateIncidentActionMother.of(); // when / then - assertThrows(InvalidRestEntityException.class, () -> facade.validate(null, "CID", "", action)); - assertThrows(InvalidRestEntityException.class, () -> facade.validate("PRJ", null, "", action)); + assertThrows(InvalidRestEntityException.class, () -> facade.validate(null, "CID", "", "", action)); + assertThrows(InvalidRestEntityException.class, () -> facade.validate("PRJ", null, "", "", action)); } @Test @@ -227,12 +227,12 @@ void givenAMissingExtraParams_whenValidateIsCalled_thenThrowsInvalidRestEntityEx var action = CreateIncidentAction.builder().parameters(new ArrayList<>()).build(); // when / then - var ex = assertThrows(InvalidRestEntityException.class, () -> facade.validate("PRJ", "CID", "", action)); + var ex = assertThrows(InvalidRestEntityException.class, () -> facade.validate("PRJ", "CID", "", "", action)); assertThat(ex.getMessage()).contains("is_deployed, change_number and reason are required"); } @Test - void givenAnEmptyAwxResponse_whenTriggerAwxWorkflowIsCalled_thenReturnsNullBody() { + void givenAnEmptyAwxResponse_whenTriggerAwxIncidentWorkflowIsCalled_thenReturnsNullBody() { // given var action = CreateIncidentActionMother.of(); var launch = new AwxWorkflowJobLaunch(); @@ -241,7 +241,7 @@ void givenAnEmptyAwxResponse_whenTriggerAwxWorkflowIsCalled_thenReturnsNullBody( when(awxService.triggerWorkflowJob(any(), any())).thenReturn(Pair.of(HttpStatus.ACCEPTED, Optional.empty())); // when - var result = facade.triggerAwxWorkflow("PRJ", "CID", action); + var result = facade.triggerAwxIncidentWorkflow("PRJ", "CID", action); // then assertNotNull(result, "Result should not be null"); @@ -459,7 +459,7 @@ void givenAProjectKeyAndComponentIdAndAction_whenValidateIsCalled_thenDoesNotThr var action = CreateIncidentActionMother.of(); // when / then - assertDoesNotThrow(() -> facade.validate("PRJ", "CID", "", action)); + assertDoesNotThrow(() -> facade.validate("PRJ", "CID", "", "", action)); } @Test @@ -564,7 +564,7 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndComponentIsAl when(authenticationProvider.getAccessToken()).thenReturn("token"); when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); - when(provisionService.getDeletionWorkflow(projectKey, componentId)).thenReturn(""); + when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(""); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); when(projectsInfoService.getProjectClusters(any(), any())).thenReturn(projectInfo); @@ -593,7 +593,7 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndWorkflowNotFo when(authenticationProvider.getAccessToken()).thenReturn("token"); when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); - when(provisionService.getDeletionWorkflow(projectKey, componentId)).thenReturn(""); + when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(""); when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); @@ -628,7 +628,7 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndWorkflowFound when(authenticationProvider.getAccessToken()).thenReturn("token"); when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); - when(provisionService.getDeletionWorkflow(projectKey, componentId)).thenReturn(deletionWorkflow); + when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(deletionWorkflow); when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); @@ -703,7 +703,7 @@ void givenAwxTriggerFails_whenRequestDeletionIsCalled_thenReturnsErrorStatus() { when(authenticationProvider.getAccessToken()).thenReturn("token"); when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); - when(provisionService.getDeletionWorkflow(projectKey, componentId)).thenReturn(""); + when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(""); when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); diff --git a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java index e87cc3c..f9c6ac5 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java @@ -353,7 +353,7 @@ void givenProjectComponentWithDeletionWorkflow_whenGetDeletionWorkflowIsCalled_t when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)).thenReturn(projectComponent); // when - var result = provisionService.getDeletionWorkflow(projectKey, componentId); + var result = provisionService.getDeletionWorkflowId(projectKey, componentId); // then assertThat(result).isEqualTo("WF_NAME"); @@ -373,7 +373,7 @@ void givenProjectComponentWithoutDeletionWorkflow_whenGetDeletionWorkflowIsCalle when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)).thenReturn(projectComponent); // when - var result = provisionService.getDeletionWorkflow(projectKey, componentId); + var result = provisionService.getDeletionWorkflowId(projectKey, componentId); // then assertThat(result).isEmpty(); From 5448a6495b216b07255d1f68cdce46aa9c8986e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Mon, 18 May 2026 14:01:40 +0200 Subject: [PATCH 2/4] Fix tests and add new cases --- .../facade/ProvisionResultsApiFacadeTest.java | 67 ++++++++++++++++++- src/test/resources/application-testing.env | 1 + 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java index fa3794f..8b3988e 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacadeTest.java @@ -66,7 +66,8 @@ class ProvisionResultsApiFacadeTest { @org.junit.jupiter.api.BeforeEach void setUp() { String workflowJobId = "WORKFLOW_123"; - ReflectionTestUtils.setField(facade, "workflowId", workflowJobId); + ReflectionTestUtils.setField(facade, "createIncidentWorkflowId", workflowJobId); + ReflectionTestUtils.setField(facade, "deletionWrapperWorkflowId", workflowJobId); } @Test @@ -228,7 +229,7 @@ void givenAMissingExtraParams_whenValidateIsCalled_thenThrowsInvalidRestEntityEx // when / then var ex = assertThrows(InvalidRestEntityException.class, () -> facade.validate("PRJ", "CID", "", "", action)); - assertThat(ex.getMessage()).contains("is_deployed, change_number and reason are required"); + assertThat(ex.getMessage()).contains("is_deployed, change_number and reason"); } @Test @@ -465,7 +466,6 @@ void givenAProjectKeyAndComponentIdAndAction_whenValidateIsCalled_thenDoesNotThr @Test void givenAProjectKeyAndAComponentId_whenBuildAwxWorkflowJobLaunchIsCalled_thenAddsRequiredParameters() { // given - ReflectionTestUtils.setField(facade, "workflowId", "WORKFLOW_123"); var action = CreateIncidentActionMother.of(); action.setParameters(new ArrayList<>()); var projectKey = "PRJ"; @@ -594,6 +594,7 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndWorkflowNotFo when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(""); + when(provisionService.getDeletionWorkflowName(projectKey, componentId)).thenReturn(""); when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); @@ -629,6 +630,8 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndWorkflowFound when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(deletionWorkflow); + when(provisionService.getDeletionWorkflowName(projectKey, componentId)).thenReturn(null); + when(provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId)).thenReturn(null); when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); ProjectInfo projectInfo = new ProjectInfo(); projectInfo.setClusters(List.of("cluster")); @@ -647,6 +650,64 @@ void givenAProjectKeyAndAComponentId_whenRequestDeletionIsCalledAndWorkflowFound verify(awxService).triggerWorkflowJob(anyString(), any()); } + @Test + void givenOnlyDeletionWorkflowName_whenRequestDeletion_thenTriggersWrapperWorkflow() { + var projectKey = "PRJ"; + var componentId = "CID"; + var action = CreateIncidentActionMother.of(); + + var pc = ProjectComponentExtendedInfo.builder() + .componentId(componentId) + .status(ProjectComponentStatus.CREATED.name()) + .build(); + + when(authenticationProvider.getAccessToken()).thenReturn("token"); + when(authenticationProvider.getUserPrincipalName()).thenReturn("user"); + + when(componentCatalogService.getProjectComponentById("token", projectKey, componentId)).thenReturn(pc); + + when(provisionService.getDeletionWorkflowId(projectKey, componentId)).thenReturn(""); + when(provisionService.getDeletionWorkflowName(projectKey, componentId)).thenReturn("WF_NAME"); + when(provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId)).thenReturn(null); + + when(provisionService.composeCatalogItemId(pc)).thenReturn("catalogItemId"); + + ProjectInfo projectInfo = new ProjectInfo(); + projectInfo.setClusters(List.of("cluster")); + when(projectsInfoService.getProjectClusters(any(), any())).thenReturn(projectInfo); + + when(entitiesMapper.asAwxWorkflowJobLaunch((ProvisionAction) any())).thenReturn(new AwxWorkflowJobLaunch()); + when(awxService.triggerWorkflowJob(any(), any())) + .thenReturn(Pair.of(HttpStatus.OK, Optional.empty())); + + var result = facade.requestDeletion(projectKey, componentId, action); + + assertThat(result.httpStatusCode()).isEqualTo(HttpStatus.OK); + verify(awxService).triggerWorkflowJob(eq("DELETE"), any()); + } + + @Test + void givenWrapperWorkflow_whenRequestDeletion_thenAddsWrapperParameters() { + var action = CreateIncidentActionMother.of(); + action.setParameters(new ArrayList<>()); + + when(authenticationProvider.getAccessToken()).thenReturn("token"); + + ReflectionTestUtils.invokeMethod( + facade, + "addDeletionWrapperWorkflowParameters", + "catalogId", + "wfId", + null, + "60", + action + ); + + assertThat(facade.getParameterString(action, "catalog_item_id")).isEqualTo("catalogId"); + assertThat(facade.getParameterString(action, "deletion_workflow_id")).isEqualTo("wfId"); + assertThat(facade.getParameterString(action, "deletion_workflow_timeout_seconds")).isEqualTo("60"); + } + @Test void givenBothCatalogItemIdAndSlugEmpty_whenResolveCatalogItemIdIsCalled_thenReturnsNull() { // when diff --git a/src/test/resources/application-testing.env b/src/test/resources/application-testing.env index fbea22b..91feaf7 100644 --- a/src/test/resources/application-testing.env +++ b/src/test/resources/application-testing.env @@ -18,6 +18,7 @@ COMPONENT_CATALOG_PROVISIONER_USERNAME= COMPONENT_CATALOG_PROVISIONER_PASSWORD= CREATE_INCIDENT_WORKFLOW_ID=3361 +DELETION_WRAPPER_WORKFLOW_ID=3823 PROJECTS_INFO_SERVICE_BASE_REST_URL="https://projects-info-service-devstack-dev.example.com/v1" CACHING_PROJECTS_INFO_SERVICE_CACHE_EVICTION_INTERVAL=5s From 0abf27d5a71aae50c66696d57454fdd09b124a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Mon, 18 May 2026 14:18:51 +0200 Subject: [PATCH 3/4] Add provisionService tests --- .../server/services/ProvisionServiceTest.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java index f9c6ac5..402bb83 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java @@ -19,6 +19,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -379,4 +380,154 @@ void givenProjectComponentWithoutDeletionWorkflow_whenGetDeletionWorkflowIsCalle assertThat(result).isEmpty(); } + @Test + void givenProjectComponentWithDeletionWorkflowName_whenGetDeletionWorkflowNameIsCalled_thenReturnsWorkflowName() { + // given + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var projectComponent = new ProjectComponentExtendedInfo(); + var parameter = new ProjectComponentParameter(); + parameter.setName("deletion_workflow_name"); + parameter.setValues(List.of("WF_NAME")); + + projectComponent.setParameters(List.of(parameter)); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + // when + var result = provisionService.getDeletionWorkflowName(projectKey, componentId); + + // then + assertThat(result).isEqualTo("WF_NAME"); + } + + @Test + void givenProjectComponentWithoutDeletionWorkflowName_whenGetDeletionWorkflowNameIsCalled_thenReturnsEmptyString() { + // given + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var projectComponent = new ProjectComponentExtendedInfo(); + projectComponent.setParameters(List.of()); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + // when + var result = provisionService.getDeletionWorkflowName(projectKey, componentId); + + // then + assertThat(result).isEmpty(); + } + + @Test + void givenProjectComponentWithDeletionWorkflowTimeout_whenGetDeletionWorkflowTimeoutIsCalled_thenReturnsTimeout() { + // given + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var projectComponent = new ProjectComponentExtendedInfo(); + var parameter = new ProjectComponentParameter(); + parameter.setName("deletion_workflow_timeout_seconds"); + parameter.setValues(List.of("120")); + + projectComponent.setParameters(List.of(parameter)); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + // when + var result = provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId); + + // then + assertThat(result).isEqualTo("120"); + } + + @Test + void givenProjectComponentWithoutDeletionWorkflowTimeout_whenGetDeletionWorkflowTimeoutIsCalled_thenReturnsEmptyString() { + // given + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var projectComponent = new ProjectComponentExtendedInfo(); + projectComponent.setParameters(List.of()); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + // when + var result = provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId); + + // then + assertThat(result).isEmpty(); + } + + @Test + void givenProjectComponentWithAllDeletionWorkflowParams_whenEachGetterIsCalled_thenReturnsCorrectValues() { + // given + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var params = List.of( + ProjectComponentParameter.builder() + .name("deletion_workflow") + .values(List.of("WF_ID")) + .build(), + ProjectComponentParameter.builder() + .name("deletion_workflow_name") + .values(List.of("WF_NAME")) + .build(), + ProjectComponentParameter.builder() + .name("deletion_workflow_timeout_seconds") + .values(List.of("300")) + .build() + ); + + var projectComponent = new ProjectComponentExtendedInfo(); + projectComponent.setParameters(params); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + // when / then + assertThat(provisionService.getDeletionWorkflowId(projectKey, componentId)).isEqualTo("WF_ID"); + assertThat(provisionService.getDeletionWorkflowName(projectKey, componentId)).isEqualTo("WF_NAME"); + assertThat(provisionService.getDeletionWorkflowTimeoutSeconds(projectKey, componentId)).isEqualTo("300"); + } + + @Test + void givenParameterWithNullValues_whenGetDeletionWorkflowIsCalled_thenThrowsException() { + var accessToken = "token"; + var projectKey = "PRJ"; + var componentId = "CID"; + + var parameter = new ProjectComponentParameter(); + parameter.setName("deletion_workflow_name"); + parameter.setValues(null); + + var projectComponent = new ProjectComponentExtendedInfo(); + projectComponent.setParameters(List.of(parameter)); + + when(authenticationProvider.getAccessToken()).thenReturn(accessToken); + when(componentCatalogService.getProjectComponentById(accessToken, projectKey, componentId)) + .thenReturn(projectComponent); + + assertThatThrownBy(() -> + provisionService.getDeletionWorkflowName(projectKey, componentId) + ).isInstanceOf(AssertionError.class); + } + + } From d989e8ff99f5a87428d0c4e090ece166e931f51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Mon, 18 May 2026 15:35:35 +0200 Subject: [PATCH 4/4] Remove WORKFLOW fallback variable --- .../server/facade/ProvisionResultsApiFacade.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java index 7ae4c06..1591386 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionResultsApiFacade.java @@ -37,10 +37,10 @@ public class ProvisionResultsApiFacade { private final ApplicationAuthenticationProvider applicationAuthenticationProvider; - @Value("${component-provisioner.awx.workflows.create-incident-workflow-id:WORKFLOW}") + @Value("${component-provisioner.awx.workflows.create-incident-workflow-id}") private String createIncidentWorkflowId; - @Value("${component-provisioner.awx.workflows.deletion-wrapper-workflow-id:WORKFLOW}") + @Value("${component-provisioner.awx.workflows.deletion-wrapper-workflow-id}") private String deletionWrapperWorkflowId; public ProvisionResultsApiFacade(AwxService awxService,