SDK AutoConfig & improvements & fixes (#1663)

* SDK autoconfiguration added
* Option for custom error decoder and request interceptor added
* Fixed authentication for targets with security token

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-02-21 15:44:27 +02:00
committed by GitHub
parent ab61b168bd
commit 8ea3fdb5e7
10 changed files with 92 additions and 38 deletions

View File

@@ -14,7 +14,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* Sort fields for {@link ActionRest}. * Sort & search fields for actions.
*/ */
public enum ActionFields implements FieldNameProvider, FieldValueConverter<ActionFields> { public enum ActionFields implements FieldNameProvider, FieldValueConverter<ActionFields> {

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.sdk;
import feign.Client; import feign.Client;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
import feign.RequestInterceptor;
import feign.codec.Decoder; import feign.codec.Decoder;
import feign.codec.Encoder; import feign.codec.Encoder;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
@@ -22,13 +23,45 @@ import org.springframework.util.ObjectUtils;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction;
@Slf4j @Slf4j
@Builder @Builder
public class HawkbitClient { public class HawkbitClient {
private static final String AUTHORIZATION = "Authorization"; private static final String AUTHORIZATION = "Authorization";
private static final ErrorDecoder DEFAULT_ERROR_DECODER = new ErrorDecoder.Default(); private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default();
public static final ErrorDecoder DEFAULT_ERROR_DECODER = (methodKey, response) -> {
final Exception e = DEFAULT_ERROR_DECODER_0.decode(methodKey, response);
log.trace("REST API call failed!", e);
return e;
};
public static final BiFunction<Tenant, Controller, RequestInterceptor> DEFAULT_REQUEST_INTERCEPTOR_FN =
(tenant, controller) ->
controller == null ?
template -> {
template.header(
AUTHORIZATION,
"Basic " +
Base64.getEncoder()
.encodeToString(
(Objects.requireNonNull(tenant.getUsername(), "User is null!") +
":" +
Objects.requireNonNull(tenant.getPassword(),"Password is not available!"))
.getBytes(StandardCharsets.ISO_8859_1)));
} :
template -> {
if (ObjectUtils.isEmpty(tenant.getGatewayToken())) {
if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
} // else do not sent authentication
} else {
template.header(AUTHORIZATION, "GatewayToken " + tenant.getGatewayToken());
}
};
private final HawkbitServer hawkBitServerProperties; private final HawkbitServer hawkBitServerProperties;
@@ -37,14 +70,31 @@ public class HawkbitClient {
private final Decoder decoder; private final Decoder decoder;
private final Contract contract; private final Contract contract;
private final ErrorDecoder errorDecoder;
private final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn;
public HawkbitClient( public HawkbitClient(
final HawkbitServer hawkBitServerProperties, final HawkbitServer hawkBitServerProperties,
final Client client, final Encoder encoder, final Decoder decoder, final Contract contract) { final Client client, final Encoder encoder, final Decoder decoder, final Contract contract) {
this(hawkBitServerProperties, client, encoder, decoder, contract, null, null);
}
/**
* Customizers gets default ones and could
*/
public HawkbitClient(
final HawkbitServer hawkBitServerProperties,
final Client client, final Encoder encoder, final Decoder decoder, final Contract contract,
final ErrorDecoder errorDecoder,
final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn) {
this.hawkBitServerProperties = hawkBitServerProperties; this.hawkBitServerProperties = hawkBitServerProperties;
this.client = client; this.client = client;
this.encoder = encoder; this.encoder = encoder;
this.decoder = decoder; this.decoder = decoder;
this.contract = contract; this.contract = contract;
this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder;
this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn;
} }
public <T> T mgmtService(final Class<T> serviceType, final Tenant tenantProperties) { public <T> T mgmtService(final Class<T> serviceType, final Tenant tenantProperties) {
@@ -54,41 +104,16 @@ public class HawkbitClient {
return service(serviceType, tenantProperties, controller); return service(serviceType, tenantProperties, controller);
} }
private <T> T service(final Class<T> serviceType, final Tenant tenantProperties, final Controller controller) { private <T> T service(final Class<T> serviceType, final Tenant tenant, final Controller controller) {
return Feign.builder().client(client) return Feign.builder().client(client)
.encoder(encoder) .encoder(encoder)
.decoder(decoder) .decoder(decoder)
.errorDecoder((methodKey, response) -> { .errorDecoder(errorDecoder)
final Exception e = DEFAULT_ERROR_DECODER.decode(methodKey, response);
log.trace("REST API call failed!", e);
return e;
})
.contract(contract) .contract(contract)
.requestInterceptor(controller == null ? .requestInterceptor(requestInterceptorFn.apply(tenant, controller))
template -> {
template.header(AUTHORIZATION,
"Basic " +
Base64.getEncoder()
.encodeToString(
(Objects.requireNonNull(tenantProperties.getUsername(),
"User is null!") +
":" +
Objects.requireNonNull(tenantProperties.getPassword(),
"Password is not available!"))
.getBytes(StandardCharsets.ISO_8859_1)));
} :
template -> {
if (ObjectUtils.isEmpty(tenantProperties.getGatewayToken())) {
if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
} // else do not sent authentication
} else {
template.header(AUTHORIZATION, "GatewayToken " + tenantProperties.getGatewayToken());
}
})
.target(serviceType, .target(serviceType,
controller == null ? controller == null ?
hawkBitServerProperties.getMgmtUrl() : hawkBitServerProperties.getMgmtUrl() :
hawkBitServerProperties.getDdiUrl()); hawkBitServerProperties.getDdiUrl());
} }
} }

View File

@@ -25,6 +25,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.WebConverters; import org.springframework.hateoas.config.WebConverters;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -38,6 +39,7 @@ import java.util.LinkedHashMap;
@EnableConfigurationProperties({ HawkbitServer.class, Tenant.class}) @EnableConfigurationProperties({ HawkbitServer.class, Tenant.class})
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
@Import(FeignClientsConfiguration.class) @Import(FeignClientsConfiguration.class)
@PropertySource("classpath:/hawkbit-sdk-defaults.properties")
public class HawkbitSDKConfigurtion { public class HawkbitSDKConfigurtion {
/** /**

View File

@@ -0,0 +1 @@
org.eclipse.hawkbit.sdk.HawkbitSDKConfigurtion

View File

@@ -8,5 +8,4 @@
# SPDX-License-Identifier: EPL-2.0 # SPDX-License-Identifier: EPL-2.0
# #
spring.cloud.openfeign.httpclient.hc5.enabled=true spring.cloud.openfeign.httpclient.hc5.enabled=true

View File

@@ -19,12 +19,14 @@
<version>${revision}</version> <version>${revision}</version>
</parent> </parent>
<artifactId>hawkbit-sdk-test</artifactId> <artifactId>hawkbit-sdk-demo</artifactId>
<name>hawkBit :: SDK :: Test / Example</name> <name>hawkBit :: SDK :: Test / Example</name>
<description>Test / Example of how SDK could be used to for devices and for Mgmt API access</description> <description>Test / Example of how SDK could be used to for devices and for Mgmt API access</description>
<properties> <properties>
<spring-shell.version>3.1.5</spring-shell.version> <spring-shell.version>3.1.5</spring-shell.version>
<spring.app.class>org.eclipse.hawkbit.sdk.demo.multidevice.MultiDeviceApp</spring.app.class>
<start-class>${spring.app.class}</start-class>
</properties> </properties>
<dependencies> <dependencies>
@@ -45,4 +47,30 @@
<version>${spring-shell.version}</version> <version>${spring-shell.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<outputDirectory>${baseDir}</outputDirectory>
<layout>JAR</layout>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project> </project>

View File

@@ -93,8 +93,10 @@ public class SetupHelper {
securityTargetToken = randomToken(); securityTargetToken = randomToken();
mgmtTargetRestApi.updateTarget(controllerId, mgmtTargetRestApi.updateTarget(controllerId,
new MgmtTargetRequestBody().setSecurityToken(securityTargetToken)); new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));
} else {
securityTargetToken = target.getSecurityToken();
} }
} else if (!securityTargetToken.equals(target.getSecurityToken())){ } else if (!securityTargetToken.equals(target.getSecurityToken())) {
// update target's with the security token (since it doesn't match) // update target's with the security token (since it doesn't match)
mgmtTargetRestApi.updateTarget(controllerId, mgmtTargetRestApi.updateTarget(controllerId,
new MgmtTargetRequestBody().setSecurityToken(securityTargetToken)); new MgmtTargetRequestBody().setSecurityToken(securityTargetToken));

View File

@@ -40,7 +40,6 @@ import java.util.concurrent.ScheduledExecutorService;
*/ */
@Slf4j @Slf4j
@SpringBootApplication @SpringBootApplication
@Import({ HawkbitSDKConfigurtion.class})
public class DeviceApp { public class DeviceApp {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -41,7 +41,6 @@ import java.util.concurrent.ScheduledExecutorService;
*/ */
@Slf4j @Slf4j
@SpringBootApplication @SpringBootApplication
@Import({ HawkbitSDKConfigurtion.class})
public class MultiDeviceApp { public class MultiDeviceApp {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -9,7 +9,6 @@
# #
spring.main.web-application-type=none spring.main.web-application-type=none
spring.cloud.openfeign.httpclient.hc5.enabled=true
logging.level.org.eclipse.hawkbit=DEBUG logging.level.org.eclipse.hawkbit=DEBUG