SDK: Add unified Link download (using auth contexts) (#2270)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-02-12 18:32:18 +02:00
committed by GitHub
parent 2f418cc65b
commit 97027de9a8
9 changed files with 295 additions and 329 deletions

View File

@@ -35,14 +35,11 @@ public class DmfController {
private static final String DEPLOYMENT_BASE_LINK = "deploymentBase";
private static final String CONFIRMATION_BASE_LINK = "confirmationBase";
private final String tenantId;
private final String controllerId;
private final Tenant tenant;
private final Controller controller;
private final UpdateHandler updateHandler;
private final DmfSender dmfSender;
// configuration
private final boolean downloadAuthenticationEnabled;
@Getter
private final Map<String, String> attributes = new HashMap<>();
@@ -64,13 +61,20 @@ public class DmfController {
final Tenant tenant, final Controller controller,
final UpdateHandler updateHandler,
final DmfSender dmfSender) {
this.tenantId = tenant.getTenantId();
downloadAuthenticationEnabled = tenant.isDownloadAuthenticationEnabled();
this.controllerId = controller.getControllerId();
this.tenant = tenant;
this.controller = controller;
this.updateHandler = updateHandler == null ? UpdateHandler.SKIP : updateHandler;
this.dmfSender = dmfSender;
}
public String getTenantId() {
return tenant.getTenantId();
}
public String getControllerId() {
return controller.getControllerId();
}
public void start(ScheduledExecutorService executorService) {
stop();
this.executorService = executorService;
@@ -95,18 +99,18 @@ public class DmfController {
}
public void processUpdate(final EventTopic actionType, final DmfDownloadAndUpdateRequest updateRequest) {
log.info(LOG_PREFIX + "Processing update for action {} .", getTenantId(), controllerId, updateRequest.getActionId());
log.info(LOG_PREFIX + "Processing update for action {} .", getTenantId(), getControllerId(), updateRequest.getActionId());
executorService.submit(updateHandler.getUpdateProcessor(this, actionType, updateRequest));
}
public void sendFeedback(final UpdateStatus updateStatus) {
log.info(LOG_PREFIX + "Sending UPDATE_ACTION_STATUS for action : {}", getTenantId(), controllerId, currentActionId);
dmfSender.sendFeedback(tenantId, currentActionId, updateStatus);
log.info(LOG_PREFIX + "Sending UPDATE_ACTION_STATUS for action : {}", getTenantId(), getControllerId(), currentActionId);
dmfSender.sendFeedback(getTenantId(), currentActionId, updateStatus);
}
public void sendUpdateAttributes() {
log.info(LOG_PREFIX + "Sending UPDATE_ATTRIBUTES", getTenantId(), controllerId);
dmfSender.updateAttributes(tenantId, controllerId, DmfUpdateMode.MERGE, attributes);
log.info(LOG_PREFIX + "Sending UPDATE_ATTRIBUTES", getTenantId(), getControllerId());
dmfSender.updateAttributes(getTenantId(), getControllerId(), DmfUpdateMode.MERGE, attributes);
}
public void setAttribute(final String key, final String value) {

View File

@@ -13,8 +13,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -23,26 +21,20 @@ import java.util.HexFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifact;
import org.eclipse.hawkbit.dmf.json.model.DmfArtifactHash;
import org.eclipse.hawkbit.dmf.json.model.DmfDownloadAndUpdateRequest;
import org.eclipse.hawkbit.dmf.json.model.DmfSoftwareModule;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.spi.ArtifactHandler;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.hateoas.Link;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Update handler provide plug-in endpoint allowing for customization of the update processing.
@@ -134,20 +126,12 @@ public interface UpdateHandler {
" and hashes " + art.getHashes() + " ...")
.toList()));
log.info(LOG_PREFIX + "Start download", dmfController.getTenantId(), dmfController.getControllerId());
log.info(LOG_PREFIX + "Start download", dmfController.getTenant().getTenantId(), dmfController.getControllerId());
final List<UpdateStatus> updateStatusList = new ArrayList<>();
modules.forEach(module -> module.getArtifacts().forEach(artifact -> {
if (dmfController.isDownloadAuthenticationEnabled()) {
handleArtifact(
updateRequest.getTargetSecurityToken(),
updateStatusList, artifact);
} else {
handleArtifact(null, updateStatusList, artifact);
}
}));
modules.forEach(module -> module.getArtifacts().forEach(artifact -> handleArtifact(updateStatusList, artifact)));
log.info(LOG_PREFIX + "Download complete.", dmfController.getTenantId(), dmfController.getControllerId());
log.info(LOG_PREFIX + "Download complete.", dmfController.getTenant().getTenantId(), dmfController.getControllerId());
final List<String> messages = new LinkedList<>();
messages.add("Download complete.");
@@ -163,7 +147,7 @@ public interface UpdateHandler {
* may get the {@link #downloads} map and apply them
*/
protected UpdateStatus update() {
log.info(LOG_PREFIX + "Updated", dmfController.getTenantId(), dmfController.getControllerId());
log.info(LOG_PREFIX + "Updated", dmfController.getTenant().getTenantId(), dmfController.getControllerId());
return new UpdateStatus(DmfActionStatus.FINISHED, List.of("Update complete."));
}
@@ -177,11 +161,11 @@ public interface UpdateHandler {
Files.delete(path);
} catch (final IOException e) {
log.warn(LOG_PREFIX + "Failed to cleanup {}",
dmfController.getTenantId(), dmfController.getControllerId(),
dmfController.getTenant().getTenantId(), dmfController.getControllerId(),
path.toFile().getAbsolutePath(), e);
}
});
log.debug(LOG_PREFIX + "Cleaned up", dmfController.getTenantId(), dmfController.getControllerId());
log.debug(LOG_PREFIX + "Cleaned up", dmfController.getTenant().getTenantId(), dmfController.getControllerId());
}
private static String hideTokenDetails(final String targetToken) {
@@ -200,23 +184,6 @@ public interface UpdateHandler {
return targetToken.substring(0, 2) + "***" + targetToken.substring(targetToken.length() - 2);
}
private static CloseableHttpClient createHttpClientThatAcceptsAllServerCerts()
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
return HttpClients
.custom()
.setConnectionManager(
PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(
new SSLConnectionSocketFactory(
SSLContextBuilder
.create()
.loadTrustMaterial(null, (chain, authType) -> true)
.build()))
.build()
)
.build();
}
private static Validator sizeValidator(final long size) {
return new Validator() {
@@ -290,98 +257,70 @@ public interface UpdateHandler {
}
private void handleArtifact(
final String targetToken,
final List<UpdateStatus> status, final DmfArtifact artifact) {
if (artifact.getUrls().containsKey("HTTPS")) {
status.add(downloadUrl(artifact.getUrls().get("HTTPS"), targetToken,
artifact.getHashes(), artifact.getSize()));
status.add(downloadUrl(Link.of(artifact.getUrls().get("HTTPS")), artifact.getHashes(), artifact.getSize()));
} else if (artifact.getUrls().containsKey("HTTP")) {
status.add(downloadUrl(artifact.getUrls().get("HTTP"), targetToken,
artifact.getHashes(), artifact.getSize()));
status.add(downloadUrl(Link.of(artifact.getUrls().get("HTTP")), artifact.getHashes(), artifact.getSize()));
}
}
private UpdateStatus downloadUrl(
final String url, final String targetToken,
final DmfArtifactHash hash, final long size) {
private UpdateStatus downloadUrl(final Link link, final DmfArtifactHash hash, final long size) {
if (log.isDebugEnabled()) {
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
dmfController.getTenantId(), dmfController.getControllerId(), url,
hideTokenDetails(targetToken), hash, size);
log.debug(LOG_PREFIX + "Downloading {}, expected hash {} and size {}",
dmfController.getTenant().getTenantId(), dmfController.getControllerId(), link.getHref(), hash, size);
}
try {
return readAndCheckDownloadUrl(url, targetToken, hash, size);
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
log.error(LOG_PREFIX + "Failed to download {}",
dmfController.getTenantId(), dmfController.getControllerId(), url, e);
return readAndCheckDownloadUrl(link, hash, size);
} catch (final NoSuchAlgorithmException | IOException e) {
log.error(LOG_PREFIX + "Failed to download {}", dmfController.getTenant().getTenantId(), dmfController.getControllerId(), link.getHref(), e);
return new UpdateStatus(
DmfActionStatus.ERROR,
List.of("Failed to download " + url + ": " + e.getMessage()));
List.of("Failed to download " + link.getHref() + ": " + e.getMessage()));
}
}
private UpdateStatus readAndCheckDownloadUrl(final String url,
final String targetToken, final DmfArtifactHash hash, final long size)
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
private UpdateStatus readAndCheckDownloadUrl(final Link link, final DmfArtifactHash hash, final long size)
throws NoSuchAlgorithmException, IOException {
final Validator sizeValidator = sizeValidator(size);
final Validator hashValidator = hashValidator(hash);
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(link.getHref());
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
final HttpGet request = new HttpGet(url);
if (StringUtils.hasLength(targetToken)) {
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
}
return httpclient.execute(request, response -> {
try {
if (response.getCode() != HttpStatus.OK.value()) {
throw new IllegalStateException("Unexpected status code: " + response.getCode());
}
if (response.getEntity().getContentLength() != size) {
throw new IllegalArgumentException(
"Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
.getContentLength() + ")!");
}
final byte[] buff = new byte[32 * 1024];
try (final InputStream is = response.getEntity().getContent()) {
for (int read; (read = is.read(buff)) != -1; ) {
sizeValidator.read(buff, read);
hashValidator.read(buff, read);
downloadHandler.read(buff, 0, read);
}
}
sizeValidator.validate();
hashValidator.validate();
final String message = "Downloaded " + url + " (" + size + " bytes)";
log.debug(LOG_PREFIX + message, dmfController.getTenantId(), dmfController.getControllerId());
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
return new UpdateStatus(DmfActionStatus.FINISHED, List.of(message));
} catch (final Exception e) {
final String message = e.getMessage();
if (log.isTraceEnabled()) {
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
dmfController.getTenantId(), dmfController.getControllerId(), e);
} else {
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
dmfController.getTenantId(), dmfController.getControllerId());
}
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
return new UpdateStatus(DmfActionStatus.ERROR, List.of(message));
try (final InputStream is = HawkbitClient.getLink(link, InputStream.class, dmfController.getTenant(), dmfController.getController())) {
try {
final byte[] buff = new byte[32 * 1024];
for (int read; (read = is.read(buff)) != -1; ) {
sizeValidator.read(buff, read);
hashValidator.read(buff, read);
downloadHandler.read(buff, 0, read);
}
});
sizeValidator.validate();
hashValidator.validate();
final String message = "Downloaded " + link + " (" + size + " bytes)";
log.debug(LOG_PREFIX + message, dmfController.getTenant().getTenantId(), dmfController.getControllerId());
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
downloadHandler.download().ifPresent(path -> downloads.put(link.getHref(), path));
return new UpdateStatus(DmfActionStatus.FINISHED, List.of(message));
} catch (final Exception e) {
final String message = e.getMessage();
if (log.isTraceEnabled()) {
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + link + " failed: " + message,
dmfController.getTenant().getTenantId(), dmfController.getControllerId(), e);
} else {
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + link + " failed: " + message,
dmfController.getTenant().getTenantId(), dmfController.getControllerId());
}
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
return new UpdateStatus(DmfActionStatus.ERROR, List.of(message));
}
}
}
private interface Validator {
void read(final byte[] buff, final int len);
void validate();
}
}