Remove not used WeightValidationHelper (#2916)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -16,7 +16,7 @@ _(Note: you have to add the JDBC driver also to your class path if you intend to
|
|||||||
Or:
|
Or:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
run org.eclipse.hawkbit.app.ddi.DDIStart
|
run org.eclipse.hawkbit.app.ddi.DdiStart
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<name>hawkBit :: DDI :: Server</name>
|
<name>hawkBit :: DDI :: Server</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring.app.class>org.eclipse.hawkbit.app.ddi.DDIStart</spring.app.class>
|
<spring.app.class>org.eclipse.hawkbit.app.ddi.DdiStart</spring.app.class>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.springframework.web.servlet.view.RedirectView;
|
|||||||
* The minimal configuration for the stand alone hawkBit DDI server.
|
* The minimal configuration for the stand alone hawkBit DDI server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
public class DDIStart {
|
public class DdiStart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method to start the spring-boot application.
|
* Main method to start the spring-boot application.
|
||||||
@@ -28,7 +28,7 @@ public class DDIStart {
|
|||||||
* @param args the VM arguments.
|
* @param args the VM arguments.
|
||||||
*/
|
*/
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
SpringApplication.run(DDIStart.class, args);
|
SpringApplication.run(DdiStart.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
@@ -12,7 +12,7 @@ _(Note: you have to add the JDBC driver also to your class path if you intend to
|
|||||||
|
|
||||||
Or:
|
Or:
|
||||||
```bash
|
```bash
|
||||||
run org.eclipse.hawkbit.app.dmf.DMFStart
|
run org.eclipse.hawkbit.app.dmf.DmfStart
|
||||||
```
|
```
|
||||||
# Clustering (Experimental!!!)
|
# Clustering (Experimental!!!)
|
||||||
## Remote Events between micro-services
|
## Remote Events between micro-services
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<name>hawkBit :: DMF :: Server</name>
|
<name>hawkBit :: DMF :: Server</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring.app.class>org.eclipse.hawkbit.app.ddi.DMFStart</spring.app.class>
|
<spring.app.class>org.eclipse.hawkbit.app.ddi.DmfStart</spring.app.class>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
* The minimal configuration for the stand alone hawkBit DMF server.
|
* The minimal configuration for the stand alone hawkBit DMF server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
public class DMFStart {
|
public class DmfStart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method to start the spring-boot application.
|
* Main method to start the spring-boot application.
|
||||||
@@ -25,6 +25,6 @@ public class DMFStart {
|
|||||||
* @param args the VM arguments.
|
* @param args the VM arguments.
|
||||||
*/
|
*/
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
SpringApplication.run(DMFStart.class, args);
|
SpringApplication.run(DmfStart.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<description>Standalone MCP server that connects to hawkBit via REST API</description>
|
<description>Standalone MCP server that connects to hawkBit via REST API</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring.app.class>org.eclipse.hawkbit.mcp.server.HawkbitMcpServerApplication</spring.app.class>
|
<spring.app.class>org.eclipse.hawkbit.mcp.server.McpServerStart</spring.app.class>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServic
|
|||||||
*/
|
*/
|
||||||
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
|
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
|
||||||
@EnableConfigurationProperties(HawkbitMcpProperties.class)
|
@EnableConfigurationProperties(HawkbitMcpProperties.class)
|
||||||
public class HawkbitMcpServerApplication {
|
public class McpServerStart {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(final String[] args) {
|
||||||
SpringApplication.run(HawkbitMcpServerApplication.class, args);
|
SpringApplication.run(McpServerStart.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ public interface AuthenticationValidator {
|
|||||||
* Result of authentication validation.
|
* Result of authentication validation.
|
||||||
*/
|
*/
|
||||||
enum ValidationResult {
|
enum ValidationResult {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credentials are valid (authenticated user).
|
* Credentials are valid (authenticated user).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mcp.server.client;
|
package org.eclipse.hawkbit.mcp.server.client;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import feign.FeignException;
|
import feign.FeignException;
|
||||||
@@ -21,11 +26,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.HexFormat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates authentication credentials against hawkBit REST API using the SDK.
|
* Validates authentication credentials against hawkBit REST API using the SDK.
|
||||||
* This validator is conditionally created when {@code hawkbit.mcp.validation.enabled=true}.
|
* This validator is conditionally created when {@code hawkbit.mcp.validation.enabled=true}.
|
||||||
@@ -39,9 +39,7 @@ public class HawkbitAuthenticationValidator implements AuthenticationValidator {
|
|||||||
private final Tenant dummyTenant;
|
private final Tenant dummyTenant;
|
||||||
private final Cache<String, Boolean> validationCache;
|
private final Cache<String, Boolean> validationCache;
|
||||||
|
|
||||||
public HawkbitAuthenticationValidator(final HawkbitClient hawkbitClient,
|
public HawkbitAuthenticationValidator(final HawkbitClient hawkbitClient, final Tenant dummyTenant, final HawkbitMcpProperties properties) {
|
||||||
final Tenant dummyTenant,
|
|
||||||
final HawkbitMcpProperties properties) {
|
|
||||||
this.hawkbitClient = hawkbitClient;
|
this.hawkbitClient = hawkbitClient;
|
||||||
this.dummyTenant = dummyTenant;
|
this.dummyTenant = dummyTenant;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mcp.server.config;
|
package org.eclipse.hawkbit.mcp.server.config;
|
||||||
|
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
import feign.Contract;
|
import feign.Contract;
|
||||||
import feign.RequestInterceptor;
|
import feign.RequestInterceptor;
|
||||||
import feign.codec.Decoder;
|
import feign.codec.Decoder;
|
||||||
@@ -23,8 +25,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common configuration for the hawkBit SDK client.
|
* Common configuration for the hawkBit SDK client.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -46,7 +46,7 @@ public class HawkbitClientConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public HawkbitServer hawkbitServer() {
|
public HawkbitServer hawkbitServer() {
|
||||||
HawkbitServer server = new HawkbitServer();
|
final HawkbitServer server = new HawkbitServer();
|
||||||
server.setMgmtUrl(properties.getMgmtUrl());
|
server.setMgmtUrl(properties.getMgmtUrl());
|
||||||
log.info("Configured hawkBit server URL: {}", properties.getMgmtUrl());
|
log.info("Configured hawkBit server URL: {}", properties.getMgmtUrl());
|
||||||
return server;
|
return server;
|
||||||
@@ -68,5 +68,4 @@ public class HawkbitClientConfiguration {
|
|||||||
.requestInterceptorFn(hawkbitRequestInterceptor)
|
.requestInterceptorFn(hawkbitRequestInterceptor)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,12 @@ import java.time.Duration;
|
|||||||
@ConfigurationProperties(prefix = "hawkbit.mcp")
|
@ConfigurationProperties(prefix = "hawkbit.mcp")
|
||||||
public class HawkbitMcpProperties {
|
public class HawkbitMcpProperties {
|
||||||
|
|
||||||
|
public static final String OP_LIST = "list";
|
||||||
|
public static final String OP_CREATE = "create";
|
||||||
|
public static final String OP_UPDATE = "update";
|
||||||
|
public static final String OP_DELETE = "delete";
|
||||||
|
public static final String OP_DELETE_BATCH = "delete-batch";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base URL of the hawkBit Management API (e.g., <a href="http://localhost:8080">...</a>).
|
* Base URL of the hawkBit Management API (e.g., <a href="http://localhost:8080">...</a>).
|
||||||
*/
|
*/
|
||||||
@@ -56,19 +62,19 @@ public class HawkbitMcpProperties {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to enable the built-in hawkBit tools.
|
* Whether to enable the built-in hawkBit tools.
|
||||||
* Set to false to provide custom tool implementations.
|
* Set to <code>false</code> to provide custom tool implementations.
|
||||||
*/
|
*/
|
||||||
private boolean toolsEnabled = true;
|
private boolean toolsEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to enable the built-in hawkBit documentation resources.
|
* Whether to enable the built-in hawkBit documentation resources.
|
||||||
* Set to false to provide custom resource implementations.
|
* Set <code>false</code> provide custom resource implementations.
|
||||||
*/
|
*/
|
||||||
private boolean resourcesEnabled = true;
|
private boolean resourcesEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to enable the built-in hawkBit prompts.
|
* Whether to enable the built-in hawkBit prompts.
|
||||||
* Set to false to provide custom prompt implementations.
|
* Set <code>false</code> provide custom prompt implementations.
|
||||||
*/
|
*/
|
||||||
private boolean promptsEnabled = true;
|
private boolean promptsEnabled = true;
|
||||||
|
|
||||||
@@ -130,10 +136,10 @@ public class HawkbitMcpProperties {
|
|||||||
*/
|
*/
|
||||||
public boolean isGlobalOperationEnabled(final String operation) {
|
public boolean isGlobalOperationEnabled(final String operation) {
|
||||||
return switch (operation.toLowerCase()) {
|
return switch (operation.toLowerCase()) {
|
||||||
case "list" -> listEnabled;
|
case OP_LIST -> listEnabled;
|
||||||
case "create" -> createEnabled;
|
case OP_CREATE -> createEnabled;
|
||||||
case "update" -> updateEnabled;
|
case OP_UPDATE -> updateEnabled;
|
||||||
case "delete" -> deleteEnabled;
|
case OP_DELETE -> deleteEnabled;
|
||||||
default -> true;
|
default -> true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -155,10 +161,10 @@ public class HawkbitMcpProperties {
|
|||||||
*/
|
*/
|
||||||
public Boolean getOperationEnabled(final String operation) {
|
public Boolean getOperationEnabled(final String operation) {
|
||||||
return switch (operation.toLowerCase()) {
|
return switch (operation.toLowerCase()) {
|
||||||
case "list" -> listEnabled;
|
case OP_LIST -> listEnabled;
|
||||||
case "create" -> createEnabled;
|
case OP_CREATE -> createEnabled;
|
||||||
case "update" -> updateEnabled;
|
case OP_UPDATE -> updateEnabled;
|
||||||
case "delete" -> deleteEnabled;
|
case OP_DELETE -> deleteEnabled;
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -215,9 +221,9 @@ public class HawkbitMcpProperties {
|
|||||||
*/
|
*/
|
||||||
public Boolean getOperationEnabled(final String operation) {
|
public Boolean getOperationEnabled(final String operation) {
|
||||||
return switch (operation.toLowerCase().replace("_", "-")) {
|
return switch (operation.toLowerCase().replace("_", "-")) {
|
||||||
case "list" -> listEnabled;
|
case OP_LIST -> listEnabled;
|
||||||
case "delete" -> deleteEnabled;
|
case OP_DELETE -> deleteEnabled;
|
||||||
case "delete-batch" -> deleteBatchEnabled;
|
case OP_DELETE_BATCH -> deleteBatchEnabled;
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,5 +68,4 @@ public class McpHttpClientConfiguration {
|
|||||||
log.info("Configured tenant for HTTP mode (per-request authentication)");
|
log.info("Configured tenant for HTTP mode (per-request authentication)");
|
||||||
return new Tenant();
|
return new Tenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mcp.server.config;
|
package org.eclipse.hawkbit.mcp.server.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -33,9 +37,6 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security configuration for the MCP server.
|
* Security configuration for the MCP server.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -62,15 +63,14 @@ public class McpSecurityConfiguration {
|
|||||||
@SuppressWarnings("java:S4502") // CSRF protection is not needed for stateless REST APIs using Authorization header
|
@SuppressWarnings("java:S4502") // CSRF protection is not needed for stateless REST APIs using Authorization header
|
||||||
public SecurityFilterChain mcpSecurityFilterChain(final HttpSecurity http) throws Exception {
|
public SecurityFilterChain mcpSecurityFilterChain(final HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
|
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
|
|
||||||
authenticationValidator.ifPresentOrElse(
|
authenticationValidator.ifPresentOrElse(
|
||||||
validator -> {
|
validator -> {
|
||||||
log.info("Authentication validation enabled - adding validation filter");
|
log.info("Authentication validation enabled - adding validation filter");
|
||||||
http.addFilterBefore(new HawkBitAuthenticationFilter(validator),
|
http.addFilterBefore(new HawkBitAuthenticationFilter(validator), UsernamePasswordAuthenticationFilter.class);
|
||||||
UsernamePasswordAuthenticationFilter.class);
|
|
||||||
},
|
},
|
||||||
() -> log.info("Authentication validation disabled - requests will be forwarded without validation")
|
() -> log.info("Authentication validation disabled - requests will be forwarded without validation")
|
||||||
);
|
);
|
||||||
@@ -91,8 +91,9 @@ public class McpSecurityConfiguration {
|
|||||||
private final AuthenticationValidator validator;
|
private final AuthenticationValidator validator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(final HttpServletRequest request, final @NonNull HttpServletResponse response,
|
protected void doFilterInternal(
|
||||||
final @NonNull FilterChain filterChain) throws ServletException, IOException {
|
final HttpServletRequest request, final @NonNull HttpServletResponse response, final @NonNull FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
final ValidationResult result = validator.validate(authHeader);
|
final ValidationResult result = validator.validate(authHeader);
|
||||||
|
|
||||||
@@ -120,10 +121,7 @@ public class McpSecurityConfiguration {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
response.setStatus(status.value());
|
response.setStatus(status.value());
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
response.getWriter().write(String.format(
|
response.getWriter().write(String.format("{\"error\":\"%s\",\"message\":\"%s\"}", status.getReasonPhrase(), message));
|
||||||
"{\"error\":\"%s\",\"message\":\"%s\"}",
|
|
||||||
status.getReasonPhrase(),
|
|
||||||
message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,9 +46,8 @@ public class McpStdioClientConfiguration {
|
|||||||
log.info("Configuring STDIO mode request interceptor (static credentials)");
|
log.info("Configuring STDIO mode request interceptor (static credentials)");
|
||||||
return (tenant, controller) -> template -> {
|
return (tenant, controller) -> template -> {
|
||||||
if (properties.hasStaticCredentials()) {
|
if (properties.hasStaticCredentials()) {
|
||||||
String credentials = properties.getUsername() + ":" + properties.getPassword();
|
final String credentials = properties.getUsername() + ":" + properties.getPassword();
|
||||||
String authHeader = "Basic " + Base64.getEncoder().encodeToString(
|
final String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
||||||
credentials.getBytes(StandardCharsets.UTF_8));
|
|
||||||
template.header(HttpHeaders.AUTHORIZATION, authHeader);
|
template.header(HttpHeaders.AUTHORIZATION, authHeader);
|
||||||
log.trace("Using static credentials from properties (STDIO mode)");
|
log.trace("Using static credentials from properties (STDIO mode)");
|
||||||
} else {
|
} else {
|
||||||
@@ -72,5 +71,4 @@ public class McpStdioClientConfiguration {
|
|||||||
}
|
}
|
||||||
return tenant;
|
return tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -45,9 +45,7 @@ public class McpToolConfiguration {
|
|||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
@ConditionalOnProperty(name = "hawkbit.mcp.tools-enabled", havingValue = "true", matchIfMissing = true)
|
@ConditionalOnProperty(name = "hawkbit.mcp.tools-enabled", havingValue = "true", matchIfMissing = true)
|
||||||
public HawkbitMcpToolProvider hawkBitMcpToolProvider(
|
public HawkbitMcpToolProvider hawkBitMcpToolProvider(
|
||||||
final HawkbitClient hawkbitClient,
|
final HawkbitClient hawkbitClient, final Tenant dummyTenant, final HawkbitMcpProperties properties) {
|
||||||
final Tenant dummyTenant,
|
|
||||||
final HawkbitMcpProperties properties) {
|
|
||||||
return new HawkbitMcpToolProvider(hawkbitClient, dummyTenant, properties);
|
return new HawkbitMcpToolProvider(hawkbitClient, dummyTenant, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mcp.server.dto;
|
package org.eclipse.hawkbit.mcp.server.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sealed interface for action management operations.
|
* Sealed interface for action management operations.
|
||||||
*/
|
*/
|
||||||
@@ -36,7 +36,7 @@ public sealed interface ActionRequest
|
|||||||
* Request to delete multiple actions.
|
* Request to delete multiple actions.
|
||||||
*
|
*
|
||||||
* @param actionIds list of action IDs to delete (mutually exclusive with rsql)
|
* @param actionIds list of action IDs to delete (mutually exclusive with rsql)
|
||||||
* @param rsql RSQL filter query for selecting actions to delete (mutually exclusive with actionIds)
|
* @param rsql RSQL filter query for selecting actions to delete (mutually exclusive with actionIds)
|
||||||
*/
|
*/
|
||||||
record DeleteBatch(List<Long> actionIds, String rsql) implements ActionRequest {}
|
record DeleteBatch(List<Long> actionIds, String rsql) implements ActionRequest {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,10 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
|||||||
public record ListRequest(
|
public record ListRequest(
|
||||||
@JsonPropertyDescription("RSQL filter query (e.g., 'name==test*')")
|
@JsonPropertyDescription("RSQL filter query (e.g., 'name==test*')")
|
||||||
String rsql,
|
String rsql,
|
||||||
|
|
||||||
@JsonPropertyDescription("Number of items to skip (default: 0)")
|
@JsonPropertyDescription("Number of items to skip (default: 0)")
|
||||||
Integer offset,
|
Integer offset,
|
||||||
|
|
||||||
@JsonPropertyDescription("Maximum number of items to return (default: 50)")
|
@JsonPropertyDescription("Maximum number of items to return (default: 50)")
|
||||||
Integer limit
|
Integer limit) {
|
||||||
) {
|
|
||||||
|
|
||||||
public static final int DEFAULT_OFFSET = 0;
|
public static final int DEFAULT_OFFSET = 0;
|
||||||
public static final int DEFAULT_LIMIT = 50;
|
public static final int DEFAULT_LIMIT = 50;
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ package org.eclipse.hawkbit.mcp.server.dto;
|
|||||||
* @param message optional message (typically for success confirmations or error details)
|
* @param message optional message (typically for success confirmations or error details)
|
||||||
* @param data the operation result data (e.g., created/updated entity)
|
* @param data the operation result data (e.g., created/updated entity)
|
||||||
*/
|
*/
|
||||||
public record OperationResponse<T>(
|
public record OperationResponse<T>(String operation, boolean success, String message, T data) {
|
||||||
String operation,
|
|
||||||
boolean success,
|
|
||||||
String message,
|
|
||||||
T data
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a successful response with data.
|
* Creates a successful response with data.
|
||||||
|
|||||||
@@ -16,12 +16,7 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @param <T> the type of items in the response
|
* @param <T> the type of items in the response
|
||||||
*/
|
*/
|
||||||
public record PagedResponse<T>(
|
public record PagedResponse<T>(List<T> content, long total, int offset, int limit) {
|
||||||
List<T> content,
|
|
||||||
long total,
|
|
||||||
int offset,
|
|
||||||
int limit
|
|
||||||
) {
|
|
||||||
|
|
||||||
public static <T> PagedResponse<T> of(final List<T> content, final long total, final int offset, final int limit) {
|
public static <T> PagedResponse<T> of(final List<T> content, final long total, final int offset, final int limit) {
|
||||||
return new PagedResponse<>(content, total, offset, limit);
|
return new PagedResponse<>(content, total, offset, limit);
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ import java.util.List;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class HawkbitMcpToolProvider {
|
public class HawkbitMcpToolProvider {
|
||||||
|
|
||||||
private static final String OP_CREATE = "CREATE";
|
private static final String OP_CREATE = HawkbitMcpProperties.OP_CREATE.toUpperCase();
|
||||||
private static final String OP_UPDATE = "UPDATE";
|
private static final String OP_UPDATE = HawkbitMcpProperties.OP_UPDATE.toUpperCase();
|
||||||
private static final String OP_DELETE = "DELETE";
|
private static final String OP_DELETE = HawkbitMcpProperties.OP_DELETE.toUpperCase();
|
||||||
private static final String OP_DELETE_BATCH = "DELETE_BATCH";
|
private static final String OP_DELETE_BATCH = HawkbitMcpProperties.OP_DELETE_BATCH.toUpperCase().replace('-', '_');
|
||||||
private static final String OP_START = "START";
|
private static final String OP_START = "START";
|
||||||
private static final String OP_PAUSE = "PAUSE";
|
private static final String OP_PAUSE = "PAUSE";
|
||||||
private static final String OP_STOP = "STOP";
|
private static final String OP_STOP = "STOP";
|
||||||
@@ -66,6 +66,14 @@ public class HawkbitMcpToolProvider {
|
|||||||
private static final String OP_RETRY = "RETRY";
|
private static final String OP_RETRY = "RETRY";
|
||||||
private static final String OP_TRIGGER_NEXT_GROUP = "TRIGGER_NEXT_GROUP";
|
private static final String OP_TRIGGER_NEXT_GROUP = "TRIGGER_NEXT_GROUP";
|
||||||
|
|
||||||
|
private static final String SOFTWARE_MODULES = "softwareModules";
|
||||||
|
private static final String DISTRIBUTION_SETS = "distributionSets";
|
||||||
|
private static final String TARGETS = "targets";
|
||||||
|
private static final String TARGET_FILTERS = "targetFilters";
|
||||||
|
|
||||||
|
private static final String BODY_IS_REQUIRED_FOR_UPDATE_OPERATION = "body is required for UPDATE operation";
|
||||||
|
private static final String BODY_IS_REQUIRED_FOR_CREATE_OPERATION = "body is required for CREATE operation";
|
||||||
|
|
||||||
private final HawkbitClient hawkbitClient;
|
private final HawkbitClient hawkbitClient;
|
||||||
private final Tenant dummyTenant;
|
private final Tenant dummyTenant;
|
||||||
private final HawkbitMcpProperties properties;
|
private final HawkbitMcpProperties properties;
|
||||||
@@ -199,13 +207,14 @@ public class HawkbitMcpToolProvider {
|
|||||||
"{\"type\":\"Create\",\"body\":{\"controllerId\":\"id\",\"name\":\"name\"}}, " +
|
"{\"type\":\"Create\",\"body\":{\"controllerId\":\"id\",\"name\":\"name\"}}, " +
|
||||||
"{\"type\":\"Update\",\"controllerId\":\"id\",\"body\":{...}}, " +
|
"{\"type\":\"Update\",\"controllerId\":\"id\",\"body\":{...}}, " +
|
||||||
"{\"type\":\"Delete\",\"controllerId\":\"id\"}")
|
"{\"type\":\"Delete\",\"controllerId\":\"id\"}")
|
||||||
|
@SuppressWarnings("java:S3776") // not too complex
|
||||||
public OperationResponse<Object> manageTarget(final TargetRequest request) {
|
public OperationResponse<Object> manageTarget(final TargetRequest request) {
|
||||||
log.debug("Managing target: request={}", request.getClass().getSimpleName());
|
log.debug("Managing target: request={}", request.getClass().getSimpleName());
|
||||||
|
|
||||||
final MgmtTargetRestApi api = hawkbitClient.mgmtService(MgmtTargetRestApi.class, dummyTenant);
|
final MgmtTargetRestApi api = hawkbitClient.mgmtService(MgmtTargetRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof TargetRequest.Create r) {
|
if (request instanceof TargetRequest.Create r) {
|
||||||
validateOperation("create", "targets");
|
validateOperation(OP_CREATE, TARGETS);
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_CREATE, "Request body is required for CREATE operation");
|
return OperationResponse.failure(OP_CREATE, "Request body is required for CREATE operation");
|
||||||
}
|
}
|
||||||
@@ -214,7 +223,7 @@ public class HawkbitMcpToolProvider {
|
|||||||
return OperationResponse.success(OP_CREATE, "Target created successfully",
|
return OperationResponse.success(OP_CREATE, "Target created successfully",
|
||||||
created != null && !created.isEmpty() ? created.get(0) : null);
|
created != null && !created.isEmpty() ? created.get(0) : null);
|
||||||
} else if (request instanceof TargetRequest.Update r) {
|
} else if (request instanceof TargetRequest.Update r) {
|
||||||
validateOperation("update", "targets");
|
validateOperation(OP_UPDATE, TARGETS);
|
||||||
if (r.controllerId() == null || r.controllerId().isBlank()) {
|
if (r.controllerId() == null || r.controllerId().isBlank()) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "controllerId is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, "controllerId is required for UPDATE operation");
|
||||||
}
|
}
|
||||||
@@ -224,7 +233,7 @@ public class HawkbitMcpToolProvider {
|
|||||||
final ResponseEntity<MgmtTarget> response = api.updateTarget(r.controllerId(), r.body());
|
final ResponseEntity<MgmtTarget> response = api.updateTarget(r.controllerId(), r.body());
|
||||||
return OperationResponse.success(OP_UPDATE, "Target updated successfully", response.getBody());
|
return OperationResponse.success(OP_UPDATE, "Target updated successfully", response.getBody());
|
||||||
} else if (request instanceof TargetRequest.Delete r) {
|
} else if (request instanceof TargetRequest.Delete r) {
|
||||||
validateOperation("delete", "targets");
|
validateOperation(OP_DELETE, TARGETS);
|
||||||
if (r.controllerId() == null || r.controllerId().isBlank()) {
|
if (r.controllerId() == null || r.controllerId().isBlank()) {
|
||||||
return OperationResponse.failure(OP_DELETE, "controllerId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "controllerId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -243,15 +252,16 @@ public class HawkbitMcpToolProvider {
|
|||||||
"Examples: {\"type\":\"Create\",\"body\":{...}}, " +
|
"Examples: {\"type\":\"Create\",\"body\":{...}}, " +
|
||||||
"{\"type\":\"Start\",\"rolloutId\":123}, " +
|
"{\"type\":\"Start\",\"rolloutId\":123}, " +
|
||||||
"{\"type\":\"Approve\",\"rolloutId\":123,\"remark\":\"approved\"}")
|
"{\"type\":\"Approve\",\"rolloutId\":123,\"remark\":\"approved\"}")
|
||||||
|
@SuppressWarnings("java:S3776") // not too complex, iterative logic
|
||||||
public OperationResponse<Object> manageRollout(final RolloutRequest request) {
|
public OperationResponse<Object> manageRollout(final RolloutRequest request) {
|
||||||
log.debug("Managing rollout: request={}", request.getClass().getSimpleName());
|
log.debug("Managing rollout: request={}", request.getClass().getSimpleName());
|
||||||
|
|
||||||
final MgmtRolloutRestApi api = hawkbitClient.mgmtService(MgmtRolloutRestApi.class, dummyTenant);
|
final MgmtRolloutRestApi api = hawkbitClient.mgmtService(MgmtRolloutRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof RolloutRequest.Create r) {
|
if (request instanceof RolloutRequest.Create r) {
|
||||||
validateRolloutOperation("create");
|
validateRolloutOperation(OP_CREATE);
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_CREATE, "body is required for CREATE operation");
|
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtRolloutResponseBody> response = api.create(r.body());
|
final ResponseEntity<MgmtRolloutResponseBody> response = api.create(r.body());
|
||||||
return OperationResponse.success(OP_CREATE, "Rollout created successfully", response.getBody());
|
return OperationResponse.success(OP_CREATE, "Rollout created successfully", response.getBody());
|
||||||
@@ -261,12 +271,12 @@ public class HawkbitMcpToolProvider {
|
|||||||
return OperationResponse.failure(OP_UPDATE, "rolloutId is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, "rolloutId is required for UPDATE operation");
|
||||||
}
|
}
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "body is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtRolloutResponseBody> response = api.update(r.rolloutId(), r.body());
|
final ResponseEntity<MgmtRolloutResponseBody> response = api.update(r.rolloutId(), r.body());
|
||||||
return OperationResponse.success(OP_UPDATE, "Rollout updated successfully", response.getBody());
|
return OperationResponse.success(OP_UPDATE, "Rollout updated successfully", response.getBody());
|
||||||
} else if (request instanceof RolloutRequest.Delete r) {
|
} else if (request instanceof RolloutRequest.Delete r) {
|
||||||
validateRolloutOperation("delete");
|
validateRolloutOperation(OP_DELETE);
|
||||||
if (r.rolloutId() == null) {
|
if (r.rolloutId() == null) {
|
||||||
return OperationResponse.failure(OP_DELETE, "rolloutId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "rolloutId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -344,26 +354,26 @@ public class HawkbitMcpToolProvider {
|
|||||||
final MgmtDistributionSetRestApi api = hawkbitClient.mgmtService(MgmtDistributionSetRestApi.class, dummyTenant);
|
final MgmtDistributionSetRestApi api = hawkbitClient.mgmtService(MgmtDistributionSetRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof DistributionSetRequest.Create r) {
|
if (request instanceof DistributionSetRequest.Create r) {
|
||||||
validateOperation("create", "distributionSets");
|
validateOperation(OP_CREATE, DISTRIBUTION_SETS);
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_CREATE, "body is required for CREATE operation");
|
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<List<MgmtDistributionSet>> response = api.createDistributionSets(List.of(r.body()));
|
final ResponseEntity<List<MgmtDistributionSet>> response = api.createDistributionSets(List.of(r.body()));
|
||||||
final List<MgmtDistributionSet> created = response.getBody();
|
final List<MgmtDistributionSet> created = response.getBody();
|
||||||
return OperationResponse.success(OP_CREATE, "Distribution set created successfully",
|
return OperationResponse.success(OP_CREATE, "Distribution set created successfully",
|
||||||
created != null && !created.isEmpty() ? created.get(0) : null);
|
created != null && !created.isEmpty() ? created.get(0) : null);
|
||||||
} else if (request instanceof DistributionSetRequest.Update r) {
|
} else if (request instanceof DistributionSetRequest.Update r) {
|
||||||
validateOperation("update", "distributionSets");
|
validateOperation(OP_UPDATE, DISTRIBUTION_SETS);
|
||||||
if (r.distributionSetId() == null) {
|
if (r.distributionSetId() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "distributionSetId is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, "distributionSetId is required for UPDATE operation");
|
||||||
}
|
}
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "body is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtDistributionSet> response = api.updateDistributionSet(r.distributionSetId(), r.body());
|
final ResponseEntity<MgmtDistributionSet> response = api.updateDistributionSet(r.distributionSetId(), r.body());
|
||||||
return OperationResponse.success(OP_UPDATE, "Distribution set updated successfully", response.getBody());
|
return OperationResponse.success(OP_UPDATE, "Distribution set updated successfully", response.getBody());
|
||||||
} else if (request instanceof DistributionSetRequest.Delete r) {
|
} else if (request instanceof DistributionSetRequest.Delete r) {
|
||||||
validateOperation("delete", "distributionSets");
|
validateOperation(OP_DELETE, DISTRIBUTION_SETS);
|
||||||
if (r.distributionSetId() == null) {
|
if (r.distributionSetId() == null) {
|
||||||
return OperationResponse.failure(OP_DELETE, "distributionSetId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "distributionSetId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -384,7 +394,7 @@ public class HawkbitMcpToolProvider {
|
|||||||
final MgmtActionRestApi api = hawkbitClient.mgmtService(MgmtActionRestApi.class, dummyTenant);
|
final MgmtActionRestApi api = hawkbitClient.mgmtService(MgmtActionRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof ActionRequest.Delete r) {
|
if (request instanceof ActionRequest.Delete r) {
|
||||||
validateActionOperation("delete");
|
validateActionOperation(OP_DELETE);
|
||||||
if (r.actionId() == null) {
|
if (r.actionId() == null) {
|
||||||
return OperationResponse.failure(OP_DELETE, "actionId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "actionId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -414,26 +424,26 @@ public class HawkbitMcpToolProvider {
|
|||||||
final MgmtSoftwareModuleRestApi api = hawkbitClient.mgmtService(MgmtSoftwareModuleRestApi.class, dummyTenant);
|
final MgmtSoftwareModuleRestApi api = hawkbitClient.mgmtService(MgmtSoftwareModuleRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof SoftwareModuleRequest.Create r) {
|
if (request instanceof SoftwareModuleRequest.Create r) {
|
||||||
validateOperation("create", "softwareModules");
|
validateOperation(OP_CREATE, SOFTWARE_MODULES);
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_CREATE, "body is required for CREATE operation");
|
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<List<MgmtSoftwareModule>> response = api.createSoftwareModules(List.of(r.body()));
|
final ResponseEntity<List<MgmtSoftwareModule>> response = api.createSoftwareModules(List.of(r.body()));
|
||||||
final List<MgmtSoftwareModule> created = response.getBody();
|
final List<MgmtSoftwareModule> created = response.getBody();
|
||||||
return OperationResponse.success(OP_CREATE, "Software module created successfully",
|
return OperationResponse.success(OP_CREATE, "Software module created successfully",
|
||||||
created != null && !created.isEmpty() ? created.get(0) : null);
|
created != null && !created.isEmpty() ? created.get(0) : null);
|
||||||
} else if (request instanceof SoftwareModuleRequest.Update r) {
|
} else if (request instanceof SoftwareModuleRequest.Update r) {
|
||||||
validateOperation("update", "softwareModules");
|
validateOperation(OP_UPDATE, SOFTWARE_MODULES);
|
||||||
if (r.softwareModuleId() == null) {
|
if (r.softwareModuleId() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "softwareModuleId is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, "softwareModuleId is required for UPDATE operation");
|
||||||
}
|
}
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "body is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtSoftwareModule> response = api.updateSoftwareModule(r.softwareModuleId(), r.body());
|
final ResponseEntity<MgmtSoftwareModule> response = api.updateSoftwareModule(r.softwareModuleId(), r.body());
|
||||||
return OperationResponse.success(OP_UPDATE, "Software module updated successfully", response.getBody());
|
return OperationResponse.success(OP_UPDATE, "Software module updated successfully", response.getBody());
|
||||||
} else if (request instanceof SoftwareModuleRequest.Delete r) {
|
} else if (request instanceof SoftwareModuleRequest.Delete r) {
|
||||||
validateOperation("delete", "softwareModules");
|
validateOperation(OP_DELETE, SOFTWARE_MODULES);
|
||||||
if (r.softwareModuleId() == null) {
|
if (r.softwareModuleId() == null) {
|
||||||
return OperationResponse.failure(OP_DELETE, "softwareModuleId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "softwareModuleId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -455,24 +465,24 @@ public class HawkbitMcpToolProvider {
|
|||||||
final MgmtTargetFilterQueryRestApi api = hawkbitClient.mgmtService(MgmtTargetFilterQueryRestApi.class, dummyTenant);
|
final MgmtTargetFilterQueryRestApi api = hawkbitClient.mgmtService(MgmtTargetFilterQueryRestApi.class, dummyTenant);
|
||||||
|
|
||||||
if (request instanceof TargetFilterRequest.Create r) {
|
if (request instanceof TargetFilterRequest.Create r) {
|
||||||
validateOperation("create", "targetFilters");
|
validateOperation(OP_CREATE, TARGET_FILTERS);
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_CREATE, "body is required for CREATE operation");
|
return OperationResponse.failure(OP_CREATE, BODY_IS_REQUIRED_FOR_CREATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtTargetFilterQuery> response = api.createFilter(r.body());
|
final ResponseEntity<MgmtTargetFilterQuery> response = api.createFilter(r.body());
|
||||||
return OperationResponse.success(OP_CREATE, "Target filter created successfully", response.getBody());
|
return OperationResponse.success(OP_CREATE, "Target filter created successfully", response.getBody());
|
||||||
} else if (request instanceof TargetFilterRequest.Update r) {
|
} else if (request instanceof TargetFilterRequest.Update r) {
|
||||||
validateOperation("update", "targetFilters");
|
validateOperation(OP_UPDATE, TARGET_FILTERS);
|
||||||
if (r.filterId() == null) {
|
if (r.filterId() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "filterId is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, "filterId is required for UPDATE operation");
|
||||||
}
|
}
|
||||||
if (r.body() == null) {
|
if (r.body() == null) {
|
||||||
return OperationResponse.failure(OP_UPDATE, "body is required for UPDATE operation");
|
return OperationResponse.failure(OP_UPDATE, BODY_IS_REQUIRED_FOR_UPDATE_OPERATION);
|
||||||
}
|
}
|
||||||
final ResponseEntity<MgmtTargetFilterQuery> response = api.updateFilter(r.filterId(), r.body());
|
final ResponseEntity<MgmtTargetFilterQuery> response = api.updateFilter(r.filterId(), r.body());
|
||||||
return OperationResponse.success(OP_UPDATE, "Target filter updated successfully", response.getBody());
|
return OperationResponse.success(OP_UPDATE, "Target filter updated successfully", response.getBody());
|
||||||
} else if (request instanceof TargetFilterRequest.Delete r) {
|
} else if (request instanceof TargetFilterRequest.Delete r) {
|
||||||
validateOperation("delete", "targetFilters");
|
validateOperation(OP_DELETE, TARGET_FILTERS);
|
||||||
if (r.filterId() == null) {
|
if (r.filterId() == null) {
|
||||||
return OperationResponse.failure(OP_DELETE, "filterId is required for DELETE operation");
|
return OperationResponse.failure(OP_DELETE, "filterId is required for DELETE operation");
|
||||||
}
|
}
|
||||||
@@ -517,7 +527,7 @@ public class HawkbitMcpToolProvider {
|
|||||||
final Boolean entitySetting = config.getOperationEnabled(operation);
|
final Boolean entitySetting = config.getOperationEnabled(operation);
|
||||||
|
|
||||||
if (entitySetting == null) {
|
if (entitySetting == null) {
|
||||||
if (operation.equals("delete") && !properties.getOperations().isGlobalOperationEnabled("delete")) {
|
if (operation.equals("delete") && !properties.getOperations().isGlobalOperationEnabled(OP_DELETE)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Operation " + operation.toUpperCase() + " is not enabled for actions. " +
|
"Operation " + operation.toUpperCase() + " is not enabled for actions. " +
|
||||||
"Check hawkbit.mcp.operations configuration.");
|
"Check hawkbit.mcp.operations configuration.");
|
||||||
@@ -547,7 +557,7 @@ public class HawkbitMcpToolProvider {
|
|||||||
private HawkbitMcpProperties.EntityConfig getEntityConfig(final String entity) {
|
private HawkbitMcpProperties.EntityConfig getEntityConfig(final String entity) {
|
||||||
final HawkbitMcpProperties.Operations ops = properties.getOperations();
|
final HawkbitMcpProperties.Operations ops = properties.getOperations();
|
||||||
return switch (entity.toLowerCase()) {
|
return switch (entity.toLowerCase()) {
|
||||||
case "targets" -> ops.getTargets();
|
case TARGETS -> ops.getTargets();
|
||||||
case "rollouts" -> ops.getRollouts();
|
case "rollouts" -> ops.getRollouts();
|
||||||
case "distributionsets" -> ops.getDistributionSets();
|
case "distributionsets" -> ops.getDistributionSets();
|
||||||
case "softwaremodules" -> ops.getSoftwareModules();
|
case "softwaremodules" -> ops.getSoftwareModules();
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
package org.eclipse.hawkbit.repository.model;
|
package org.eclipse.hawkbit.repository.model;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@@ -77,6 +79,8 @@ public class DeploymentRequest {
|
|||||||
@Setter
|
@Setter
|
||||||
private long forceTime = RepositoryModelConstants.NO_FORCE_TIME;
|
private long forceTime = RepositoryModelConstants.NO_FORCE_TIME;
|
||||||
@Setter
|
@Setter
|
||||||
|
@Min(Action.WEIGHT_MIN)
|
||||||
|
@Max(Action.WEIGHT_MAX)
|
||||||
private ActionType actionType = ActionType.FORCED;
|
private ActionType actionType = ActionType.FORCED;
|
||||||
private String maintenanceSchedule;
|
private String maintenanceSchedule;
|
||||||
private String maintenanceWindowDuration;
|
private String maintenanceWindowDuration;
|
||||||
@@ -85,8 +89,7 @@ public class DeploymentRequest {
|
|||||||
private boolean confirmationRequired;
|
private boolean confirmationRequired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a builder for a target distribution set assignment with the
|
* Create a builder for a target distribution set assignment with the mandatory fields
|
||||||
* mandatory fields
|
|
||||||
*
|
*
|
||||||
* @param controllerId ID of the target
|
* @param controllerId ID of the target
|
||||||
* @param distributionSetId ID of the distribution set
|
* @param distributionSetId ID of the distribution set
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
|
|||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
import org.eclipse.hawkbit.repository.model.Action.Status;
|
import org.eclipse.hawkbit.repository.model.Action.Status;
|
||||||
@@ -170,7 +169,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
|||||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||||
public List<DistributionSetAssignmentResult> assignDistributionSets(
|
public List<DistributionSetAssignmentResult> assignDistributionSets(
|
||||||
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
|
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
|
||||||
WeightValidationHelper.validate(deploymentRequests);
|
|
||||||
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
|
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRollou
|
|||||||
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
|
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
|
import org.eclipse.hawkbit.repository.model.ActionCancellationType;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
@@ -718,8 +717,6 @@ public class JpaRolloutManagement implements RolloutManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
|
private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
|
||||||
WeightValidationHelper.validate(rollout);
|
|
||||||
|
|
||||||
rollout.setCreatedAt(System.currentTimeMillis());
|
rollout.setCreatedAt(System.currentTimeMillis());
|
||||||
|
|
||||||
final JpaDistributionSet distributionSet = rollout.getDistributionSet();
|
final JpaDistributionSet distributionSet = rollout.getDistributionSet();
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
|
|||||||
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
|
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
|
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
@@ -150,7 +149,6 @@ class JpaTargetFilterQueryManagement
|
|||||||
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
targetFilterQuery.setAutoAssignInitiatedBy(null);
|
||||||
targetFilterQuery.setConfirmationRequired(false);
|
targetFilterQuery.setConfirmationRequired(false);
|
||||||
} else {
|
} else {
|
||||||
WeightValidationHelper.validate(update);
|
|
||||||
assertMaxTargetsQuota(targetFilterQuery.getQuery(), targetFilterQuery.getName(), update.dsId());
|
assertMaxTargetsQuota(targetFilterQuery.getQuery(), targetFilterQuery.getName(), update.dsId());
|
||||||
|
|
||||||
DistributionSet distributionSet = distributionSetManagement.getValidAndComplete(update.dsId());
|
DistributionSet distributionSet = distributionSetManagement.getValidAndComplete(update.dsId());
|
||||||
@@ -215,10 +213,8 @@ class JpaTargetFilterQueryManagement
|
|||||||
QLSupport.getInstance().validate(query, TargetFields.class, JpaTarget.class);
|
QLSupport.getInstance().validate(query, TargetFields.class, JpaTarget.class);
|
||||||
|
|
||||||
// enforce the 'max targets per auto assign' quota right here even if the result of the filter query can vary over time
|
// enforce the 'max targets per auto assign' quota right here even if the result of the filter query can vary over time
|
||||||
Optional.ofNullable(create.getAutoAssignDistributionSet()).ifPresent(dsId -> {
|
Optional.ofNullable(create.getAutoAssignDistributionSet())
|
||||||
WeightValidationHelper.validate(create);
|
.ifPresent(dsId -> assertMaxTargetsQuota(query, create.getName(), dsId.getId()));
|
||||||
assertMaxTargetsQuota(query, create.getName(), dsId.getId());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
if (create.getAutoAssignWeight() == null) {
|
if (create.getAutoAssignWeight() == null) {
|
||||||
create.setAutoAssignWeight(create.getAutoAssignDistributionSet() == null ? 0 : repositoryProperties.getActionWeightIfAbsent());
|
create.setAutoAssignWeight(create.getAutoAssignDistributionSet() == null ? 0 : repositoryProperties.getActionWeightIfAbsent());
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2019 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.utils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.NoWeightProvidedInMultiAssignmentModeException;
|
|
||||||
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
|
|
||||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to handle weight validation in Rollout, Auto Assignments, and Online Assignment.
|
|
||||||
*/
|
|
||||||
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
|
||||||
public final class WeightValidationHelper {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating weights associated with all the {@link DeploymentRequest}s
|
|
||||||
*
|
|
||||||
* @param deploymentRequests the {@linkplain List} of {@link DeploymentRequest}s
|
|
||||||
*/
|
|
||||||
public static void validate(final List<DeploymentRequest> deploymentRequests) {
|
|
||||||
final long assignmentsWithWeight = deploymentRequests.stream()
|
|
||||||
.filter(request -> request.getTargetWithActionType().getWeight() != null).count();
|
|
||||||
final boolean containsAssignmentWithWeight = assignmentsWithWeight > 0;
|
|
||||||
final boolean containsAssignmentWithoutWeight = assignmentsWithWeight < deploymentRequests.size();
|
|
||||||
|
|
||||||
validateWeight(containsAssignmentWithWeight, containsAssignmentWithoutWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating weight associated with the {@link Rollout}
|
|
||||||
*
|
|
||||||
* @param rollout the {@linkplain Rollout}
|
|
||||||
*/
|
|
||||||
public static void validate(final Rollout rollout) {
|
|
||||||
validateWeight(rollout.getWeight().orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating weight associated with the target filter query
|
|
||||||
*
|
|
||||||
* @param targetFilterQueryCreate the target filter query
|
|
||||||
*/
|
|
||||||
public static void validate(final TargetFilterQueryManagement.Create targetFilterQueryCreate) {
|
|
||||||
validateWeight(targetFilterQueryCreate.getAutoAssignWeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating weight associated with the auto assignment
|
|
||||||
*
|
|
||||||
* @param autoAssignDistributionSetUpdate the auto assignment distribution set update
|
|
||||||
*/
|
|
||||||
public static void validate(final TargetFilterQueryManagement.AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate) {
|
|
||||||
validateWeight(autoAssignDistributionSetUpdate.weight());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the weight is valid
|
|
||||||
*
|
|
||||||
* @param weight weight tied to the rollout, auto assignment, or online assignment.
|
|
||||||
*/
|
|
||||||
public static void validateWeight(final Integer weight) {
|
|
||||||
final boolean hasWeight = weight != null;
|
|
||||||
validateWeight(hasWeight, !hasWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the weight is valid with the multi-assignments being turned off/on.
|
|
||||||
*
|
|
||||||
* @param hasWeight indicator of the weight if it has numerical value
|
|
||||||
* @param hasNoWeight indicator of the weight if it doesn't have a numerical value
|
|
||||||
*/
|
|
||||||
public static void validateWeight(final boolean hasWeight, final boolean hasNoWeight) {
|
|
||||||
// remove bypassing the weight enforcement as soon as weight can be set via UI
|
|
||||||
final boolean bypassWeightEnforcement = true;
|
|
||||||
if (!bypassWeightEnforcement && hasNoWeight) {
|
|
||||||
throw new NoWeightProvidedInMultiAssignmentModeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,11 +49,11 @@ public class Tenant {
|
|||||||
|
|
||||||
// amqp settings (if DMF is used)
|
// amqp settings (if DMF is used)
|
||||||
@Nullable
|
@Nullable
|
||||||
private DMF dmf;
|
private Dmf dmf;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@ToString
|
@ToString
|
||||||
public static class DMF {
|
public static class Dmf {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String virtualHost;
|
private String virtualHost;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import java.security.NoSuchAlgorithmException;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.sdk.Tenant.DMF;
|
import org.eclipse.hawkbit.sdk.Tenant;
|
||||||
|
import org.eclipse.hawkbit.sdk.Tenant.Dmf;
|
||||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||||
@@ -40,7 +41,7 @@ public class Amqp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S3358") // java:S3358
|
@SuppressWarnings("java:S3358") // java:S3358
|
||||||
public VHost getVhost(final DMF dmf, final boolean initVHost) {
|
public VHost getVhost(final Tenant.Dmf dmf, final boolean initVHost) {
|
||||||
final String vHost = dmf == null || ObjectUtils.isEmpty(dmf.getVirtualHost()) ?
|
final String vHost = dmf == null || ObjectUtils.isEmpty(dmf.getVirtualHost()) ?
|
||||||
(rabbitProperties.getVirtualHost() == null ? "/" : rabbitProperties.getVirtualHost()) :
|
(rabbitProperties.getVirtualHost() == null ? "/" : rabbitProperties.getVirtualHost()) :
|
||||||
dmf.getVirtualHost();
|
dmf.getVirtualHost();
|
||||||
@@ -48,7 +49,7 @@ public class Amqp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S4449") // java:S4449 - setUsername/Password is called with non-null
|
@SuppressWarnings("java:S4449") // java:S4449 - setUsername/Password is called with non-null
|
||||||
private ConnectionFactory getConnectionFactory(final DMF dmf, final String vHost) {
|
private ConnectionFactory getConnectionFactory(final Dmf dmf, final String vHost) {
|
||||||
final CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
final CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||||
connectionFactory.setHost(rabbitProperties.getHost());
|
connectionFactory.setHost(rabbitProperties.getHost());
|
||||||
connectionFactory.setPort(rabbitProperties.determinePort());
|
connectionFactory.setPort(rabbitProperties.determinePort());
|
||||||
|
|||||||
Reference in New Issue
Block a user