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:
Stefan Behl
2023-01-10 17:19:05 +01:00
committed by GitHub
parent 27872282f5
commit d7796121d1
19 changed files with 1046 additions and 37 deletions

View File

@@ -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[]

View File

@@ -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\"";
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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