Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ out/
### secrets skip for token generation ###
/openshift/*.key
/openshift/*.p12
/src/main/generated/
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,13 @@ dependencies {
implementation 'org.apache.commons:commons-collections4:4.5.0'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'nl.basjes.codeowners:codeowners-reader:1.9.0'
implementation 'org.mapstruct:mapstruct:1.5.5.Final'

// Development dependencies
implementation 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

// Testing dependencies
Expand Down
103 changes: 78 additions & 25 deletions openapi/openapi-component_catalog-v1.0.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ paths:
required: true
schema:
type: string
- name: accessToken
in: query
description: access token for azure queries.
required: true
schema:
type: string
responses:
"200":
description: A list of Project Component Information
Expand Down Expand Up @@ -81,6 +75,50 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/RestErrorMessage'
/project/{projectKey}/component/{componentId}:
get:
tags:
- Project-components
summary: Returns the extended information of a project component given both its project key and component ID in the Bitbucket repository.
operationId: getProjectComponentById
parameters:
- name: projectKey
in: path
description: project key.
required: true
schema:
type: string
- name: componentId
in: path
description: component ID.
required: true
schema:
type: string
responses:
"200":
description: The extended information of a project component.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectComponentExtendedInfo'
"401":
description: Invalid client token on the request.
content:
application/json:
schema:
$ref: '#/components/schemas/RestErrorMessage'
"403":
description: Insufficient permissions for the client to access the resource.
content:
application/json:
schema:
$ref: '#/components/schemas/RestErrorMessage'
"500":
description: Server error.
content:
application/json:
schema:
$ref: '#/components/schemas/RestErrorMessage'
/catalog-descriptors:
get:
tags:
Expand Down Expand Up @@ -357,12 +395,6 @@ paths:
required: true
schema:
type: string
- name: accessToken
in: query
description: access token for azure queries.
required: true
schema:
type: string
- name: sortByTitle
in: query
description: Sort the returned CatalogItems by title, either in ascending or descending order.
Expand Down Expand Up @@ -436,12 +468,6 @@ paths:
required: true
schema:
type: string
- name: accessToken
in: query
description: access token for azure queries.
required: true
schema:
type: string
responses:
"200":
description: The CatalogItem.
Expand Down Expand Up @@ -871,6 +897,39 @@ components:
componentUrl:
type: string
example: 'https://bitbucket.com/projects/CATALOGS/repos/project-components/browse/projects'
ProjectComponentParameter:
properties:
name:
type: string
example: 'a name'
values:
type: array
items:
type: string
example:
- 'value 1'
- 'value 2'
ProjectComponentExtendedInfo:
properties:
componentId:
type: string
example: 'edpc-4132-v2'
catalogItemId:
type: string
example: 'edpc-4132-v2'
catalogItemRef:
type: string
example: 'edpc-4132-v2'
status:
type: string
example: 'CREATING'
componentUrl:
type: string
example: 'https://bitbucket.com/projects/CATALOGS/repos/project-components/browse/projects'
parameters:
type: array
items:
$ref: '#/components/schemas/ProjectComponentParameter'
CatalogDescriptor:
properties:
id:
Expand Down Expand Up @@ -1224,12 +1283,6 @@ components:
example: "https://bitbucket.com/projects/DEVSTACK/repos/devstack-component-catalog"
nullable: true

accessToken:
type: string
description: the access token to be used to get azure groups
example: "some-access-token"
nullable: false

parameters:
type: array
description: List of name/value string parameters.
Expand Down Expand Up @@ -1282,4 +1335,4 @@ components:
type: string
example:
- "production"
- "staging"
- "staging"
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"org.opendevstack.component_provisioner.server.security",
"org.opendevstack.component_provisioner.config",
"org.opendevstack.component_provisioner.server.services",
"org.opendevstack.component_provisioner.server.mappers",
"org.opendevstack.component_provisioner.client.awx.v2"
},
nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.opendevstack.component_provisioner.server.controllers.exceptions.*;
import org.opendevstack.component_provisioner.server.model.RestErrorMessage;
import lombok.extern.slf4j.Slf4j;
import org.opendevstack.component_provisioner.server.services.exceptions.InvalidIdException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -61,6 +62,16 @@ public ResponseEntity<RestErrorMessage> handleSlugNotFoundException(SlugNotFound
return defaultErrResponse(ex, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(ComponentNotFoundException.class)
public ResponseEntity<RestErrorMessage> handleComponentNotFoundException(ComponentNotFoundException ex) {
return defaultErrResponse(ex, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(InvalidIdException.class)
public ResponseEntity<RestErrorMessage> handleInvalidIdException(InvalidIdException ex) {
return defaultErrResponse(ex, HttpStatus.BAD_REQUEST);
}

private static ResponseEntity<RestErrorMessage> defaultErrResponse(Exception ex, HttpStatus errStatus) {
// Explicitly setting MediaType.APPLICATION_JSON contentType is required,
// due to clients sending miscellaneous Accept headers on the request,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.opendevstack.component_provisioner.server.controllers.exceptions;

public class ComponentNotFoundException extends RuntimeException {
public ComponentNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ private String resolveCatalogItemId(String accessToken,
}

public void deleteProvisioningStatus(String projectKey, String componentId, String accessToken) {
provisionService.deleteProvisioningStatus(projectKey, componentId, accessToken);
var projectComponents = componentCatalogService.getProjectComponentExtendedInfo(projectKey, componentId, accessToken);

provisionService.deleteProvisioningStatus(projectKey, componentId, projectComponents, accessToken);
}


public void validate(String projectKey, String status, NotifyProvisioningStatusUpdateRequest request) {
validate(projectKey, status);
if (StringUtils.isNotBlank(request.getCatalogItemId()) && StringUtils.isNotBlank(request.getCatalogItemSlug())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.opendevstack.component_provisioner.server.mappers;

import org.mapstruct.Mapper;
import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProjectComponentParameter;
import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningStatusUpdateRequestParametersInner;

@Mapper(componentModel = "spring")
public interface ProvisioningStatusUpdateRequestParametersInnerMapper {

ProvisioningStatusUpdateRequestParametersInner toTarget(
ProjectComponentParameter source
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ public void notifyComponentCatalogProvisionStarts(String projectKey,
.componentId(componentId)
.catalogItemId(catalogItemId)
.componentUrl(componentUrl)
.accessToken(accessToken)
.parameters(obfuscatedParameters)
.build();

Expand All @@ -134,15 +133,22 @@ public List<ProjectComponentInfo> getProjectComponents(String projectKey, String
var auth = (HttpBearerAuth) componentCatalogApiClient.getAuthentication("bearerAuth");
auth.setBearerToken(accessToken);

return projectComponentsApi.getProjectComponents(projectKey, accessToken);
return projectComponentsApi.getProjectComponents(projectKey);
}

public ProjectComponentExtendedInfo getProjectComponentExtendedInfo(String projectKey, String componentId, String accessToken) {
var auth = (HttpBearerAuth) componentCatalogApiClient.getAuthentication("bearerAuth");
auth.setBearerToken(accessToken);

return projectComponentsApi.getProjectComponentById(projectKey, componentId);
}

@Cacheable
public CatalogItem getCatalogItem(String accessToken, String catalogItemId, String projectKey) {
var apiClient = apiClientsBuilder.componentCatalogApiClient(accessToken, componentCatalogServiceProps.getBaseRestUrl().toString());
var catalogItemsApi = apiClientsBuilder.catalogItemsApi(apiClient);

var catalogItem = catalogItemsApi.getCatalogItemByIdForProjectKey(catalogItemId, projectKey, accessToken);
var catalogItem = catalogItemsApi.getCatalogItemByIdForProjectKey(catalogItemId, projectKey);

log.debug("Retrieved catalog item with id {} for project key {}: {}", catalogItemId, projectKey, catalogItem);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
package org.opendevstack.component_provisioner.server.services;

import lombok.AllArgsConstructor;
import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningDeleteRequest;
import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningStatusUpdateRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.opendevstack.component_provisioner.client.component_catalog.v1.model.*;
import org.opendevstack.component_provisioner.config.ApplicationPropertiesConfiguration;
import org.opendevstack.component_provisioner.server.controllers.model.ProjectComponentStatus;
import org.opendevstack.component_provisioner.server.mappers.ProvisioningStatusUpdateRequestParametersInnerMapper;
import org.opendevstack.component_provisioner.server.services.exceptions.InvalidIdException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.opendevstack.component_provisioner.server.services.common.IdEncoderDecoder.idDecode;
import static org.opendevstack.component_provisioner.server.services.common.IdEncoderDecoder.idEncode;

@Service
@Slf4j
@AllArgsConstructor
public class ProvisionService {

private final ApiClientsBuilder apiClientsBuilder;
private final ApplicationPropertiesConfiguration.ComponentCatalogServiceProps componentCatalogServiceProps;
private final ProvisioningStatusUpdateRequestParametersInnerMapper provisioningStatusUpdateRequestParametersInnerMapper;

public void notifyProvisioningStatusUpdate(String projectKey, ProjectComponentStatus status, String componentId,
String catalogItemId, String componentUrl, String accessToken) {
Expand All @@ -26,7 +38,6 @@
.componentId(componentId)
.catalogItemId(catalogItemId)
.componentUrl(componentUrl)
.accessToken(accessToken)
.build();

log.debug("Calling provisionerActionsApi.notifyProvisioningStatusUpdatePartially. ProjectKey: {}, status: {}, notifyProvisioningCompletedRequest: {}",
Expand All @@ -35,15 +46,74 @@
provisionerActionsApi.notifyProvisioningStatusUpdatePartially(projectKey, status.name(), notifyProvisioningCompletedRequest);
}

public void deleteProvisioningStatus(String projectKey, String componentId, String accessToken) {
public void deleteProvisioningStatus(String projectKey,
String componentId,
ProjectComponentExtendedInfo projectComponent,
String accessToken) {
log.info("Deleting provisioning completed. Project Key: {}, componentId: {}", projectKey, componentId);

var catalogItemId = composeCatalogItemId(projectComponent);

var apiClient = apiClientsBuilder.componentCatalogApiClient(accessToken, componentCatalogServiceProps.getBaseRestUrl().toString());

var catalogItemsApi = apiClientsBuilder.catalogItemsApi(apiClient);
var catalogItem = catalogItemsApi.getCatalogItemById(catalogItemId);

var parametersToSend = extractDeletionParameters(catalogItem, projectComponent);

if (parametersToSend.isEmpty()) {
log.debug("No parameters marked as sendOnDeletion found");
}

var provisioningDeleteRequest = ProvisioningDeleteRequest.builder()
.componentId(componentId)
.parameters(parametersToSend)
.build();

var provisionerActionsApi = apiClientsBuilder.provisionerActionsApi(accessToken, componentCatalogServiceProps.getBaseRestUrl().toString());
var provisionerActionsApi = apiClientsBuilder.provisionerActionsApi(accessToken,
componentCatalogServiceProps.getBaseRestUrl().toString());

provisionerActionsApi.deleteProvisioningStatus(projectKey, provisioningDeleteRequest);
}

private String composeCatalogItemId(ProjectComponentExtendedInfo projectComponents) {
try {
var decodedCatalogItemId = idDecode(projectComponents.getCatalogItemId());
var decodedCatalogItemRef = idDecode(projectComponents.getCatalogItemRef());
return idEncode(Strings.concat(decodedCatalogItemId, decodedCatalogItemRef));

} catch (InvalidIdException e) {
throw new RuntimeException(e);

Check warning on line 86 in src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=opendevstack_ods-component-provisioner&issues=AZ26Zf4LLbYehPOnDpwI&open=AZ26Zf4LLbYehPOnDpwI&pullRequest=24
}
}

private List<ProvisioningStatusUpdateRequestParametersInner> extractDeletionParameters(
CatalogItem catalogItem,
ProjectComponentExtendedInfo projectComponent) {
var projectParametersByName =
Optional.ofNullable(projectComponent.getParameters())
.orElse(List.of())
.stream()
.filter(p -> p.getName() != null)
.collect(Collectors.toMap(
ProjectComponentParameter::getName,
Function.identity(), (a, b) -> a
));

return Optional.ofNullable(catalogItem.getUserActions())
.orElse(List.of())
.stream()
.peek(action -> log.debug("User action found: {}", action))
.flatMap(action ->
Optional.ofNullable(action.getParameters())
.orElse(List.of())
.stream()
)
.peek(param -> log.debug("Parameter found: {}", param))
.filter(param -> Boolean.TRUE.equals(param.getSendOnDeletion()))
.map(param -> projectParametersByName.get(param.getName()))
.filter(Objects::nonNull)
.map(provisioningStatusUpdateRequestParametersInnerMapper::toTarget)
.toList();
}
}
Loading
Loading