Add resource collection /rest/v1/actions to Management REST API (#1299)
* Initial commit Signed-off-by: Stefan Behl <stefan.behl@bosch.io> * Added filtering by RSQL Signed-off-by: Stefan Behl <stefan.behl@bosch.io> * Support for filtering actions by distribution set, target, rollout * Added REST docs * Fixed REST docs * Introduce a config property which allows to disable the actions endpoint * Introduce representation mode parameter * Adapt REST docs * Incorporate review findings * Adapt REST docs * Improve unit tests * Minor improvements * Fix REST docs * Fix REST docs * Fix PR review findings Signed-off-by: Stefan Behl <stefan.behl@bosch.io>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
:doctype: book
|
||||
:icons: font
|
||||
:source-highlighter: highlightjs
|
||||
:toc: macro
|
||||
:toclevels: 1
|
||||
:sectlinks:
|
||||
:linkattrs:
|
||||
|
||||
[[actions]]
|
||||
= Actions
|
||||
|
||||
toc::[]
|
||||
|
||||
== GET /rest/v1/actions
|
||||
|
||||
=== Implementation notes
|
||||
|
||||
Handles the GET request of retrieving all actions within Hawkbit. Required permission: READ_TARGET
|
||||
|
||||
=== Get paged list of actions
|
||||
|
||||
==== CURL
|
||||
|
||||
include::{snippets}/actions/get-actions/curl-request.adoc[]
|
||||
|
||||
==== Request URL
|
||||
|
||||
A `GET` request is used to access the actions
|
||||
|
||||
include::{snippets}/actions/get-actions/http-request.adoc[]
|
||||
|
||||
==== Request query parameter
|
||||
|
||||
include::{snippets}/actions/get-actions-with-parameters/request-parameters.adoc[]
|
||||
|
||||
==== Request parameter example
|
||||
|
||||
include::{snippets}/actions/get-actions-with-parameters/http-request.adoc[]
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
==== Response fields
|
||||
|
||||
include::{snippets}/actions/get-actions/response-fields.adoc[]
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/actions/get-actions/http-response.adoc[]
|
||||
|
||||
==== Response example for representation mode 'full'
|
||||
|
||||
include::{snippets}/actions/get-actions-with-parameters/http-response.adoc[]
|
||||
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== Additional content
|
||||
|
||||
[[error-body]]
|
||||
=== Error body
|
||||
|
||||
include::../errors/error-response-body.adoc[]
|
||||
@@ -33,6 +33,7 @@ public final class MgmtApiModelProperties {
|
||||
public static final String LINK_TO_OPTIONAL_SMT = "Link to optional software modules types in this distribution set type.";
|
||||
public static final String LINK_TO_ROLLOUT = "The link to the rollout.";
|
||||
public static final String LINK_TO_TARGET_TYPE = "The link to the target type.";
|
||||
public static final String LINK_TO_TARGET = "The link to the target.";
|
||||
|
||||
// software module types
|
||||
public static final String SMT_TYPE = "The type of the software module identified by its key.";
|
||||
@@ -158,6 +159,8 @@ public final class MgmtApiModelProperties {
|
||||
|
||||
public static final String ACTION_EXECUTION_STATUS = "Status of action.";
|
||||
|
||||
public static final String ACTION_DETAIL_STATUS = "Detailed status of action.";
|
||||
|
||||
public static final String ACTION_LIST = "List of actions.";
|
||||
|
||||
public static final String ACTION_WEIGHT = "Weight of the action showing the importance of the update.";
|
||||
@@ -245,4 +248,7 @@ public final class MgmtApiModelProperties {
|
||||
public static final String CONFIG_PARAM = "The name of the configuration parameter.";
|
||||
|
||||
public static final String DS_NEW_ASSIGNED_ACTIONS = "The newly created actions as a result of this assignment";
|
||||
|
||||
public static final String REPRESENTATION_MODE = "The representation mode. Can be \"full\" or \"compact\". Defaults to \"compact\"";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
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.snippet.Attributes.key;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.repository.ActionStatusFields;
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
|
||||
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
|
||||
import org.eclipse.hawkbit.rest.documentation.MgmtApiModelProperties;
|
||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.restdocs.payload.JsonFieldType;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
|
||||
/**
|
||||
* Documentation generation for Management API for {@link Action}.
|
||||
*/
|
||||
@Feature("Spring Rest Docs Tests - Action")
|
||||
@Story("Action Resource")
|
||||
public class ActionResourceDocumentationTest extends AbstractApiRestDocumentation {
|
||||
|
||||
private final String targetId = "target137";
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return "actions";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all actions. Required Permission: READ_TARGET.")
|
||||
public void getActions() throws Exception {
|
||||
enableMultiAssignments();
|
||||
generateRolloutActionForTarget(targetId);
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING)).andExpect(status().isOk())
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(responseFields(
|
||||
fieldWithPath("size").type(JsonFieldType.NUMBER).description(ApiModelPropertiesGeneric.SIZE),
|
||||
fieldWithPath("total").description(ApiModelPropertiesGeneric.TOTAL_ELEMENTS),
|
||||
fieldWithPath("content[]").description(MgmtApiModelProperties.ACTION_LIST),
|
||||
fieldWithPath("content[].createdBy").description(ApiModelPropertiesGeneric.CREATED_BY),
|
||||
fieldWithPath("content[].createdAt").description(ApiModelPropertiesGeneric.CREATED_AT),
|
||||
fieldWithPath("content[].lastModifiedBy")
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_BY).type("String"),
|
||||
fieldWithPath("content[].lastModifiedAt")
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("String"),
|
||||
fieldWithPath("content[].type").description(MgmtApiModelProperties.ACTION_TYPE)
|
||||
.attributes(key("value").value("['update', 'cancel']")),
|
||||
|
||||
fieldWithPath("content[].status").description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
|
||||
.attributes(key("value").value("['finished', 'pending']")),
|
||||
fieldWithPath("content[].detailStatus").description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
|
||||
.attributes(key("value").value(
|
||||
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
|
||||
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
|
||||
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
|
||||
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
|
||||
fieldWithPath("content[].rollout").description(MgmtApiModelProperties.ACTION_ROLLOUT),
|
||||
fieldWithPath("content[].rolloutName")
|
||||
.description(MgmtApiModelProperties.ACTION_ROLLOUT_NAME))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all actions based on parameters. Required Permission: READ_TARGET.")
|
||||
public void getActionsWithParameters() throws Exception {
|
||||
final Action action = generateRolloutActionForTarget(targetId);
|
||||
|
||||
mockMvc.perform(
|
||||
get(MgmtRestConstants.ACTION_V1_REQUEST_MAPPING + "?limit=10&sort=id:ASC&offset=0&q=rollout.id=="
|
||||
+ action.getRollout().getId() + "&representation=full"))
|
||||
.andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(requestParameters(
|
||||
parameterWithName("limit").attributes(key("type").value("query"))
|
||||
.description(ApiModelPropertiesGeneric.LIMIT),
|
||||
parameterWithName("sort").description(ApiModelPropertiesGeneric.SORT),
|
||||
parameterWithName("offset").description(ApiModelPropertiesGeneric.OFFSET),
|
||||
parameterWithName("q").description(ApiModelPropertiesGeneric.FIQL),
|
||||
parameterWithName("representation").description(MgmtApiModelProperties.REPRESENTATION_MODE))));
|
||||
}
|
||||
|
||||
private Action generateRolloutActionForTarget(final String knownControllerId) throws Exception {
|
||||
return generateActionForTarget(knownControllerId, true, false, null, null, null, true);
|
||||
}
|
||||
|
||||
private Action generateActionForTarget(final String knownControllerId, final boolean inSync,
|
||||
final boolean timeforced, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
|
||||
final String maintenanceWindowTimeZone, final boolean createRollout) throws Exception {
|
||||
final PageRequest pageRequest = PageRequest.of(0, 1, Direction.ASC, ActionStatusFields.ID.getFieldName());
|
||||
|
||||
createTargetByGivenNameWithAttributes(knownControllerId, inSync, timeforced, createDistributionSet(),
|
||||
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone, createRollout);
|
||||
|
||||
final List<Action> actions = deploymentManagement.findActionsAll(pageRequest).getContent();
|
||||
|
||||
assertThat(actions).hasSize(1);
|
||||
return actions.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -155,6 +155,8 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
.description(MgmtApiModelProperties.ROLLOUT_LINKS_APPROVE));
|
||||
allFieldDescriptor.add(
|
||||
fieldWithPath(arrayPrefix + "_links.deny").description(MgmtApiModelProperties.ROLLOUT_LINKS_DENY));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.distributionset")
|
||||
.description(MgmtApiModelProperties.LINK_TO_DS));
|
||||
}
|
||||
|
||||
return new DocumenationResponseFieldsSnippet(allFieldDescriptor);
|
||||
|
||||
@@ -237,6 +237,10 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
fieldWithPath("content[].status")
|
||||
.description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
|
||||
.attributes(key("value").value("['finished', 'pending']")),
|
||||
fieldWithPath("content[].detailStatus")
|
||||
.description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
|
||||
.attributes(key("value").value(
|
||||
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
|
||||
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
|
||||
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
|
||||
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
|
||||
@@ -270,11 +274,15 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.description(ApiModelPropertiesGeneric.LAST_MODIFIED_AT).type("String"),
|
||||
fieldWithPath("content[].type").description(MgmtApiModelProperties.ACTION_TYPE)
|
||||
.attributes(key("value").value("['update', 'cancel']")),
|
||||
|
||||
fieldWithPath("content[].status")
|
||||
.description(MgmtApiModelProperties.ACTION_EXECUTION_STATUS)
|
||||
.attributes(key("value").value("['finished', 'pending']")),
|
||||
fieldWithPath("content[]._links").description(MgmtApiModelProperties.LINK_TO_ACTION),
|
||||
fieldWithPath("content[].detailStatus")
|
||||
.description(MgmtApiModelProperties.ACTION_DETAIL_STATUS)
|
||||
.attributes(key("value").value(
|
||||
"['finished', 'error', 'running', 'warning', 'scheduled', 'canceling', 'canceled', 'download', 'downloaded', 'retrieved', 'cancel_rejected']")),
|
||||
fieldWithPath("content[]._links.self")
|
||||
.description(MgmtApiModelProperties.LINK_TO_ACTION),
|
||||
fieldWithPath("content[].id").description(MgmtApiModelProperties.ACTION_ID),
|
||||
fieldWithPath("content[].weight").description(MgmtApiModelProperties.ACTION_WEIGHT),
|
||||
fieldWithPath("content[].maintenanceWindow")
|
||||
@@ -363,13 +371,17 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.type("String"),
|
||||
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']")),
|
||||
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.rollout").description(MgmtApiModelProperties.LINK_TO_ROLLOUT),
|
||||
fieldWithPath("_links.target").description(MgmtApiModelProperties.LINK_TO_TARGET))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -401,6 +413,9 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.type("String"),
|
||||
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']")),
|
||||
fieldWithPath("maintenanceWindow")
|
||||
.description(MgmtApiModelProperties.MAINTENANCE_WINDOW),
|
||||
fieldWithPath("maintenanceWindow.schedule")
|
||||
@@ -414,7 +429,8 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
fieldWithPath("_links.self").ignored(),
|
||||
fieldWithPath("_links.distributionset").description(MgmtApiModelProperties.LINK_TO_DS),
|
||||
fieldWithPath("_links.status")
|
||||
.description(MgmtApiModelProperties.LINKS_ACTION_STATUSES))));
|
||||
.description(MgmtApiModelProperties.LINKS_ACTION_STATUSES),
|
||||
fieldWithPath("_links.target").description(MgmtApiModelProperties.LINK_TO_TARGET))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -451,10 +467,14 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
.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']")),
|
||||
fieldWithPath("_links.self").ignored(),
|
||||
fieldWithPath("_links.distributionset").description(MgmtApiModelProperties.LINK_TO_DS),
|
||||
fieldWithPath("_links.status")
|
||||
.description(MgmtApiModelProperties.LINKS_ACTION_STATUSES))));
|
||||
.description(MgmtApiModelProperties.LINKS_ACTION_STATUSES),
|
||||
fieldWithPath("_links.target").description(MgmtApiModelProperties.LINK_TO_TARGET))));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user