Added template variables for request driven URL calculation. (#416)
Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -74,7 +74,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.port=8080
|
||||
hawkbit.artifact.url.protocols.download-http.supports=DMF,DDI
|
||||
hawkbit.artifact.url.protocols.download-http.ref={protocol}://{hostname}:{port}/{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}
|
||||
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.hostname=${hawkbit.artifact.url.protocols.download-http.hostname}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.api;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -15,7 +16,6 @@ import java.util.List;
|
||||
* URLs to specific artifacts.
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ArtifactUrlHandler {
|
||||
|
||||
/**
|
||||
@@ -30,4 +30,20 @@ public interface ArtifactUrlHandler {
|
||||
* @return an URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api);
|
||||
|
||||
/**
|
||||
* Returns a generated download URL for a given artifact parameters for a
|
||||
* specific protocol.
|
||||
*
|
||||
* @param placeholder
|
||||
* data for URL generation
|
||||
* @param api
|
||||
* given protocol that URL needs to support
|
||||
* @param requestUri
|
||||
* of the request that allows the handler to align the generated
|
||||
* URL to the original request.
|
||||
*
|
||||
* @return an URL for the given artifact parameters in a given protocol
|
||||
*/
|
||||
List<ArtifactUrl> getUrls(URLPlaceholder placeholder, ApiType api, URI requestUri);
|
||||
}
|
||||
|
||||
@@ -8,15 +8,19 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.api;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
|
||||
@@ -46,6 +50,9 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
|
||||
private static final String IP_PLACEHOLDER = "ip";
|
||||
private static final String PORT_PLACEHOLDER = "port";
|
||||
private static final String HOSTNAME_PLACEHOLDER = "hostname";
|
||||
private static final String HOSTNAME_REQUEST_PLACEHOLDER = "hostnameRequest";
|
||||
private static final String PORT_REQUEST_PLACEHOLDER = "portRequest";
|
||||
private static final String HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER = "domainRequest";
|
||||
private static final String ARTIFACT_FILENAME_PLACEHOLDER = "artifactFileName";
|
||||
private static final String ARTIFACT_SHA1_PLACEHOLDER = "artifactSHA1";
|
||||
private static final String ARTIFACT_ID_BASE10_PLACEHOLDER = "artifactId";
|
||||
@@ -68,18 +75,24 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api) {
|
||||
return getUrls(placeholder, api, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArtifactUrl> getUrls(final URLPlaceholder placeholder, final ApiType api, final URI requestUri) {
|
||||
|
||||
return urlHandlerProperties.getProtocols().entrySet().stream()
|
||||
.filter(entry -> entry.getValue().getSupports().contains(api))
|
||||
.filter(entry -> entry.getValue().isEnabled())
|
||||
.map(entry -> new ArtifactUrl(entry.getValue().getProtocol().toUpperCase(), entry.getValue().getRel(),
|
||||
generateUrl(entry.getValue(), placeholder)))
|
||||
generateUrl(entry.getValue(), placeholder, requestUri)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
private static String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder) {
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder).entrySet();
|
||||
private static String generateUrl(final UrlProtocol protocol, final URLPlaceholder placeholder,
|
||||
final URI requestUri) {
|
||||
final Set<Entry<String, String>> entrySet = getReplaceMap(protocol, placeholder, requestUri).entrySet();
|
||||
|
||||
String urlPattern = protocol.getRef();
|
||||
|
||||
@@ -94,15 +107,21 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
|
||||
return urlPattern;
|
||||
}
|
||||
|
||||
private static Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder) {
|
||||
private static Map<String, String> getReplaceMap(final UrlProtocol protocol, final URLPlaceholder placeholder,
|
||||
final URI requestUri) {
|
||||
final Map<String, String> replaceMap = new HashMap<>();
|
||||
replaceMap.put(IP_PLACEHOLDER, protocol.getIp());
|
||||
replaceMap.put(HOSTNAME_PLACEHOLDER, protocol.getHostname());
|
||||
|
||||
replaceMap.put(HOSTNAME_REQUEST_PLACEHOLDER, getRequestHost(protocol, requestUri));
|
||||
replaceMap.put(PORT_REQUEST_PLACEHOLDER, getRequestPort(protocol, requestUri));
|
||||
replaceMap.put(HOSTNAME_WITH_DOMAIN_REQUEST_PLACEHOLDER, computeHostWithRequestDomain(protocol, requestUri));
|
||||
|
||||
replaceMap.put(ARTIFACT_FILENAME_PLACEHOLDER,
|
||||
UrlEscapers.urlFragmentEscaper().escape(placeholder.getSoftwareData().getFilename()));
|
||||
replaceMap.put(ARTIFACT_SHA1_PLACEHOLDER, placeholder.getSoftwareData().getSha1Hash());
|
||||
replaceMap.put(PROTOCOL_PLACEHOLDER, protocol.getProtocol());
|
||||
replaceMap.put(PORT_PLACEHOLDER, protocol.getPort() == null ? null : String.valueOf(protocol.getPort()));
|
||||
replaceMap.put(PORT_PLACEHOLDER, getPort(protocol));
|
||||
replaceMap.put(TENANT_PLACEHOLDER, placeholder.getTenant());
|
||||
replaceMap.put(TENANT_ID_BASE10_PLACEHOLDER, String.valueOf(placeholder.getTenantId()));
|
||||
replaceMap.put(TENANT_ID_BASE62_PLACEHOLDER, Base62Util.fromBase10(placeholder.getTenantId()));
|
||||
@@ -119,4 +138,48 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
|
||||
return replaceMap;
|
||||
}
|
||||
|
||||
private static String getRequestPort(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return getPort(protocol);
|
||||
}
|
||||
|
||||
return requestUri.getPort() > 0 ? String.valueOf(requestUri.getPort()) : getPort(protocol);
|
||||
}
|
||||
|
||||
private static String getRequestHost(final UrlProtocol protocol, final URI requestUri) {
|
||||
if (requestUri == null) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(requestUri.getHost()).orElse(protocol.getHostname());
|
||||
}
|
||||
|
||||
private static String getPort(final UrlProtocol protocol) {
|
||||
return protocol.getPort() == null ? null : String.valueOf(protocol.getPort());
|
||||
}
|
||||
|
||||
private static String computeHostWithRequestDomain(final UrlProtocol protocol, final URI requestUri) {
|
||||
|
||||
if (requestUri == null) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
if (!protocol.getHostname().contains(".")) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
final String host = Splitter.on('.').trimResults().omitEmptyStrings().splitToList(protocol.getHostname())
|
||||
.get(0);
|
||||
|
||||
final List<String> domainElements = Splitter.on('.').trimResults().omitEmptyStrings()
|
||||
.splitToList(requestUri.getHost());
|
||||
final String domain = Joiner.on(".").join(domainElements.subList(1, domainElements.size()));
|
||||
|
||||
if (Strings.isNullOrEmpty(domain)) {
|
||||
return protocol.getHostname();
|
||||
}
|
||||
|
||||
return host + "." + domain;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.api;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties.UrlProtocol;
|
||||
@@ -121,4 +123,72 @@ public class PropertyBasedArtifactUrlHandlerTest {
|
||||
TEST_PROTO + "://127.0.0.1:5683/fws/" + TENANT + "/" + TARGETID_BASE62 + "/" + ARTIFACTID_BASE62)),
|
||||
urls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfies that the full qualified host of the statically defined hostname is replaced with the host of the request.")
|
||||
public void urlGenerationWithHostFromRequest() throws URISyntaxException {
|
||||
final String testHost = "ddi.host.com";
|
||||
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setIp("127.0.0.1");
|
||||
proto.setPort(5683);
|
||||
proto.setProtocol(TEST_PROTO);
|
||||
proto.setRel(TEST_REL);
|
||||
proto.setSupports(Lists.newArrayList(ApiType.DDI));
|
||||
proto.setRef("{protocol}://{hostnameRequest}:{port}/fws/{tenant}/{targetIdBase62}/{artifactIdBase62}");
|
||||
properties.getProtocols().put("ftp", proto);
|
||||
|
||||
final List<ArtifactUrl> urls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI,
|
||||
new URI("https://" + testHost));
|
||||
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl(TEST_PROTO.toUpperCase(), TEST_REL, TEST_PROTO + "://"
|
||||
+ testHost + ":5683/fws/" + TENANT + "/" + TARGETID_BASE62 + "/" + ARTIFACTID_BASE62)), urls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfies that the port of the statically defined hostname is replaced with the port of the request.")
|
||||
public void urlGenerationWithPortFromRequest() throws URISyntaxException {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setRef(
|
||||
"{protocol}://{hostname}:{portRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI,
|
||||
new URI("http://anotherHost.com:8083"));
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl("http".toUpperCase(), "download-http",
|
||||
"http://localhost:8083/" + TENANT + "/controller/v1/" + CONTROLLER_ID + "/softwaremodules/"
|
||||
+ SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
|
||||
ddiUrls);
|
||||
|
||||
final List<ArtifactUrl> dmfUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl("http".toUpperCase(), "download-http",
|
||||
"http://localhost:8080/" + TENANT + "/controller/v1/" + CONTROLLER_ID + "/softwaremodules/"
|
||||
+ SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
|
||||
dmfUrls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verfies that the domain of the statically defined hostname is replaced with the domain of the request.")
|
||||
public void urlGenerationWithDomainFromRequest() throws URISyntaxException {
|
||||
final UrlProtocol proto = new UrlProtocol();
|
||||
proto.setHostname("host.bumlux.net");
|
||||
proto.setRef(
|
||||
"{protocol}://{domainRequest}/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}");
|
||||
|
||||
properties.getProtocols().put("download-http", proto);
|
||||
|
||||
final List<ArtifactUrl> ddiUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DDI,
|
||||
new URI("http://anotherHost.com:8083"));
|
||||
assertEquals(Lists.newArrayList(
|
||||
new ArtifactUrl("http".toUpperCase(), "download-http", "http://host.com/" + TENANT + "/controller/v1/"
|
||||
+ CONTROLLER_ID + "/softwaremodules/" + SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
|
||||
ddiUrls);
|
||||
|
||||
final List<ArtifactUrl> dmfUrls = urlHandlerUnderTest.getUrls(placeholder, ApiType.DMF);
|
||||
assertEquals(Lists.newArrayList(new ArtifactUrl("http".toUpperCase(), "download-http",
|
||||
"http://host.bumlux.net/" + TENANT + "/controller/v1/" + CONTROLLER_ID + "/softwaremodules/"
|
||||
+ SOFTWAREMODULEID + "/artifacts/" + FILENAME)),
|
||||
dmfUrls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.eclipse.hawkbit.repository.model.Artifact;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
@@ -49,10 +50,12 @@ public final class DataConversionHelper {
|
||||
}
|
||||
|
||||
static List<DdiChunk> createChunks(final Target target, final Action uAction,
|
||||
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
|
||||
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement,
|
||||
final HttpRequest request) {
|
||||
return uAction.getDistributionSet().getModules().stream()
|
||||
.map(module -> new DdiChunk(mapChunkLegacyKeys(module.getType().getKey()), module.getVersion(),
|
||||
module.getName(), createArtifacts(target, module, artifactUrlHandler, systemManagement)))
|
||||
module.getName(),
|
||||
createArtifacts(target, module, artifactUrlHandler, systemManagement, request)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
@@ -68,30 +71,18 @@ public final class DataConversionHelper {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all (rest) artifacts for a given software module.
|
||||
*
|
||||
* @param target
|
||||
* for create URLs for
|
||||
* @param module
|
||||
* the software module
|
||||
* @param artifactUrlHandler
|
||||
* for creating download URLs
|
||||
* @param systemManagement
|
||||
* for access to tenant meta data
|
||||
* @return a list of artifacts or a empty list. Cannot be <null>.
|
||||
*/
|
||||
public static List<DdiArtifact> createArtifacts(final Target target,
|
||||
static List<DdiArtifact> createArtifacts(final Target target,
|
||||
final org.eclipse.hawkbit.repository.model.SoftwareModule module,
|
||||
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement) {
|
||||
final ArtifactUrlHandler artifactUrlHandler, final SystemManagement systemManagement,
|
||||
final HttpRequest request) {
|
||||
|
||||
return module.getArtifacts().stream()
|
||||
.map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement))
|
||||
.map(artifact -> createArtifact(target, artifactUrlHandler, artifact, systemManagement, request))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static DdiArtifact createArtifact(final Target target, final ArtifactUrlHandler artifactUrlHandler,
|
||||
final Artifact artifact, final SystemManagement systemManagement) {
|
||||
final Artifact artifact, final SystemManagement systemManagement, final HttpRequest request) {
|
||||
final DdiArtifact file = new DdiArtifact();
|
||||
file.setHashes(new DdiArtifactHash(artifact.getSha1Hash(), artifact.getMd5Hash()));
|
||||
file.setFilename(artifact.getFilename());
|
||||
@@ -102,7 +93,7 @@ public final class DataConversionHelper {
|
||||
systemManagement.getTenantMetadata().getId(), target.getControllerId(), target.getId(),
|
||||
new SoftwareData(artifact.getSoftwareModule().getId(), artifact.getFilename(), artifact.getId(),
|
||||
artifact.getSha1Hash())),
|
||||
ApiType.DDI)
|
||||
ApiType.DDI, request.getURI())
|
||||
.forEach(entry -> file.add(new Link(entry.getRef()).withRel(entry.getRel())));
|
||||
|
||||
return file;
|
||||
|
||||
@@ -55,6 +55,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -120,7 +121,8 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(
|
||||
DataConversionHelper.createArtifacts(target, softwareModule, artifactUrlHandler, systemManagement),
|
||||
DataConversionHelper.createArtifacts(target, softwareModule, artifactUrlHandler, systemManagement,
|
||||
new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest())),
|
||||
HttpStatus.OK);
|
||||
}
|
||||
|
||||
@@ -244,7 +246,8 @@ public class DdiRootController implements DdiRootControllerRestApi {
|
||||
if (!action.isCancelingOrCanceled()) {
|
||||
|
||||
final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
|
||||
systemManagement);
|
||||
systemManagement,
|
||||
new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest()));
|
||||
|
||||
final HandlingType handlingType = action.isForce() ? HandlingType.FORCED : HandlingType.ATTEMPT;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user