diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 405e0397d..3d62fbee5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -13,6 +13,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; @@ -851,6 +852,16 @@ public interface TargetManagement { boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(@NotNull String controllerId, long distributionSetId, @NotNull String targetFilterQuery); + /** + * Finds a single target tags its id. + * + * @param controllerId of the {@link Target} + * @return the found Tag set + * @throws EntityNotFoundException if target with given ID does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Set getTagsByControllerId(@NotEmpty String controllerId); + /** * Creates a list of target meta data entries. * diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index 1a1c970b0..3e046ce2f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import jakarta.persistence.EntityManager; @@ -178,6 +179,13 @@ public class JpaTargetManagement implements TargetManagement { return targetRepository.count(); } + @Override + public Set getTagsByControllerId(@NotEmpty String controllerId) { + // the method has PreAuthorized by itself + return getByControllerID(controllerId).map(JpaTarget.class::cast).map(JpaTarget::getTags) + .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); + } + @Override @Transactional @Retryable(include = { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index d21270703..3c79db17d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -207,11 +207,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest } protected Set getTargetTags(final String controllerId) { - return targetRepository - .findOne(TargetSpecifications.hasControllerId(controllerId)) - .map(JpaTarget.class::cast) - .map(JpaTarget::getTags) - .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); + return targetManagement.getTagsByControllerId(controllerId); } private JpaRollout refresh(final Rollout rollout) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java index 041fcad7c..cf80c8bda 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java @@ -201,7 +201,7 @@ class TargetTagManagementTest extends AbstractJpaIntegrationTest { } @SafeVarargs - private final Collection concat(final Collection... targets) { + private Collection concat(final Collection... targets) { final List result = new ArrayList<>(); Arrays.asList(targets).forEach(result::addAll); return result; diff --git a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java index 453e91d88..89fa7a9f9 100644 --- a/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java +++ b/hawkbit-rest/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java @@ -27,6 +27,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; +import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignments; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes; @@ -715,6 +716,34 @@ public interface MgmtTargetRestApi { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity getInstalledDistributionSet(@PathVariable("targetId") String targetId); + /** + * Gets a paged list of meta data for a target. + * + * @param targetId the ID of the target for the meta data + */ + @Operation(summary = "Return tags for specific target", description = "Get a paged list of tags for a target. Required permission: READ_REPOSITORY") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved"), + @ApiResponse(responseCode = "204", description = "No tags"), + @ApiResponse(responseCode = "401", description = "The request requires user authentication.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", + description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " + + "data volume restriction applies.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", description = "Target not found.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " + + "and the client has to wait another second.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) + }) + @GetMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/tags", produces = { + MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity> getTags(@PathVariable("targetId") String targetId); + /** * Gets a paged list of meta data for a target. * diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTagMapper.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTagMapper.java index 4958217e8..d463403c8 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTagMapper.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTagMapper.java @@ -17,6 +17,8 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTagRequestBodyPut; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetTagRestApi; @@ -30,14 +32,10 @@ import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.rest.data.ResponseList; /** - * A mapper which maps repository model to RESTful model representation and - * back. - * + * A mapper which maps repository model to RESTful model representation and back. */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) final class MgmtTagMapper { - private MgmtTagMapper() { - // Utility class - } static List toResponse(final List targetTags) { final List tagsRest = new ArrayList<>(); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index 94fb29c8d..c0602f50e 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -30,6 +31,7 @@ import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentResponseBody; +import org.eclipse.hawkbit.mgmt.json.model.tag.MgmtTag; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtDistributionSetAssignments; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAttributes; @@ -51,6 +53,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetMetadata; +import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.utils.TenantConfigHelper; import org.springframework.data.domain.Page; @@ -391,6 +394,16 @@ public class MgmtTargetResource implements MgmtTargetRestApi { return ResponseEntity.ok(MgmtTargetMapper.toResponseWithLinks(targetId, action)); } + @Override + public ResponseEntity> getTags(@PathVariable("targetId") String targetId) { + final Set tags = targetManagement.getTagsByControllerId(targetId); + if (tags.isEmpty()) { + return ResponseEntity.noContent().build(); + } else { + return ResponseEntity.ok(MgmtTagMapper.toResponse(tags.stream().toList())); + } + } + @Override public ResponseEntity> getMetadata(@PathVariable("targetId") final String targetId, @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index e47300ceb..2703ee3c8 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -18,6 +18,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -70,6 +71,7 @@ import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetMetadata; +import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.test.util.WithUser; @@ -1895,6 +1897,39 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { return targetManagement.getByControllerID(tA.getControllerId()).get(); } + @Test + void getControllerTagReturnsTagWithNoContent() throws Exception { + // create target with attributes + final String knownTargetId = "targetIdWithNoTags"; + final Target target = testdataFactory.createTarget(knownTargetId); + + // test query target over rest resource + mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/tags")) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNoContent()); + } + + @Test + void getControllerTagReturnsTagWithOk() throws Exception { + // create target with attributes + final String knownTargetId = "targetIdWithTags"; + final Target target = testdataFactory.createTarget(knownTargetId); + final List targetTags = testdataFactory.createTargetTags(2, "tag_getControllerTagReturnsTagWithOk"); + final List tagNames = new ArrayList<>(); + for (final TargetTag targetTag : targetTags) { + targetManagement.toggleTagAssignment(Collections.singletonList(target.getControllerId()), targetTag.getName()); + tagNames.add(targetTag.getName()); + } + + // test query target over rest resource + mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/tags")) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.hasSize(2))) + .andExpect(jsonPath("$.[0].name", in(tagNames))) + .andExpect(jsonPath("$.[1].name", in(tagNames))); + } + @Test @Description("Ensures that the metadata creation through API is reflected by the repository.") void createMetadata() throws Exception {