Extend get module artifacts API by download URL (#1390)

* Introduce request parameter to request download URLs when retrieving list of artifacts for a specific software module.

* Fix DDI integration test by aligning download path to new config

* Make use of mgmt representation mode in sw-module mgmt api

* Changed path

* refactor test names
This commit is contained in:
Michael Herdt
2023-07-12 15:50:59 +02:00
committed by GitHub
parent 593a0bb146
commit 160e44f0ef
9 changed files with 152 additions and 58 deletions

View File

@@ -43,7 +43,7 @@ hawkbit.artifact.url.protocols.download-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-http.protocol=http hawkbit.artifact.url.protocols.download-http.protocol=http
hawkbit.artifact.url.protocols.download-http.port=8080 hawkbit.artifact.url.protocols.download-http.port=8080
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName} hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostnameRequest}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}/download
hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http hawkbit.artifact.url.protocols.md5sum-http.rel=md5sum-http
hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol} hawkbit.artifact.url.protocols.md5sum-http.protocol=${hawkbit.artifact.url.protocols.download-http.protocol}
hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname} hawkbit.artifact.url.protocols.md5sum-http.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
@@ -57,7 +57,7 @@ hawkbit.artifact.url.protocols.download-cdn-http.ip=127.0.0.1
hawkbit.artifact.url.protocols.download-cdn-http.protocol=http hawkbit.artifact.url.protocols.download-cdn-http.protocol=http
hawkbit.artifact.url.protocols.download-cdn-http.port=8080 hawkbit.artifact.url.protocols.download-cdn-http.port=8080
hawkbit.artifact.url.protocols.download-cdn-http.supports=MGMT hawkbit.artifact.url.protocols.download-cdn-http.supports=MGMT
hawkbit.artifact.url.protocols.download-cdn-http.ref={protocol}://{hostnameRequest}:{portRequest}/rest/v1/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName} hawkbit.artifact.url.protocols.download-cdn-http.ref={protocol}://download-cdn.com/artifacts/{artifactFileName}/download
## Download URL Generation - END ## Download URL Generation - END
# Quota - START # Quota - START

View File

@@ -208,11 +208,12 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
contains(artifact.getSha256Hash()))) contains(artifact.getSha256Hash())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.download-http.href", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.download-http.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename()))) + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename()
+ "/download")))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0]._links.md5sum-http.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename() + "/softwaremodules/" + osModuleId + "/artifacts/" + artifact.getFilename()
+ ".MD5SUM"))) + "/download.MD5SUM")))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE))) .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].size", contains(ARTIFACT_SIZE)))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].filename", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1].filename",
contains(artifactSignature.getFilename()))) contains(artifactSignature.getFilename())))
@@ -224,11 +225,12 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
contains(artifactSignature.getSha256Hash()))) contains(artifactSignature.getSha256Hash())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.download-http.href", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.download-http.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename()))) + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename()
+ "/download")))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[1]._links.md5sum-http.href",
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId
+ "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() + "/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename()
+ ".MD5SUM"))) + "/download.MD5SUM")))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version", .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version",
contains(ds.findFirstModuleByType(appType).get().getVersion()))) contains(ds.findFirstModuleByType(appType).get().getVersion())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist()) .andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist())

View File

@@ -276,7 +276,6 @@ public final class MgmtRestConstants {
*/ */
public static final String REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER = "useartifacturlhandler"; public static final String REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER = "useartifacturlhandler";
// constant class, private constructor. // constant class, private constructor.
private MgmtRestConstants() { private MgmtRestConstants() {

View File

@@ -82,7 +82,9 @@ public interface MgmtSoftwareModuleRestApi {
@GetMapping(value = MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING @GetMapping(value = MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING
+ "/{softwareModuleId}/artifacts", produces = { MediaTypes.HAL_JSON_VALUE, + "/{softwareModuleId}/artifacts", produces = { MediaTypes.HAL_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE }) MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtArtifact>> getArtifacts(@PathVariable("softwareModuleId") final Long softwareModuleId); ResponseEntity<List<MgmtArtifact>> getArtifacts(@PathVariable("softwareModuleId") final Long softwareModuleId,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_REPRESENTATION_MODE_DEFAULT) String representationModeParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_USE_ARTIFACT_URL_HANDLER, required = false) final Boolean useArtifactUrlHandler);
/** /**
* Handles the GET request of retrieving a single Artifact meta data * Handles the GET request of retrieving a single Artifact meta data

View File

@@ -124,7 +124,7 @@ public final class MgmtSoftwareModuleMapper {
} }
static void addLinks(final SoftwareModule softwareModule, final MgmtSoftwareModule response) { static void addLinks(final SoftwareModule softwareModule, final MgmtSoftwareModule response) {
response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getArtifacts(response.getModuleId())) response.add(linkTo(methodOn(MgmtSoftwareModuleRestApi.class).getArtifacts(response.getModuleId(), null, null))
.withRel(MgmtRestConstants.SOFTWAREMODULE_V1_ARTIFACT).expand()); .withRel(MgmtRestConstants.SOFTWAREMODULE_V1_ARTIFACT).expand());
response.add(linkTo( response.add(linkTo(
@@ -172,12 +172,4 @@ public final class MgmtSoftwareModuleMapper {
urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand())); urls.forEach(entry -> response.add(Link.of(entry.getRef()).withRel(entry.getRel()).expand()));
} }
static List<MgmtArtifact> artifactsToResponse(final Collection<Artifact> artifacts) {
if (artifacts == null) {
return Collections.emptyList();
}
return new ResponseList<>(
artifacts.stream().map(MgmtSoftwareModuleMapper::toResponse).collect(Collectors.toList()));
}
} }

View File

@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMeta
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMetadataBodyPut; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPost;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleRequestBodyPut;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi;
import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ArtifactManagement;
@@ -39,6 +40,7 @@ import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.rest.data.ResponseList;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -105,7 +107,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
fileName = file.getOriginalFilename(); fileName = file.getOriginalFilename();
} }
try (InputStream in = file.getInputStream()) { try (final InputStream in = file.getInputStream()) {
final Artifact result = artifactManagement.create(new ArtifactUpload(in, softwareModuleId, fileName, final Artifact result = artifactManagement.create(new ArtifactUpload(in, softwareModuleId, fileName,
md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(), md5Sum == null ? null : md5Sum.toLowerCase(), sha1Sum == null ? null : sha1Sum.toLowerCase(),
sha256Sum == null ? null : sha256Sum.toLowerCase(), false, file.getContentType(), file.getSize())); sha256Sum == null ? null : sha256Sum.toLowerCase(), false, file.getContentType(), file.getSize()));
@@ -122,10 +124,30 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
@Override @Override
public ResponseEntity<List<MgmtArtifact>> getArtifacts( public ResponseEntity<List<MgmtArtifact>> getArtifacts(
@PathVariable("softwareModuleId") final Long softwareModuleId) { @PathVariable("softwareModuleId") final Long softwareModuleId, final String representationModeParam,
final Boolean useArtifactUrlHandler) {
final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null);
return ResponseEntity.ok(MgmtSoftwareModuleMapper.artifactsToResponse(module.getArtifacts()));
final boolean isFullMode = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
final List<MgmtArtifact> response = module.getArtifacts().stream().map(artifact -> {
final MgmtArtifact mgmtArtifact = MgmtSoftwareModuleMapper.toResponse(artifact);
if (isFullMode && !module.isDeleted() && Boolean.TRUE.equals(useArtifactUrlHandler)) {
MgmtSoftwareModuleMapper.addLinks(artifact, mgmtArtifact, artifactUrlHandler, systemManagement);
} else if (isFullMode && !module.isDeleted()) {
MgmtSoftwareModuleMapper.addLinks(artifact, mgmtArtifact);
}
return mgmtArtifact;
}).toList();
return ResponseEntity.ok(new ResponseList<>(response));
}
private static MgmtRepresentationMode parseRepresentationMode(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;
});
} }
@Override @Override
@@ -141,7 +163,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
final MgmtArtifact response = MgmtSoftwareModuleMapper.toResponse(module.getArtifact(artifactId).get()); final MgmtArtifact response = MgmtSoftwareModuleMapper.toResponse(module.getArtifact(artifactId).get());
if (!module.isDeleted()) { if (!module.isDeleted()) {
if(useArtifactUrlHandler != null && useArtifactUrlHandler) { if (Boolean.TRUE.equals(useArtifactUrlHandler)) {
MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response, artifactUrlHandler, MgmtSoftwareModuleMapper.addLinks(module.getArtifact(artifactId).get(), response, artifactUrlHandler,
systemManagement); systemManagement);
} else { } else {
@@ -177,7 +199,7 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting);
final Slice<SoftwareModule> findModulesAll; final Slice<SoftwareModule> findModulesAll;
long countModulesAll; final long countModulesAll;
if (rsqlParam != null) { if (rsqlParam != null) {
findModulesAll = softwareModuleManagement.findByRsql(pageable, rsqlParam); findModulesAll = softwareModuleManagement.findByRsql(pageable, rsqlParam);
countModulesAll = ((Page<SoftwareModule>) findModulesAll).getTotalElements(); countModulesAll = ((Page<SoftwareModule>) findModulesAll).getTotalElements();

View File

@@ -41,6 +41,7 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.exception.SpServerError;
import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact; import org.eclipse.hawkbit.mgmt.json.model.artifact.MgmtArtifact;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.Constants; import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
@@ -64,6 +65,8 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -570,14 +573,12 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
final int artifactSize = 5 * 1024; final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize); final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create( final Artifact artifact = artifactManagement
new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
// perform test // perform test
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId()).accept( mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId())
MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.id", equalTo(artifact.getId().intValue()))) .andExpect(jsonPath("$.id", equalTo(artifact.getId().intValue())))
.andExpect(jsonPath("$.size", equalTo(random.length))) .andExpect(jsonPath("$.size", equalTo(random.length)))
@@ -586,29 +587,30 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(jsonPath("$.hashes.sha256", equalTo(artifact.getSha256Hash()))) .andExpect(jsonPath("$.hashes.sha256", equalTo(artifact.getSha256Hash())))
.andExpect(jsonPath("$.providedFilename", equalTo("file1"))) .andExpect(jsonPath("$.providedFilename", equalTo("file1")))
.andExpect(jsonPath("$._links.download.href", .andExpect(jsonPath("$._links.download.href",
equalTo("http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s/download"
+ artifact.getId() + "/download"))) .formatted(sm.getId(), artifact.getId()))))
.andExpect(jsonPath("$._links.self.href", equalTo( .andExpect(jsonPath("$._links.self.href",
"http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact.getId()))); equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s".formatted(sm.getId(),
artifact.getId()))));
} }
@Test @ParameterizedTest
@ValueSource(booleans = { true, false })
@Description("Verifies the listing of one defined artifact assigned to a given software module. That includes the artifact metadata and cdn download links.") @Description("Verifies the listing of one defined artifact assigned to a given software module. That includes the artifact metadata and cdn download links.")
void getArtifactWithCdnDownloadUrl() throws Exception { void getArtifactWithUseArtifactUrlHandlerParameter(final boolean useArtifactUrlHandler) throws Exception {
// prepare data for test // prepare data for test
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final int artifactSize = 5 * 1024; final int artifactSize = 5 * 1024;
final byte random[] = randomBytes(artifactSize); final byte random[] = randomBytes(artifactSize);
final Artifact artifact = artifactManagement.create( final Artifact artifact = artifactManagement
new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
// perform test // perform test
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}?useartifacturlhandler=true", sm.getId(), artifact.getId()).accept( mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}", sm.getId(), artifact.getId())
MediaType.APPLICATION_JSON)) .param("useartifacturlhandler", String.valueOf(useArtifactUrlHandler))
.andDo(MockMvcResultPrinter.print()) .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.id", equalTo(artifact.getId().intValue()))) .andExpect(jsonPath("$.id", equalTo(artifact.getId().intValue())))
.andExpect(jsonPath("$.size", equalTo(random.length))) .andExpect(jsonPath("$.size", equalTo(random.length)))
@@ -616,11 +618,13 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
.andExpect(jsonPath("$.hashes.sha1", equalTo(artifact.getSha1Hash()))) .andExpect(jsonPath("$.hashes.sha1", equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$.hashes.sha256", equalTo(artifact.getSha256Hash()))) .andExpect(jsonPath("$.hashes.sha256", equalTo(artifact.getSha256Hash())))
.andExpect(jsonPath("$.providedFilename", equalTo("file1"))) .andExpect(jsonPath("$.providedFilename", equalTo("file1")))
.andExpect(jsonPath("$._links.download.href", .andExpect(jsonPath("$._links.download.href", useArtifactUrlHandler
equalTo("http://localhost:8080/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" ? equalTo("http://download-cdn.com/artifacts/%s/download".formatted(artifact.getFilename()))
+ artifact.getFilename()))) : equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s/download"
.formatted(sm.getId(), artifact.getId()))))
.andExpect(jsonPath("$._links.self.href", equalTo( .andExpect(jsonPath("$._links.self.href", equalTo(
"http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact.getId()))); "http://localhost/rest/v1/softwaremodules/%s/artifacts/%s".formatted(sm.getId(),
artifact.getId()))));
} }
@Test @Test
@@ -651,7 +655,7 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
} }
@Test @Test
@Description("Verifies the listing of all artifacts assigned to a software module. That includes the artifact metadata and download links.") @Description("Verifies the listing of all artifacts assigned to a software module. That includes the artifact metadata.")
void getArtifacts() throws Exception { void getArtifacts() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
@@ -685,6 +689,52 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
"http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact2.getId()))); "http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artifact2.getId())));
} }
@ParameterizedTest
@ValueSource(booleans = { true, false })
@Description("Verifies the listing of all artifacts assigned to a software module. That includes the artifact metadata and download links.")
void getArtifactsWithUseArtifactUrlHandlerParameter(final boolean useArtifactUrlHandler) throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final int artifactSize = 5 * 1024;
final byte[] random = randomBytes(artifactSize);
final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, artifactSize));
final Artifact artifact2 = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file2", false, artifactSize));
mvc.perform(get("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId())
.param("representation", MgmtRepresentationMode.FULL.toString())
.param("useartifacturlhandler", String.valueOf(useArtifactUrlHandler))
.accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("$.[0].id", equalTo(artifact.getId().intValue())))
.andExpect(jsonPath("$.[0].size", equalTo(random.length)))
.andExpect(jsonPath("$.[0].hashes.md5", equalTo(artifact.getMd5Hash())))
.andExpect(jsonPath("$.[0].hashes.sha1", equalTo(artifact.getSha1Hash())))
.andExpect(jsonPath("$.[0].hashes.sha256", equalTo(artifact.getSha256Hash())))
.andExpect(jsonPath("$.[0].providedFilename", equalTo("file1")))
.andExpect(jsonPath("$.[0]._links.download.href", useArtifactUrlHandler
? equalTo("http://download-cdn.com/artifacts/%s/download".formatted(artifact.getFilename()))
: equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s/download"
.formatted(sm.getId(), artifact.getId()))))
.andExpect(jsonPath("$.[0]._links.self.href",
equalTo("http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/"
+ artifact.getId())))
.andExpect(jsonPath("$.[1].id", equalTo(artifact2.getId().intValue())))
.andExpect(jsonPath("$.[1].hashes.md5", equalTo(artifact2.getMd5Hash())))
.andExpect(jsonPath("$.[1].hashes.sha1", equalTo(artifact2.getSha1Hash())))
.andExpect(jsonPath("$.[1].hashes.sha256", equalTo(artifact2.getSha256Hash())))
.andExpect(jsonPath("$.[1].providedFilename", equalTo("file2")))
.andExpect(jsonPath("$.[1]._links.download.href", useArtifactUrlHandler
? equalTo("http://download-cdn.com/artifacts/%s/download".formatted(artifact2.getFilename()))
: equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s/download"
.formatted(sm.getId(), artifact2.getId()))))
.andExpect(jsonPath("$.[1]._links.self.href",
equalTo("http://localhost/rest/v1/softwaremodules/%s/artifacts/%s".formatted(sm.getId(),
artifact2.getId()))));
}
@Test @Test
@Description("Verifies that the system refuses unsupported request types and answers as defined to them, e.g. NOT FOUND on a non existing resource. Or a HTTP POST for updating a resource results in METHOD NOT ALLOWED etc.") @Description("Verifies that the system refuses unsupported request types and answers as defined to them, e.g. NOT FOUND on a non existing resource. Or a HTTP POST for updating a resource results in METHOD NOT ALLOWED etc.")
void invalidRequestsOnArtifactResource() throws Exception { void invalidRequestsOnArtifactResource() throws Exception {

View File

@@ -271,6 +271,10 @@ include::{snippets}/softwaremodules/get-artifacts/http-request.adoc[]
include::{snippets}/softwaremodules/get-artifacts/path-parameters.adoc[] include::{snippets}/softwaremodules/get-artifacts/path-parameters.adoc[]
==== Request query parameter
include::{snippets}/softwaremodules/get-artifacts-with-parameters/request-parameters.adoc[]
=== Response (Status 200) === Response (Status 200)
==== Response fields ==== Response fields

View File

@@ -28,6 +28,7 @@ import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRepresentationMode;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.Constants; import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.Artifact;
@@ -249,7 +250,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
public void getArtifacts() throws Exception { public void getArtifacts() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0)); artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
@@ -278,13 +279,36 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
fieldWithPath("[]._links.self").ignored()))); fieldWithPath("[]._links.self").ignored())));
} }
@Test
@Description("Handles the GET request of retrieving all meta data of artifacts assigned to a software module (in full representation mode including a download URL by the artifact provider). Required Permission: "
+ SpPermission.READ_REPOSITORY)
public void getArtifactsWithParameters() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte[] random = RandomStringUtils.random(5).getBytes();
artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform(
get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts", sm.getId())
.param("representation", MgmtRepresentationMode.FULL.toString())
.param("useartifacturlhandler", Boolean.TRUE.toString()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON))
.andDo(this.document.document(requestParameters(
parameterWithName("representation").description(MgmtApiModelProperties.REPRESENTATION_MODE)
.optional(),
parameterWithName("useartifacturlhandler")
.description(MgmtApiModelProperties.ARTIFACT_DOWNLOAD_USE_URL_HANDLER).optional())));
}
@Test @Test
@Description("Handles POST request for artifact upload. Required Permission: " + SpPermission.CREATE_REPOSITORY) @Description("Handles POST request for artifact upload. Required Permission: " + SpPermission.CREATE_REPOSITORY)
public void postArtifact() throws Exception { public void postArtifact() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
// create test file // create test file
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random); final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random);
mockMvc.perform( mockMvc.perform(
@@ -320,7 +344,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random); final MockMultipartFile file = new MockMultipartFile("file", "origFilename", null, random);
mockMvc.perform( mockMvc.perform(
@@ -342,7 +366,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
public void deleteArtifact() throws Exception { public void deleteArtifact() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
@@ -361,7 +385,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
public void getArtifact() throws Exception { public void getArtifact() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
@@ -397,19 +421,18 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
public void getArtifactWithParameters() throws Exception { public void getArtifactWithParameters() throws Exception {
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));
mockMvc.perform( mockMvc.perform(
get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts/{artifactId}", get(MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING + "/{softwareModuleId}/artifacts/{artifactId}",
sm.getId(), artifact.getId()).param("useartifacturlhandler", "true")) sm.getId(), artifact.getId()).param("useartifacturlhandler", "true"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON)) .andExpect(content().contentType(MediaTypes.HAL_JSON))
.andDo(this.document.document( .andDo(this.document.document(requestParameters(parameterWithName("useartifacturlhandler")
requestParameters( .description(MgmtApiModelProperties.ARTIFACT_DOWNLOAD_USE_URL_HANDLER).optional())));
parameterWithName("useartifacturlhandler").description(MgmtApiModelProperties.ARTIFACT_DOWNLOAD_USE_URL_HANDLER))));
} }
@Test @Test
@@ -419,7 +442,7 @@ public class SoftwaremodulesDocumentationTest extends AbstractApiRestDocumentati
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs(); final SoftwareModule sm = testdataFactory.createSoftwareModuleOs();
final byte random[] = RandomStringUtils.random(5).getBytes(); final byte[] random = RandomStringUtils.random(5).getBytes();
final Artifact artifact = artifactManagement final Artifact artifact = artifactManagement
.create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0)); .create(new ArtifactUpload(new ByteArrayInputStream(random), sm.getId(), "file1", false, 0));