diff --git a/multiapps-controller-api/pom.xml b/multiapps-controller-api/pom.xml index 69c7fc8c07..464ab07c3e 100644 --- a/multiapps-controller-api/pom.xml +++ b/multiapps-controller-api/pom.xml @@ -120,7 +120,27 @@ mtarest MTA REST API - This is the API of the Cloud Foundry MultiApps Controller + This is the API of the Cloud Foundry MultiApps Controller. + +## Typical deploy sequence + +Step 1 (optional) — Obtain a CSRF token: + GET /api/v1/csrf-token + +Step 2 — Upload the MTA archive: + POST /api/v1/spaces/{spaceGuid}/files + +Step 3 — Start a deploy operation: + POST /api/v1/spaces/{spaceGuid}/operations + Request body: Operation object with processType=DEPLOY and mtaId. + +Step 4 — Poll operation status: + GET /api/v1/spaces/{spaceGuid}/operations/{operationId} + Repeat until state is one of: FINISHED, ERROR, ABORTED, ACTION_REQUIRED. + +Step 5 (optional) — Execute an action: + POST /api/v1/spaces/{spaceGuid}/operations/{operationId}?actionId=abort|retry + Use when state is ACTION_REQUIRED or to abort a running operation. 1.4.0 @@ -151,6 +171,37 @@ + + + org.apache.maven.plugins + maven-antrun-plugin + + + quote-yaml-response-codes + compile + + run + + + + + + + + + + diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java index 6462e97470..e77bb356c0 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/Constants.java @@ -71,4 +71,24 @@ private Endpoints() { } + public static class HttpResponses { + + private HttpResponses() { + } + + public static final String OK = "OK"; + public static final String CREATED = "Created"; + public static final String NO_CONTENT = "No Content"; + public static final String ACCEPTED = "Accepted"; + public static final String BAD_REQUEST = "Bad Request"; + public static final String UNAUTHORIZED = "Unauthorized"; + public static final String FORBIDDEN = "Forbidden"; + public static final String NOT_FOUND = "Not Found"; + public static final String CONFLICT = "Conflict"; + public static final String REQUEST_ENTITY_TOO_LARGE = "Request Entity Too Large"; + public static final String UNSUPPORTED_MEDIA_TYPE = "Unsupported Media Type"; + public static final String INTERNAL_SERVER_ERROR = "Internal Server Error"; + + } + } diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/CsrfTokenApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/CsrfTokenApi.java index d84179ba2d..7658e5f22f 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/CsrfTokenApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/CsrfTokenApi.java @@ -2,8 +2,9 @@ import jakarta.inject.Inject; -import org.cloudfoundry.multiapps.controller.api.CsrfTokenApiService; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; +import org.cloudfoundry.multiapps.controller.api.CsrfTokenApiService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -24,10 +25,13 @@ public class CsrfTokenApi { private CsrfTokenApiService delegate; @GetMapping - @ApiOperation(value = "", notes = "Retrieves a csrf-token header ", authorizations = { @Authorization(value = "oauth2", scopes = { + @ApiOperation(value = "Retrieves a CSRF token header", notes = "Retrieves a csrf-token header", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 204, message = "No Content") }) + @ApiResponses(value = { @ApiResponse(code = 204, message = HttpResponses.NO_CONTENT), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity getCsrfToken() { return delegate.getCsrfToken(); } diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/FilesApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/FilesApi.java index beb6dc1a84..db1f9365a4 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/FilesApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/FilesApi.java @@ -5,6 +5,7 @@ import jakarta.inject.Inject; import org.cloudfoundry.multiapps.controller.api.Constants.Endpoints; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.PathVariables; import org.cloudfoundry.multiapps.controller.api.Constants.RequestVariables; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; @@ -39,11 +40,14 @@ public class FilesApi { private FilesApiService delegate; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "getMtaFiles", notes = "Retrieves all Multi-Target Application files ", response = FileMetadata.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "Retrieve all uploaded MTA archive files in a space", nickname = "getMtaFiles", notes = "Retrieves all Multi-Target Application files", response = FileMetadata.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = FileMetadata.class, responseContainer = "List") }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = FileMetadata.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity> getFiles(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @ApiParam(value = "Filter mtas by namespace") @RequestParam(name = RequestVariables.NAMESPACE, required = false) String namespace) { @@ -51,11 +55,17 @@ public class FilesApi { } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "uploadMtaFile", notes = "Uploads a Multi Target Application archive or an Extension Descriptor ", response = FileMetadata.class, authorizations = { + @ApiOperation(value = "Upload an MTA archive or Extension Descriptor", nickname = "uploadMtaFile", notes = "Uploads a Multi Target Application archive or an Extension Descriptor", response = FileMetadata.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 201, message = "Created", response = FileMetadata.class) }) + @ApiResponses(value = { @ApiResponse(code = 201, message = HttpResponses.CREATED, response = FileMetadata.class), + @ApiResponse(code = 400, message = HttpResponses.BAD_REQUEST), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 413, message = HttpResponses.REQUEST_ENTITY_TOO_LARGE), + @ApiResponse(code = 415, message = HttpResponses.UNSUPPORTED_MEDIA_TYPE), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity uploadFile(MultipartHttpServletRequest request, @ApiParam(value = "GUID of space you wish to deploy in") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @@ -64,11 +74,16 @@ public class FilesApi { } @PostMapping(path = Endpoints.ASYNC_UPLOAD, consumes = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "startUploadFromUrl", notes = "Uploads a Multi Target Application archive or an Extension Descriptor from a remote endpoint", authorizations = { + @ApiOperation(value = "Start an asynchronous MTA archive upload from a URL", nickname = "startUploadFromUrl", notes = "Uploads a Multi Target Application archive or an Extension Descriptor from a remote endpoint", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 202, message = "Accepted") }) + @ApiResponses(value = { @ApiResponse(code = 202, message = HttpResponses.ACCEPTED), + @ApiResponse(code = 400, message = HttpResponses.BAD_REQUEST), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 413, message = HttpResponses.REQUEST_ENTITY_TOO_LARGE), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity startUploadFromUrl(@ApiParam(value = "GUID of space you wish to deploy in") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @ApiParam(value = "file namespace") @RequestParam(name = RequestVariables.NAMESPACE, required = false) String namespace, @@ -77,12 +92,16 @@ public class FilesApi { } @GetMapping(path = Endpoints.ASYNC_UPLOAD_JOB, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "getAsyncUploadJob", notes = "Gets the status of an async upload job", response = AsyncUploadResult.class, authorizations = { + @ApiOperation(value = "Get the status of an asynchronous URL upload job", nickname = "getAsyncUploadJob", notes = "Gets the status of an async upload job", response = AsyncUploadResult.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), - @ApiResponse(code = 201, message = "Created", response = AsyncUploadResult.class) }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK), + @ApiResponse(code = 201, message = HttpResponses.CREATED, response = AsyncUploadResult.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity getUploadFromUrlJob(@ApiParam(value = "GUID of space you wish to deploy in") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @ApiParam(value = "file namespace") @RequestParam(name = RequestVariables.NAMESPACE, required = false) String namespace, diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/InfoApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/InfoApi.java index 9570a32eac..1a52de5dea 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/InfoApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/InfoApi.java @@ -3,6 +3,7 @@ import jakarta.inject.Inject; import org.cloudfoundry.multiapps.controller.api.InfoApiService; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; import org.cloudfoundry.multiapps.controller.api.model.Info; import org.springframework.http.MediaType; @@ -26,11 +27,14 @@ public class InfoApi { private InfoApiService delegate; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", notes = "Retrieve information about the Deploy Service application ", response = Info.class, authorizations = { + @ApiOperation(value = "Retrieve information about the Deploy Service application", notes = "Retrieve information about the Deploy Service application", response = Info.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Info.class) }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Info.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity getInfo() { return delegate.getInfo(); } diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java index 8a7433f537..1a38c30570 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/MtasApi.java @@ -6,6 +6,7 @@ import org.cloudfoundry.multiapps.controller.api.MtasApiService; import org.cloudfoundry.multiapps.controller.api.Constants.Endpoints; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.PathVariables; import org.cloudfoundry.multiapps.controller.api.Constants.RequestVariables; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; @@ -33,22 +34,29 @@ public class MtasApi { private MtasApiService delegate; @GetMapping(path = Endpoints.MTA, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", notes = "Retrieves Multi-Target Application in a space ", response = Mta.class, authorizations = { + @ApiOperation(value = "Retrieve a specific deployed MTA by ID", notes = "Retrieves Multi-Target Application in a space", response = Mta.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Mta.class) }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Mta.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity getMta(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @ApiParam(value = "mtaID of requested mta") @PathVariable(RequestVariables.MTA_ID) String mtaId) { return delegate.getMta(spaceGuid, mtaId); } @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", notes = "Retrieves all Multi-Target Applications in a space ", response = Mta.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "Retrieve all deployed MTAs in a space", notes = "Retrieves all Multi-Target Applications in a space", response = Mta.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Mta.class, responseContainer = "List") }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Mta.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity> getMtas(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid) { return delegate.getMtas(spaceGuid); diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/OperationsApi.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/OperationsApi.java index f94e5e130f..fb0d628604 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/OperationsApi.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v1/OperationsApi.java @@ -11,6 +11,7 @@ import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import org.cloudfoundry.multiapps.controller.api.Constants.Endpoints; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.PathVariables; import org.cloudfoundry.multiapps.controller.api.Constants.QueryVariables; import org.cloudfoundry.multiapps.controller.api.Constants.RequestVariables; @@ -37,85 +38,114 @@ public class OperationsApi { private OperationsApiService delegate; @PostMapping(path = Endpoints.OPERATION) - @ApiOperation(value = "", notes = "Executes a particular action over Multi-Target Application operation ", authorizations = { + @ApiOperation(value = "Execute an action on an MTA operation", notes = "Executes a particular action over Multi-Target Application operation", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 202, message = "Accepted") }) - public ResponseEntity executeOperationAction(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @PathVariable(PathVariables.OPERATION_ID) String operationId, - @RequestParam(PathVariables.ACTION_ID) String actionId) { + @ApiResponses(value = { @ApiResponse(code = 202, message = HttpResponses.ACCEPTED), + @ApiResponse(code = 400, message = HttpResponses.BAD_REQUEST), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity executeOperationAction(@ApiParam(value = "GUID of the CF space containing the operation") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Process ID of the MTA operation") @PathVariable(PathVariables.OPERATION_ID) String operationId, + @ApiParam(value = "Action to perform, e.g. abort or retry") @RequestParam(PathVariables.ACTION_ID) String actionId) { return delegate.executeOperationAction(spaceGuid, operationId, actionId); } @GetMapping(path = Endpoints.OPERATION, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "getMtaOperation", notes = "Retrieves Multi-Target Application operation ", response = Operation.class, authorizations = { + @ApiOperation(value = "Retrieve a specific MTA operation", nickname = "getMtaOperation", notes = "Retrieves Multi-Target Application operation", response = Operation.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Operation.class) }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Operation.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity - getOperation(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @PathVariable(PathVariables.OPERATION_ID) String operationId, - @ApiParam(value = "Adds the specified property in the response body ") @RequestParam(name = "embed", required = false) String embed) { + getOperation(@ApiParam(value = "GUID of the CF space containing the operation") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Process ID of the MTA operation") @PathVariable(PathVariables.OPERATION_ID) String operationId, + @ApiParam(value = "Adds the specified property in the response body") @RequestParam(name = "embed", required = false) String embed) { return delegate.getOperation(spaceGuid, operationId, embed); } @GetMapping(path = Endpoints.OPERATION_LOGS, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "getMtaOperationLogs", notes = "Retrieves the logs Multi-Target Application operation ", response = Log.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "List log files for an MTA operation", nickname = "getMtaOperationLogs", notes = "Retrieves the logs Multi-Target Application operation", response = Log.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Log.class, responseContainer = "List") }) - public ResponseEntity> getOperationLogs(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @PathVariable(PathVariables.OPERATION_ID) String operationId) { + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Log.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity> getOperationLogs(@ApiParam(value = "GUID of the CF space containing the operation") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Process ID of the MTA operation") @PathVariable(PathVariables.OPERATION_ID) String operationId) { return delegate.getOperationLogs(spaceGuid, operationId); } @GetMapping(path = Endpoints.OPERATION_LOG_CONTENT, produces = MediaType.TEXT_PLAIN_VALUE) - @ApiOperation(value = "", nickname = "getMtaOperationLogContent", notes = "Retrieves the log content for Multi-Target Application operation ", response = String.class, authorizations = { + @ApiOperation(value = "Retrieve the content of a specific operation log", nickname = "getMtaOperationLogContent", notes = "Retrieves the log content for Multi-Target Application operation", response = String.class, authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class) }) - public ResponseEntity getOperationLogContent(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @PathVariable(PathVariables.OPERATION_ID) String operationId, - @PathVariable(PathVariables.LOG_ID) String logId) { + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = String.class), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity getOperationLogContent(@ApiParam(value = "GUID of the CF space containing the operation") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Process ID of the MTA operation") @PathVariable(PathVariables.OPERATION_ID) String operationId, + @ApiParam(value = "ID of the log file to retrieve") @PathVariable(PathVariables.LOG_ID) String logId) { return delegate.getOperationLogContent(spaceGuid, operationId, logId); } @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "getMtaOperations", notes = "Retrieves Multi-Target Application operations ", response = Operation.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "List MTA operations in a space", nickname = "getMtaOperations", notes = "Retrieves Multi-Target Application operations", response = Operation.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Operation.class, responseContainer = "List") }) - public ResponseEntity> getOperations(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @RequestParam(name = RequestVariables.MTA_ID, required = false) String mtaId, - @RequestParam(name = QueryVariables.LAST, required = false) Integer last, - @RequestParam(name = QueryVariables.STATE, required = false) List states) { + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Operation.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity> getOperations(@ApiParam(value = "GUID of the CF space") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Filter operations by MTA ID") @RequestParam(name = RequestVariables.MTA_ID, required = false) String mtaId, + @ApiParam(value = "Return only the last N operations") @RequestParam(name = QueryVariables.LAST, required = false) Integer last, + @ApiParam(value = "Filter operations by state, e.g. RUNNING, FINISHED, ERROR, ABORTED") @RequestParam(name = QueryVariables.STATE, required = false) List states) { return delegate.getOperations(spaceGuid, mtaId, states, last); } @GetMapping(path = Endpoints.OPERATION_ACTIONS, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", notes = "Retrieves available actions for Multi-Target Application operation ", response = String.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "List available actions for an MTA operation", notes = "Retrieves available actions for Multi-Target Application operation", response = String.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = String.class, responseContainer = "List") }) - public ResponseEntity> getOperationActions(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @PathVariable(PathVariables.OPERATION_ID) String operationId) { + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = String.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 404, message = HttpResponses.NOT_FOUND), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity> getOperationActions(@ApiParam(value = "GUID of the CF space containing the operation") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Process ID of the MTA operation") @PathVariable(PathVariables.OPERATION_ID) String operationId) { return delegate.getOperationActions(spaceGuid, operationId); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", nickname = "startMtaOperation", notes = "Starts execution of a Multi-Target Application operation ", authorizations = { + @ApiOperation(value = "Start a new MTA operation (deploy, undeploy, blue-green deploy)", nickname = "startMtaOperation", notes = "Starts execution of a Multi-Target Application operation", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 202, message = "Accepted") }) - public ResponseEntity startOperation(@PathVariable(PathVariables.SPACE_GUID) String spaceGuid, - @RequestBody Operation operation, HttpServletRequest httpServletRequest) { + @ApiResponses(value = { @ApiResponse(code = 202, message = HttpResponses.ACCEPTED), + @ApiResponse(code = 400, message = HttpResponses.BAD_REQUEST), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 409, message = HttpResponses.CONFLICT), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) + public ResponseEntity startOperation(@ApiParam(value = "GUID of the CF space to deploy into") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, + @ApiParam(value = "Operation descriptor specifying the process type and MTA parameters") @RequestBody Operation operation, HttpServletRequest httpServletRequest) { return delegate.startOperation(spaceGuid, operation, httpServletRequest); } diff --git a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v2/MtasApiV2.java b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v2/MtasApiV2.java index e89fff5469..592d5e4b9c 100644 --- a/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v2/MtasApiV2.java +++ b/multiapps-controller-api/src/main/java/org/cloudfoundry/multiapps/controller/api/v2/MtasApiV2.java @@ -4,6 +4,7 @@ import jakarta.inject.Inject; +import org.cloudfoundry.multiapps.controller.api.Constants.HttpResponses; import org.cloudfoundry.multiapps.controller.api.Constants.PathVariables; import org.cloudfoundry.multiapps.controller.api.Constants.RequestVariables; import org.cloudfoundry.multiapps.controller.api.Constants.Resources; @@ -33,11 +34,14 @@ public class MtasApiV2 { private MtasApiService delegate; @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation(value = "", notes = "Retrieves all Multi-Target Applications in a given space and namespace", response = Mta.class, responseContainer = "List", authorizations = { + @ApiOperation(value = "Retrieve all deployed MTAs in a space, filtered by namespace or name", notes = "Retrieves all Multi-Target Applications in a given space and namespace", response = Mta.class, responseContainer = "List", authorizations = { @Authorization(value = "oauth2", scopes = { }) }, tags = {}) - @ApiResponses(value = { @ApiResponse(code = 200, message = "OK", response = Mta.class, responseContainer = "List") }) + @ApiResponses(value = { @ApiResponse(code = 200, message = HttpResponses.OK, response = Mta.class, responseContainer = "List"), + @ApiResponse(code = 401, message = HttpResponses.UNAUTHORIZED), + @ApiResponse(code = 403, message = HttpResponses.FORBIDDEN), + @ApiResponse(code = 500, message = HttpResponses.INTERNAL_SERVER_ERROR) }) public ResponseEntity> getMtas(@ApiParam(value = "GUID of space with mtas") @PathVariable(PathVariables.SPACE_GUID) String spaceGuid, @ApiParam(value = "Filter mtas by namespace") @RequestParam(name = RequestVariables.NAMESPACE, required = false) String namespace, diff --git a/multiapps-controller-api/src/main/resources/mtarest.yaml b/multiapps-controller-api/src/main/resources/mtarest.yaml index 8a6de8c701..88e1e79a31 100644 --- a/multiapps-controller-api/src/main/resources/mtarest.yaml +++ b/multiapps-controller-api/src/main/resources/mtarest.yaml @@ -1,7 +1,15 @@ --- swagger: "2.0" info: - description: "This is the API of the Cloud Foundry MultiApps Controller" + description: "This is the API of the Cloud Foundry MultiApps Controller.\n\n## Typical\ + \ deploy sequence\n\nStep 1 (optional) — Obtain a CSRF token:\n GET /api/v1/csrf-token\n\ + \nStep 2 — Upload the MTA archive:\n POST /api/v1/spaces/{spaceGuid}/files\n\n\ + Step 3 — Start a deploy operation:\n POST /api/v1/spaces/{spaceGuid}/operations\n\ + \ Request body: Operation object with processType=DEPLOY and mtaId.\n\nStep 4\ + \ — Poll operation status:\n GET /api/v1/spaces/{spaceGuid}/operations/{operationId}\n\ + \ Repeat until state is one of: FINISHED, ERROR, ABORTED, ACTION_REQUIRED.\n\n\ + Step 5 (optional) — Execute an action:\n POST /api/v1/spaces/{spaceGuid}/operations/{operationId}?actionId=abort|retry\n\ + \ Use when state is ACTION_REQUIRED or to abort a running operation." version: "1.4.0" title: "MTA REST API" contact: {} @@ -13,34 +21,46 @@ schemes: paths: /api/v1/csrf-token: get: - summary: "" - description: "Retrieves a csrf-token header " + summary: "Retrieves a CSRF token header" + description: "Retrieves a csrf-token header" operationId: "getCsrfToken" parameters: [] responses: - 204: + "204": description: "No Content" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/info: get: - summary: "" - description: "Retrieve information about the Deploy Service application " + summary: "Retrieve information about the Deploy Service application" + description: "Retrieve information about the Deploy Service application" operationId: "getInfo" produces: - "application/json" parameters: [] responses: - 200: + "200": description: "OK" schema: $ref: "#/definitions/Info" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/files: get: - summary: "" - description: "Retrieves all Multi-Target Application files " + summary: "Retrieve all uploaded MTA archive files in a space" + description: "Retrieves all Multi-Target Application files" operationId: "getMtaFiles" produces: - "application/json" @@ -56,17 +76,23 @@ paths: required: false type: "string" responses: - 200: + "200": description: "OK" schema: type: "array" items: $ref: "#/definitions/FileMetadata" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] post: - summary: "" - description: "Uploads a Multi Target Application archive or an Extension Descriptor " + summary: "Upload an MTA archive or Extension Descriptor" + description: "Uploads a Multi Target Application archive or an Extension Descriptor" operationId: "uploadMtaFile" consumes: - "multipart/form-data" @@ -84,19 +110,27 @@ paths: required: false type: "string" responses: - 200: - description: "successful operation" - schema: - $ref: "#/definitions/FileMetadata" - 201: + "201": description: "Created" schema: $ref: "#/definitions/FileMetadata" + "400": + description: "Bad Request" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "413": + description: "Request Entity Too Large" + "415": + description: "Unsupported Media Type" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/files/async: post: - summary: "" + summary: "Start an asynchronous MTA archive upload from a URL" description: "Uploads a Multi Target Application archive or an Extension Descriptor\ \ from a remote endpoint" operationId: "startUploadFromUrl" @@ -120,13 +154,23 @@ paths: schema: $ref: "#/definitions/FileUrl" responses: - 202: + "202": description: "Accepted" + "400": + description: "Bad Request" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "413": + description: "Request Entity Too Large" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/files/jobs/{jobId}: get: - summary: "" + summary: "Get the status of an asynchronous URL upload job" description: "Gets the status of an async upload job" operationId: "getAsyncUploadJob" produces: @@ -148,20 +192,28 @@ paths: required: true type: "string" responses: - 200: + "200": description: "OK" schema: $ref: "#/definitions/AsyncUploadResult" - 201: + "201": description: "Created" schema: $ref: "#/definitions/AsyncUploadResult" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/mtas: get: - summary: "" - description: "Retrieves all Multi-Target Applications in a space " + summary: "Retrieve all deployed MTAs in a space" + description: "Retrieves all Multi-Target Applications in a space" operationId: "getMtas" produces: - "application/json" @@ -172,18 +224,24 @@ paths: required: true type: "string" responses: - 200: + "200": description: "OK" schema: type: "array" items: $ref: "#/definitions/Mta" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/mtas/{mtaId}: get: - summary: "" - description: "Retrieves Multi-Target Application in a space " + summary: "Retrieve a specific deployed MTA by ID" + description: "Retrieves Multi-Target Application in a space" operationId: "getMta" produces: - "application/json" @@ -199,52 +257,70 @@ paths: required: true type: "string" responses: - 200: + "200": description: "OK" schema: $ref: "#/definitions/Mta" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/operations: get: - summary: "" - description: "Retrieves Multi-Target Application operations " + summary: "List MTA operations in a space" + description: "Retrieves Multi-Target Application operations" operationId: "getMtaOperations" produces: - "application/json" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space" required: true type: "string" - name: "mtaId" in: "query" + description: "Filter operations by MTA ID" required: false type: "string" - name: "last" in: "query" + description: "Return only the last N operations" required: false type: "integer" format: "int32" - name: "state" in: "query" + description: "Filter operations by state, e.g. RUNNING, FINISHED, ERROR, ABORTED" required: false type: "array" items: type: "string" collectionFormat: "multi" responses: - 200: + "200": description: "OK" schema: type: "array" items: $ref: "#/definitions/Operation" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] post: - summary: "" - description: "Starts execution of a Multi-Target Application operation " + summary: "Start a new MTA operation (deploy, undeploy, blue-green deploy)" + description: "Starts execution of a Multi-Target Application operation" operationId: "startMtaOperation" consumes: - "application/json" @@ -253,147 +329,209 @@ paths: parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space to deploy into" required: true type: "string" - in: "body" name: "body" + description: "Operation descriptor specifying the process type and MTA parameters" required: false schema: $ref: "#/definitions/Operation" responses: - 200: - description: "successful operation" - schema: - $ref: "#/definitions/Operation" - 202: + "202": description: "Accepted" + "400": + description: "Bad Request" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "409": + description: "Conflict" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/operations/{operationId}: get: - summary: "" - description: "Retrieves Multi-Target Application operation " + summary: "Retrieve a specific MTA operation" + description: "Retrieves Multi-Target Application operation" operationId: "getMtaOperation" produces: - "application/json" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space containing the operation" required: true type: "string" - name: "operationId" in: "path" + description: "Process ID of the MTA operation" required: true type: "string" - name: "embed" in: "query" - description: "Adds the specified property in the response body " + description: "Adds the specified property in the response body" required: false type: "string" responses: - 200: + "200": description: "OK" schema: $ref: "#/definitions/Operation" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] post: - summary: "" - description: "Executes a particular action over Multi-Target Application operation " + summary: "Execute an action on an MTA operation" + description: "Executes a particular action over Multi-Target Application operation" operationId: "executeOperationAction" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space containing the operation" required: true type: "string" - name: "operationId" in: "path" + description: "Process ID of the MTA operation" required: true type: "string" - name: "actionId" in: "query" + description: "Action to perform, e.g. abort or retry" required: true type: "string" responses: - 202: + "202": description: "Accepted" + "400": + description: "Bad Request" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/operations/{operationId}/actions: get: - summary: "" - description: "Retrieves available actions for Multi-Target Application operation " + summary: "List available actions for an MTA operation" + description: "Retrieves available actions for Multi-Target Application operation" operationId: "getOperationActions" produces: - "application/json" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space containing the operation" required: true type: "string" - name: "operationId" in: "path" + description: "Process ID of the MTA operation" required: true type: "string" responses: - 200: + "200": description: "OK" schema: type: "array" items: type: "string" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/operations/{operationId}/logs: get: - summary: "" - description: "Retrieves the logs Multi-Target Application operation " + summary: "List log files for an MTA operation" + description: "Retrieves the logs Multi-Target Application operation" operationId: "getMtaOperationLogs" produces: - "application/json" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space containing the operation" required: true type: "string" - name: "operationId" in: "path" + description: "Process ID of the MTA operation" required: true type: "string" responses: - 200: + "200": description: "OK" schema: type: "array" items: $ref: "#/definitions/Log" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] /api/v1/spaces/{spaceGuid}/operations/{operationId}/logs/{logId}/content: get: - summary: "" - description: "Retrieves the log content for Multi-Target Application operation " + summary: "Retrieve the content of a specific operation log" + description: "Retrieves the log content for Multi-Target Application operation" operationId: "getMtaOperationLogContent" produces: - "text/plain" parameters: - name: "spaceGuid" in: "path" + description: "GUID of the CF space containing the operation" required: true type: "string" - name: "operationId" in: "path" + description: "Process ID of the MTA operation" required: true type: "string" - name: "logId" in: "path" + description: "ID of the log file to retrieve" required: true type: "string" responses: - 200: + "200": description: "OK" schema: type: "string" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "404": + description: "Not Found" + "500": + description: "Internal Server Error" security: - oauth2: [] securityDefinitions: diff --git a/multiapps-controller-api/src/main/resources/mtarest_v2.yaml b/multiapps-controller-api/src/main/resources/mtarest_v2.yaml index 86cdc2210e..12f96029f0 100644 --- a/multiapps-controller-api/src/main/resources/mtarest_v2.yaml +++ b/multiapps-controller-api/src/main/resources/mtarest_v2.yaml @@ -13,7 +13,7 @@ schemes: paths: /api/v2/spaces/{spaceGuid}/mtas: get: - summary: "" + summary: "Retrieve all deployed MTAs in a space, filtered by namespace or name" description: "Retrieves all Multi-Target Applications in a given space and namespace" operationId: "getMtas" produces: @@ -35,12 +35,18 @@ paths: required: false type: "string" responses: - 200: + "200": description: "OK" schema: type: "array" items: $ref: "#/definitions/Mta" + "401": + description: "Unauthorized" + "403": + description: "Forbidden" + "500": + description: "Internal Server Error" security: - oauth2: [] securityDefinitions: