diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtActionRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtActionRestApi.java
index 8ebcd4137..c8edc7636 100644
--- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtActionRestApi.java
+++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtActionRestApi.java
@@ -14,6 +14,7 @@ import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -54,4 +55,14 @@ public interface MgmtActionRestApi {
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE_DEFAULT) String representationModeParam);
+ /**
+ * Handles the GET request of retrieving a specific Action by actionId
+ *
+ * @param actionId
+ *
+ * @return the action
+ */
+ @GetMapping(value = "/{actionId}", produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
+ ResponseEntity getAction(
+ @PathVariable("actionId") Long actionId);
}
\ No newline at end of file
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResource.java
index 7a5326dc1..55390ac55 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResource.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResource.java
@@ -14,6 +14,7 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtActionRestApi;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
+import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.Action;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,16 +56,29 @@ public class MgmtActionResource implements MgmtActionRestApi {
totalActionCount = this.deploymentManagement.countActionsAll();
}
- final MgmtRepresentationMode repMode = MgmtRepresentationMode.fromValue(representationModeParam)
- .orElseGet(() -> {
- // no need for a 400, just apply a safe fallback
- LOG.warn("Received an invalid representation mode: {}", representationModeParam);
- return MgmtRepresentationMode.COMPACT;
- });
+ final MgmtRepresentationMode repMode = getRepresentationModeFromString(representationModeParam);
return ResponseEntity
.ok(new PagedList<>(MgmtActionMapper.toResponse(actions.getContent(), repMode), totalActionCount));
}
+ @Override
+ public ResponseEntity getAction(final Long actionId) {
+
+ final Action action = deploymentManagement.findAction(actionId)
+ .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
+
+ return ResponseEntity.ok(MgmtActionMapper.toResponse(action, MgmtRepresentationMode.FULL));
+ }
+
+ private MgmtRepresentationMode getRepresentationModeFromString(final String representationModeParam) {
+ return MgmtRepresentationMode.fromValue(representationModeParam)
+ .orElseGet(() -> {
+ // no need for a 400, just apply a safe fallback
+ LOG.warn("Received an invalid representation mode: {}", representationModeParam);
+ return MgmtRepresentationMode.COMPACT;
+ });
+ }
+
}
diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResourceTest.java
index bb2aec6b8..0641b14cf 100644
--- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResourceTest.java
+++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtActionResourceTest.java
@@ -54,10 +54,14 @@ class MgmtActionResourceTest extends AbstractManagementApiIntegrationTest {
private static final String JSON_PATH_FIELD_SIZE = ".size";
private static final String JSON_PATH_FIELD_TOTAL = ".total";
+ private static final String JSON_PATH_FIELD_ID = ".id";
+
private static final String JSON_PATH_PAGED_LIST_CONTENT = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTENT;
private static final String JSON_PATH_PAGED_LIST_SIZE = JSON_PATH_ROOT + JSON_PATH_FIELD_SIZE;
private static final String JSON_PATH_PAGED_LIST_TOTAL = JSON_PATH_ROOT + JSON_PATH_FIELD_TOTAL;
+ private static final String JSON_PATH_ACTION_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID;
+
@Test
@Description("Verifies that actions can be filtered based on action status.")
void filterActionsByStatus() throws Exception {
@@ -381,10 +385,6 @@ class MgmtActionResourceTest extends AbstractManagementApiIntegrationTest {
final List actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
final Long actionId = actions.get(0).getId();
- // ensure specific action cannot be accessed via the actions resource
- mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + actionId))
- .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound());
-
// not allowed methods
mvc.perform(post(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isMethodNotAllowed());
@@ -394,6 +394,28 @@ class MgmtActionResourceTest extends AbstractManagementApiIntegrationTest {
.andExpect(status().isMethodNotAllowed());
}
+ @Test
+ @Description("Verifies that the correct action is returned")
+ void shouldRetrieveCorrectActionById() throws Exception {
+ final String knownTargetId = "targetId";
+
+ final List actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
+ final Long actionId = actions.get(0).getId();
+
+ mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + actionId))
+ .andDo(MockMvcResultPrinter.print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(JSON_PATH_ACTION_ID, equalTo(actionId.intValue())));
+ }
+
+ @Test
+ @Description("Verifies that NOT_FOUND is returned when there is no such action.")
+ void requestActionThatDoesNotExistsLeadsToNotFound() throws Exception {
+ mvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/" + 101))
+ .andDo(MockMvcResultPrinter.print())
+ .andExpect(status().isNotFound());
+ }
+
private List generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId) {
return generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId, null, null, null);
}
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/actions-api-guide.adoc b/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/actions-api-guide.adoc
index a4ca2a594..1c19c5a5c 100644
--- a/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/actions-api-guide.adoc
+++ b/hawkbit-rest/hawkbit-rest-docs/src/main/asciidoc/actions-api-guide.adoc
@@ -65,6 +65,44 @@ include::../errors/406.adoc[]
include::../errors/429.adoc[]
|===
+== GET /rest/v1/actions/{actionId}
+
+=== Implementation notes
+
+Handles the GET request of retrieving a single action within Hawkbit by actionId.
+
+=== Get single action
+
+==== CURL
+
+include::{snippets}/actions/get-action/curl-request.adoc[]
+
+==== Request URL
+
+A `GET` request is used to access a single action
+
+include::{snippets}/actions/get-action/http-request.adoc[]
+
+=== Response (Status 200)
+
+==== Response fields
+
+include::{snippets}/actions/get-action/response-fields.adoc[]
+
+==== Response example
+
+include::{snippets}/actions/get-action/http-response.adoc[]
+
+|===
+| HTTP Status Code | Reason | Response Model
+
+include::../errors/400.adoc[]
+include::../errors/401.adoc[]
+include::../errors/404.adoc[]
+include::../errors/406.adoc[]
+include::../errors/429.adoc[]
+|===
+
== Additional content
[[error-body]]
diff --git a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/ActionResourceDocumentationTest.java b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/ActionResourceDocumentationTest.java
index 512804fe6..b8fca2d8a 100644
--- a/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/ActionResourceDocumentationTest.java
+++ b/hawkbit-rest/hawkbit-rest-docs/src/test/java/org/eclipse/hawkbit/rest/mgmt/documentation/ActionResourceDocumentationTest.java
@@ -11,8 +11,7 @@ package org.eclipse.hawkbit.rest.mgmt.documentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
-import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
-import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
+import static org.springframework.restdocs.request.RequestDocumentation.*;
import static org.springframework.restdocs.snippet.Attributes.key;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -103,6 +102,47 @@ public class ActionResourceDocumentationTest extends AbstractApiRestDocumentatio
parameterWithName("representation").description(MgmtApiModelProperties.REPRESENTATION_MODE))));
}
+ @Test
+ @Description("Handles the GET request of retrieving a specific action.")
+ public void getAction() throws Exception {
+ final Action action = generateRolloutActionForTarget(targetId);
+ provideCodeFeedback(action, 200);
+
+ assertThat(deploymentManagement.findAction(action.getId()).get().getActionType())
+ .isEqualTo(Action.ActionType.FORCED);
+
+ mockMvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "/{actionId}", action.getId()))
+ .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
+ .andDo(this.document.document(
+ pathParameters(parameterWithName("actionId").description(ApiModelPropertiesGeneric.ITEM_ID)),
+ responseFields(fieldWithPath("createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
+ fieldWithPath("createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
+ fieldWithPath("id").description(MgmtApiModelProperties.ACTION_ID),
+ fieldWithPath("lastModifiedBy").description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY)
+ .type("String"),
+ fieldWithPath("lastModifiedAt").description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT)
+ .type("String"),
+ fieldWithPath("type").description(MgmtApiModelProperties.ACTION_TYPE)
+ .attributes(key("value").value("['update', 'cancel']")),
+ fieldWithPath("forceType").description(MgmtApiModelProperties.ACTION_FORCE_TYPE)
+ .attributes(key("value").value("['forced', 'soft', 'timeforced']")),
+ fieldWithPath("status").description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
+ .attributes(key("value").value("['finished', 'pending']")),
+ fieldWithPath("detailStatus").description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
+ .attributes(key("value").value(
+ "['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
+ optionalRequestFieldWithPath("lastStatusCode")
+ .description(MgmtApiModelProperties.ACTION_LAST_STATUS_CODE).type("Integer"),
+ fieldWithPath("rollout").description(MgmtApiModelProperties.ACTION_ROLLOUT),
+ fieldWithPath("rolloutName").description(MgmtApiModelProperties.ACTION_ROLLOUT_NAME),
+ fieldWithPath("_links.self").ignored(),
+ fieldWithPath("_links.distributionset").description(MgmtApiModelProperties.LINK_TO_DS),
+ fieldWithPath("_links.status")
+ .description(MgmtApiModelProperties.LINKS_ACTION_STATUSES),
+ fieldWithPath("_links.rollout").description(MgmtApiModelProperties.LINK_TO_ROLLOUT),
+ fieldWithPath("_links.target").description(MgmtApiModelProperties.LINK_TO_TARGET))));
+ }
+
private Action generateRolloutActionForTarget(final String knownControllerId) throws Exception {
return generateActionForTarget(knownControllerId, true, false, null, null, null, true);
}