Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
97 changes: 79 additions & 18 deletions api-project-component-v0/openapi/api-project-component-v0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ paths:
- name: projectId
in: path
required: true
description: Project id
description: Project key
schema:
type: string
requestBody:
Expand All @@ -37,8 +37,8 @@ paths:
schema:
$ref: '#/components/schemas/CreateComponentRequest'
responses:
'201':
description: Created
'200':
description: OK
headers:
Location:
schema:
Expand All @@ -49,14 +49,26 @@ paths:
$ref: '#/components/schemas/CreateComponentResponse'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/CreateComponentResponse'
'401':
$ref: '#/components/responses/UnauthorizedError'
'403':
description: Forbidden
'404':
description: Endpoint not found / Project not found / Product not found
content:
application/json:
schema:
$ref: '#/components/schemas/CreateComponentResponse'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/CreateComponentResponse'

/projects/{projectId}/components/{componentId}:
get:
Expand All @@ -69,7 +81,7 @@ paths:
- name: projectId
in: path
required: true
description: Project id
description: Project key
schema:
type: string
- name: componentId
Expand All @@ -78,6 +90,7 @@ paths:
description: Component id
schema:
type: string
format: uuid
responses:
'200':
description: Component information
Expand Down Expand Up @@ -105,6 +118,10 @@ components:
schemas:
Component:
type: object
x-class-extra-annotation: |
@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
required:
- params
properties:
id:
type: string
Expand All @@ -117,50 +134,94 @@ components:
description: Description of the product
productName:
type: string
description: Name of the product (e.g. Docker plain)
description: Name of the product
productId:
type: string
description: Product ID
environment:
type: string
description: Environment (e.g. DEV)
$ref: '#/components/schemas/EnvironmentsTypeDTO'
description: Environment
status:
type: string
description: Status of the component (e.g. READY, NOT_READY)
$ref: '#/components/schemas/EnvironmentsStatusDTO'
description: Status of the component
resultTraceback:
type: string
type: object
nullable: true
description: Traceback information in case of error
x-field-extra-annotation: |
@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY)
repositoryURL:
type: string
description: URL of the repository (for ODS products)
params:
type: object
description: Additional parameters (key-value pairs)
additionalProperties: true
component-type:
type: string
description: Type of component (ods|awx)
CreateComponentResponse:
type: object
required:
- timestamp
- httpStatus
- errorKey
- message
- path
properties:
errorCode:
timestamp:
type: integer
field:
format: int64
httpStatus:
type: string
errorKey:
type: string
error:
type: string
message:
type: string
location:
path:
type: string
format: url
CreateComponentRequest:
type: object
required:
- name
- productId
- params
properties:
name:
type: string
description: component name
description: Component name
minLength: 2
maxLength: 52
pattern: "^[a-z]+(-?[a-z0-9]+)*$"
x-field-extra-annotation: |
@Size(min=2, message = "The component name must contain more than 1 character.")
@Size(max=52, message = "The component name must contain less than 53 characters.")
@Pattern(regexp = "^[a-z]+(-?[a-z0-9]+)*$", message = "Only lowercase letters and numbers are allowed (example: componentname8), with optional dashes in between (example: my-component). The first character must be a letter.")
productId:
type: string
description: product id
description: Product id
registerOnly:
type: boolean
description: Register only flag (no provisioning)
default: false
params:
type: object
additionalProperties:
type: string
description: Additional parameters (key-value pairs)
additionalProperties: true
EnvironmentsTypeDTO:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if it is needed for the new Markeplace 2.0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

type: string
enum:
- DEV
- QA
- PROD
EnvironmentsStatusDTO:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the status of the environment, it is the status of the Component / Project provisioning

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

type: string
enum:
- READY
- RUNNING
- FAILED
- DELETING
- DELETED
- UNKNOWN
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.opendevstack.apiservice.project.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.openapitools.jackson.nullable.JsonNullable;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JsonNullableConfig {

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonNullableCustomizer() {
return builder -> {
builder.modulesToInstall(JsonNullableModule.class);
builder.postConfigurer(this::configureJsonNullableInclusion);
};
}

private void configureJsonNullableInclusion(ObjectMapper objectMapper) {
objectMapper.configOverride(JsonNullable.class)
.setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_ABSENT, JsonInclude.Include.NON_ABSENT));
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
package org.opendevstack.apiservice.project.controller;

import org.opendevstack.apiservice.project.exception.ComponentErrorKey;
import org.opendevstack.apiservice.project.model.CreateComponentResponse;
import org.springframework.http.HttpStatus;

public class ComponentsResponseFactory {
public final class ComponentsResponseFactory {

private ComponentsResponseFactory() {
}

public static CreateComponentResponse error(String projectId) {
public static CreateComponentResponse entityCreated(String projectId, String componentId) {
String path = String.format("/api/pub/v0/projects/%s/components/%s", projectId, componentId);
return ok(path, "Component created");
}

public static CreateComponentResponse badRequest(String path, String message, ComponentErrorKey errorKey) {
return buildResponse(HttpStatus.BAD_REQUEST, errorKey, path, message);
}

public static CreateComponentResponse forbidden(String path, String message, ComponentErrorKey errorKey) {
return buildResponse(HttpStatus.FORBIDDEN, errorKey, path, message);
}

public static CreateComponentResponse notFound(String path, String message, ComponentErrorKey errorKey) {
return buildResponse(HttpStatus.NOT_FOUND, errorKey, path, message);
}

public static CreateComponentResponse internalError(String path, String message, ComponentErrorKey errorKey) {
return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, errorKey, path, message);
}

private static CreateComponentResponse buildResponse(HttpStatus httpStatus, ComponentErrorKey errorKey, String path,
String message) {
CreateComponentResponse response = new CreateComponentResponse();
response.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setMessage("Failed to create component for project '" + projectId + "'");
response.setTimestamp(System.currentTimeMillis());
response.setHttpStatus(httpStatus.name());
response.setErrorKey(errorKey.getKey());
response.setError(errorKey.getMessage());
response.setMessage(message);
response.setPath(path);
return response;
}

public static CreateComponentResponse entityCreated(String projectId, String componentId) {
public static CreateComponentResponse ok(String path, String message) {
CreateComponentResponse response = new CreateComponentResponse();
response.setErrorCode(HttpStatus.CREATED.value());
response.setMessage(componentId + " component created successfully in project " + projectId);
response.setTimestamp(System.currentTimeMillis());
response.setHttpStatus(HttpStatus.OK.name());
response.setErrorKey(ComponentErrorKey.OK.getKey());
response.setMessage(message);
response.setPath(path);
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opendevstack.apiservice.project.api.ProjectComponentsApi;
import org.opendevstack.apiservice.project.exception.ComponentCreationException;
import org.opendevstack.apiservice.project.exception.ComponentNotFoundException;
import org.opendevstack.apiservice.project.facade.ComponentsFacade;
import org.opendevstack.apiservice.project.mapper.ComponentResponseMapper;
import org.opendevstack.apiservice.project.model.Component;
Expand All @@ -13,44 +15,43 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@AllArgsConstructor
@Slf4j
@RequestMapping("/api/pub/v0")
@RequestMapping(ProjectComponentsController.API_BASE_PATH)
public class ProjectComponentsController implements ProjectComponentsApi {

public static final String API_BASE_PATH = "/api/pub/v0";

private final ComponentsFacade componentsFacade;

private final ComponentResponseMapper componentResponseMapper;

@Override
public ResponseEntity<CreateComponentResponse> createProjectComponent(String projectId, CreateComponentRequest createComponentRequest) {
try {
Component component = componentsFacade.createProjectComponent(projectId, createComponentRequest);
log.info("Created component {} for project id {} and request {}", component, projectId, createComponentRequest);
if (component == null) {
log.error("Failed to create component for project '{}'", projectId);
return componentResponseMapper.toResponseEntity(ComponentsResponseFactory.error(projectId));
}
return componentResponseMapper.toResponseEntity(ComponentsResponseFactory.entityCreated(projectId, component.getId()));
} catch (Exception e) {
log.error("Error while trying to create component for project '" + projectId + "': " + e.getMessage(), e);
return componentResponseMapper.toResponseEntity(ComponentsResponseFactory.error(projectId));
Component component = componentsFacade.createProjectComponent(projectId, createComponentRequest);
if (component == null) {
throw new ComponentCreationException(String.format("Failed to create component for project '%s'", projectId));
}

log.info("Created component {} for project id {} and request {}", component, projectId, createComponentRequest);
return componentResponseMapper.toResponseEntity(
ComponentsResponseFactory.entityCreated(projectId, component.getId())
);
}

@Override
public ResponseEntity<Component> getProjectComponent(String projectId, String componentId) {
try {
Component component = componentsFacade.getProjectComponent(projectId, componentId);
log.info("Retrieved component '{}' for project '{}': {}", componentId, projectId, component);
if (component == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.status(HttpStatus.OK).body(component);
} catch (Exception e) {
log.error("Error retrieving component '{}' for project '{}': {}", componentId, projectId, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
public ResponseEntity<Component> getProjectComponent(String projectId, UUID componentId) {
Component component = componentsFacade.getProjectComponent(projectId, componentId.toString());
if (component == null) {
throw new ComponentNotFoundException(
String.format("Component '%s' not found for project '%s'", componentId, projectId)
);
}

log.info("Retrieved component '{}' for project '{}': {}", componentId, projectId, component);
return ResponseEntity.status(HttpStatus.OK).body(component);
}
}
Loading
Loading