From 73f0a62f481bcdca1d855d693577be07e5ab8d35 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Mon, 6 Jan 2025 16:36:15 +0800 Subject: [PATCH] feat: Support generating default routes pointing to the landing page (#399) --- .licenserc.yaml | 1 + .../console/aop/ApiStandardizationAspect.java | 6 +- .../higress/console/config/SdkConfig.java | 11 +- .../console/constant/SystemConfigKey.java | 16 +- .../console/constant/UserConfigKey.java | 2 + .../controller/DashboardController.java | 4 +- .../console/controller/LandingController.java | 53 ++++++ .../console/controller/SessionController.java | 4 +- .../console/controller/SystemController.java | 38 +--- .../console/controller/UserController.java | 2 +- .../controller/dto/SystemInitRequest.java | 2 + .../dto => model}/DashboardInfo.java | 2 +- .../dto => model}/DashboardType.java | 2 +- .../{controller/dto => model}/SystemInfo.java | 62 +++---- .../{controller/dto => model}/User.java | 2 +- .../console/service/ConfigServiceImpl.java | 6 +- .../console/service/DashboardService.java | 4 +- .../console/service/DashboardServiceImpl.java | 4 +- .../console/service/SessionService.java | 4 +- .../console/service/SessionServiceImpl.java | 2 +- .../console/service/SessionUserHelper.java | 2 +- .../console/service/SystemService.java | 7 +- .../console/service/SystemServiceImpl.java | 166 +++++++++++++++++- .../higress/console/util/CertificateUtil.java | 84 +++++++++ .../src/main/resources/landing/index.html | 40 +++++ .../sdk/config/HigressServiceConfig.java | 35 +++- .../sdk/constant/HigressConstants.java | 1 + .../com/alibaba/higress/sdk/model/Route.java | 2 + .../higress/sdk/model/RoutePageQuery.java | 2 + .../sdk/service/DomainServiceImpl.java | 19 +- .../higress/sdk/service/RouteServiceImpl.java | 39 +++- .../kubernetes/KubernetesClientService.java | 133 ++++++++++---- .../kubernetes/KubernetesModelConverter.java | 9 +- .../service/kubernetes/KubernetesUtil.java | 18 ++ .../KubernetesModelConverterTest.java | 147 ++++++++++++++-- helm/templates/deployment.yaml | 12 +- helm/values.yaml | 8 + 37 files changed, 787 insertions(+), 164 deletions(-) create mode 100644 backend/console/src/main/java/com/alibaba/higress/console/controller/LandingController.java rename backend/console/src/main/java/com/alibaba/higress/console/{controller/dto => model}/DashboardInfo.java (94%) rename backend/console/src/main/java/com/alibaba/higress/console/{controller/dto => model}/DashboardType.java (93%) rename backend/console/src/main/java/com/alibaba/higress/console/{controller/dto => model}/SystemInfo.java (91%) rename backend/console/src/main/java/com/alibaba/higress/console/{controller/dto => model}/User.java (94%) create mode 100644 backend/console/src/main/java/com/alibaba/higress/console/util/CertificateUtil.java create mode 100644 backend/console/src/main/resources/landing/index.html diff --git a/.licenserc.yaml b/.licenserc.yaml index ae88d711..afe1d5d9 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -20,6 +20,7 @@ header: - 'backend/**/pom.xml' - 'backend/*.sh' - 'backend/**/*.json' + - 'backend/**/*.html' - 'backend/**/*.md' - 'backend/**/*.yaml' - 'backend/.mvn/**' diff --git a/backend/console/src/main/java/com/alibaba/higress/console/aop/ApiStandardizationAspect.java b/backend/console/src/main/java/com/alibaba/higress/console/aop/ApiStandardizationAspect.java index bd02ea7c..ee829592 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/aop/ApiStandardizationAspect.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/aop/ApiStandardizationAspect.java @@ -27,11 +27,12 @@ import com.alibaba.higress.console.context.HttpContext; import com.alibaba.higress.console.controller.HealthzController; +import com.alibaba.higress.console.controller.LandingController; import com.alibaba.higress.console.controller.SessionController; import com.alibaba.higress.console.controller.SystemController; import com.alibaba.higress.console.controller.dto.Response; -import com.alibaba.higress.console.controller.dto.User; import com.alibaba.higress.console.controller.exception.AuthException; +import com.alibaba.higress.console.model.User; import com.alibaba.higress.console.service.SessionService; import com.alibaba.higress.console.service.SessionUserHelper; import com.alibaba.higress.sdk.exception.BusinessException; @@ -104,6 +105,9 @@ private static boolean isLoginRequired(ProceedingJoinPoint point) { if (point.getTarget() instanceof HealthzController) { return false; } + if (point.getTarget() instanceof LandingController) { + return false; + } if (point.getTarget() instanceof SessionController) { return false; } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java b/backend/console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java index 0120c645..59fc0319 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/config/SdkConfig.java @@ -50,9 +50,11 @@ public class SdkConfig { @Value("${" + SystemConfigKey.NS_KEY + ":" + HigressConstants.NS_DEFAULT + "}") private String controllerNamespace = HigressConstants.NS_DEFAULT; - @Value("${" + SystemConfigKey.CONTROLLER_INGRESS_CLASS_NAME_KEY + ":" - + HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT + "}") - private String controllerIngressClassName = HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT; + @Value("${" + SystemConfigKey.CONTROLLER_WATCHED_NAMESPACE_KEY + ":}") + private String controllerWatchedNamespace; + + @Value("${" + SystemConfigKey.CONTROLLER_INGRESS_CLASS_NAME_KEY + ":}") + private String controllerWatchedIngressClassName; @Value("${" + SystemConfigKey.CONTROLLER_SERVICE_HOST_KEY + ":" + HigressConstants.CONTROLLER_SERVICE_HOST_DEFAULT + "}") @@ -74,7 +76,8 @@ public class SdkConfig { @PostConstruct public void initialize() throws IOException { HigressServiceConfig config = HigressServiceConfig.builder().withKubeConfigPath(kubeConfig) - .withIngressClassName(controllerIngressClassName).withControllerNamespace(controllerNamespace) + .withControllerNamespace(controllerNamespace).withControllerWatchedNamespace(controllerWatchedNamespace) + .withControllerWatchedIngressClassName(controllerWatchedIngressClassName) .withControllerServiceName(controllerServiceName).withControllerServiceHost(controllerServiceHost) .withControllerServicePort(controllerServicePort).withControllerJwtPolicy(controllerJwtPolicy) .withControllerAccessToken(controllerAccessToken).build(); diff --git a/backend/console/src/main/java/com/alibaba/higress/console/constant/SystemConfigKey.java b/backend/console/src/main/java/com/alibaba/higress/console/constant/SystemConfigKey.java index 5467a6f7..87582b17 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/constant/SystemConfigKey.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/constant/SystemConfigKey.java @@ -34,10 +34,20 @@ public class SystemConfigKey { public static final String CONTROLLER_SERVICE_HOST_KEY = CONFIG_KEY_PREFIX + "controller.service.host"; + public static final String CONTROLLER_WATCHED_NAMESPACE_KEY = CONFIG_KEY_PREFIX + "controller.watched-namespace"; + public static final String CONTROLLER_INGRESS_CLASS_NAME_KEY = CONFIG_KEY_PREFIX + "controller.ingress-class-name"; public static final String CONTROLLER_SERVICE_NAME_KEY = CONFIG_KEY_PREFIX + "controller.service.name"; + public static final String CONSOLE_SERVICE_HOST_KEY = CONFIG_KEY_PREFIX + "service.host"; + + public static final String DEFAULT_CONSOLE_SERVICE_HOST = "higress-console.higress-system.svc.cluster.local"; + + public static final String CONSOLE_SERVICE_PORT_KEY = CONFIG_KEY_PREFIX + "service.port"; + + public static final int DEFAULT_CONSOLE_SERVICE_PORT = 8080; + public static final String CONFIG_MAP_NAME_KEY = CONFIG_KEY_PREFIX + "config-map.name"; public static final String CONFIG_MAP_NAME_KEY_DEFAULT = "higress-console"; @@ -71,13 +81,15 @@ public class SystemConfigKey { public static final String DASHBOARD_PASSWORD_DEFAULT = "admin"; - public static final String DASHBOARD_DATASOURCE_PROM_NAME_KEY = CONFIG_KEY_PREFIX + "dashboard.datasource.prom.name"; + public static final String DASHBOARD_DATASOURCE_PROM_NAME_KEY = + CONFIG_KEY_PREFIX + "dashboard.datasource.prom.name"; public static final String DASHBOARD_DATASOURCE_PROM_NAME_DEFAULT = "Prometheus"; public static final String DASHBOARD_DATASOURCE_PROM_URL_KEY = CONFIG_KEY_PREFIX + "dashboard.datasource.prom.url"; - public static final String DASHBOARD_DATASOURCE_LOKI_NAME_KEY = CONFIG_KEY_PREFIX + "dashboard.datasource.loki.name"; + public static final String DASHBOARD_DATASOURCE_LOKI_NAME_KEY = + CONFIG_KEY_PREFIX + "dashboard.datasource.loki.name"; public static final String DASHBOARD_DATASOURCE_LOKI_NAME_DEFAULT = "Loki"; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/constant/UserConfigKey.java b/backend/console/src/main/java/com/alibaba/higress/console/constant/UserConfigKey.java index 4a13d8d1..84ece696 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/constant/UserConfigKey.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/constant/UserConfigKey.java @@ -21,6 +21,7 @@ public class UserConfigKey { private UserConfigKey() {} + public static final String DEFAULT_ROUTE_INITIALIZED = "route.default.initialized"; public static final String SYSTEM_INITIALIZED = "system.initialized"; public static final String LOGIN_PAGE_PROMPT_KEY = "login.prompt"; public static final String DASHBOARD_URL = "dashboard.url"; @@ -37,6 +38,7 @@ private UserConfigKey() {} CONFIG_VALUE_TYPES.put(CHAT_ENABLED, Boolean.class); CONFIG_VALUE_TYPES.put(ADMIN_PASSWORD_CHANGE_DISABLED, Boolean.class); CONFIG_VALUE_TYPES.put(DASHBOARD_BUILTIN, Boolean.class); + CONFIG_VALUE_TYPES.put(DEFAULT_ROUTE_INITIALIZED, Boolean.class); CONFIG_VALUE_TYPES.put(SYSTEM_INITIALIZED, Boolean.class); } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/DashboardController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/DashboardController.java index c3cb12c9..c117cd0e 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/DashboardController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/DashboardController.java @@ -25,9 +25,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.alibaba.higress.console.controller.dto.DashboardInfo; -import com.alibaba.higress.console.controller.dto.DashboardType; import com.alibaba.higress.console.controller.dto.Response; +import com.alibaba.higress.console.model.DashboardInfo; +import com.alibaba.higress.console.model.DashboardType; import com.alibaba.higress.console.service.DashboardService; import com.alibaba.higress.sdk.exception.ValidationException; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/LandingController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/LandingController.java new file mode 100644 index 00000000..7a71ad4b --- /dev/null +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/LandingController.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022-2025 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.alibaba.higress.console.controller; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import javax.annotation.PostConstruct; + +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequestMapping("/landing") +public class LandingController { + + private static final String BUILTIN_LANDING_PAGE = "landing/index.html"; + private static final String DEFAULT_LANDING_PAGE_HTML = "

Thanks for using Higress.

"; + + private String landingPageHtml; + + @PostConstruct + public void initialize() { + String landingPageHtml; + try (InputStream stream = getClass().getClassLoader().getResourceAsStream(BUILTIN_LANDING_PAGE)) { + landingPageHtml = StreamUtils.copyToString(stream, StandardCharsets.UTF_8); + } catch (IOException ex) { + log.error("Error occurs when loading the built-in landing page.", ex); + landingPageHtml = DEFAULT_LANDING_PAGE_HTML; + } + this.landingPageHtml = landingPageHtml; + } + + @RequestMapping(produces = "text/html") + public String index() { + return landingPageHtml; + } +} diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/SessionController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/SessionController.java index 75834d73..cda8e670 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/SessionController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/SessionController.java @@ -25,11 +25,11 @@ import com.alibaba.higress.console.controller.dto.LoginRequest; import com.alibaba.higress.console.controller.dto.Response; -import com.alibaba.higress.console.controller.dto.User; import com.alibaba.higress.console.controller.exception.AuthException; -import com.alibaba.higress.sdk.exception.ValidationException; import com.alibaba.higress.console.controller.util.ControllerUtil; +import com.alibaba.higress.console.model.User; import com.alibaba.higress.console.service.SessionService; +import com.alibaba.higress.sdk.exception.ValidationException; /** * @author CH3CHO diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/SystemController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/SystemController.java index 9b11c828..015a32aa 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/SystemController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/SystemController.java @@ -17,9 +17,6 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; - -import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -33,13 +30,11 @@ import com.alibaba.higress.console.constant.UserConfigKey; import com.alibaba.higress.console.controller.dto.Response; -import com.alibaba.higress.console.controller.dto.SystemInfo; import com.alibaba.higress.console.controller.dto.SystemInitRequest; -import com.alibaba.higress.console.controller.dto.User; import com.alibaba.higress.console.controller.util.ControllerUtil; +import com.alibaba.higress.console.model.SystemInfo; +import com.alibaba.higress.console.model.User; import com.alibaba.higress.console.service.ConfigService; -import com.alibaba.higress.console.service.DashboardService; -import com.alibaba.higress.console.service.SessionService; import com.alibaba.higress.console.service.SystemService; import com.alibaba.higress.sdk.exception.ValidationException; @@ -51,21 +46,9 @@ @Validated public class SystemController { - private DashboardService dashboardService; - private SessionService sessionService; private ConfigService configService; private SystemService systemService; - @Autowired - public void setDashboardService(DashboardService dashboardService) { - this.dashboardService = dashboardService; - } - - @Autowired - public void setSessionService(SessionService sessionService) { - this.sessionService = sessionService; - } - @Autowired public void setConfigService(ConfigService configService) { this.configService = configService; @@ -76,12 +59,6 @@ public void setSystemService(SystemService systemService) { this.systemService = systemService; } - @PostConstruct - public void syncSystemState() { - configService.setConfig(UserConfigKey.SYSTEM_INITIALIZED, sessionService.isAdminInitialized()); - configService.setConfig(UserConfigKey.DASHBOARD_BUILTIN, dashboardService.isBuiltIn()); - } - @PostMapping("/init") public ResponseEntity initialize(@RequestBody SystemInitRequest request) { User adminUser = request.getAdminUser(); @@ -92,16 +69,7 @@ public ResponseEntity initialize(@RequestBody SystemInitRequest request) { throw new ValidationException("Incomplete adminUser object."); } - if (!sessionService.isAdminInitialized()) { - sessionService.initializeAdmin(adminUser); - } - - Map configs = new HashMap<>(); - if (MapUtils.isNotEmpty(request.getConfigs())) { - configs.putAll(request.getConfigs()); - } - configs.put(UserConfigKey.SYSTEM_INITIALIZED, true); - configService.setConfigs(configs); + systemService.initSystem(adminUser, request.getConfigs()); return ControllerUtil.buildSuccessResponseEntity(); } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/UserController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/UserController.java index b1263bae..dcb08463 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/UserController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/UserController.java @@ -25,7 +25,7 @@ import com.alibaba.higress.console.controller.dto.ChangePasswordRequest; import com.alibaba.higress.console.controller.dto.Response; -import com.alibaba.higress.console.controller.dto.User; +import com.alibaba.higress.console.model.User; import com.alibaba.higress.sdk.exception.ValidationException; import com.alibaba.higress.console.controller.util.ControllerUtil; import com.alibaba.higress.console.service.SessionService; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInitRequest.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInitRequest.java index 5ef56b80..db78d051 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInitRequest.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInitRequest.java @@ -14,6 +14,8 @@ import java.util.Map; +import com.alibaba.higress.console.model.User; + import io.swagger.annotations.ApiModel; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardInfo.java b/backend/console/src/main/java/com/alibaba/higress/console/model/DashboardInfo.java similarity index 94% rename from backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardInfo.java rename to backend/console/src/main/java/com/alibaba/higress/console/model/DashboardInfo.java index 065800d3..b35a8a8e 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardInfo.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/model/DashboardInfo.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package com.alibaba.higress.console.controller.dto; +package com.alibaba.higress.console.model; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardType.java b/backend/console/src/main/java/com/alibaba/higress/console/model/DashboardType.java similarity index 93% rename from backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardType.java rename to backend/console/src/main/java/com/alibaba/higress/console/model/DashboardType.java index 83f8e134..6dba4b41 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/DashboardType.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/model/DashboardType.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package com.alibaba.higress.console.controller.dto; +package com.alibaba.higress.console.model; public enum DashboardType { diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInfo.java b/backend/console/src/main/java/com/alibaba/higress/console/model/SystemInfo.java similarity index 91% rename from backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInfo.java rename to backend/console/src/main/java/com/alibaba/higress/console/model/SystemInfo.java index bc0d93d8..8cb1f3c6 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/SystemInfo.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/model/SystemInfo.java @@ -1,31 +1,31 @@ -/* - * Copyright (c) 2022-2023 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package com.alibaba.higress.console.controller.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class SystemInfo { - - private String version; - - private List capabilities; -} +/* + * Copyright (c) 2022-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.alibaba.higress.console.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SystemInfo { + + private String version; + + private List capabilities; +} diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/User.java b/backend/console/src/main/java/com/alibaba/higress/console/model/User.java similarity index 94% rename from backend/console/src/main/java/com/alibaba/higress/console/controller/dto/User.java rename to backend/console/src/main/java/com/alibaba/higress/console/model/User.java index f5b683bd..d4b5f2d0 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/dto/User.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/model/User.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package com.alibaba.higress.console.controller.dto; +package com.alibaba.higress.console.model; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/ConfigServiceImpl.java b/backend/console/src/main/java/com/alibaba/higress/console/service/ConfigServiceImpl.java index 162f5223..c16ec0ea 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/ConfigServiceImpl.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/ConfigServiceImpl.java @@ -26,6 +26,7 @@ import com.alibaba.higress.console.constant.SystemConfigKey; import com.alibaba.higress.sdk.exception.BusinessException; +import com.alibaba.higress.sdk.http.HttpStatus; import com.alibaba.higress.sdk.service.kubernetes.KubernetesClientService; import com.google.common.collect.ImmutableList; @@ -205,6 +206,9 @@ private V1ConfigMap getConfigMap() { try { return kubernetesClientService.readConfigMap(configMapName); } catch (ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND) { + return null; + } throw new BusinessException("Error occurs when reading ConfigMap " + configMapName, e); } } @@ -218,7 +222,7 @@ private V1ConfigMap initConfigMap() { try { return kubernetesClientService.createConfigMap(configMap); } catch (ApiException e) { - throw new BusinessException("Error occurs when reading ConfigMap " + configMapName, e); + throw new BusinessException("Error occurs when creating ConfigMap " + configMapName, e); } } } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardService.java b/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardService.java index 5f42b087..0efb93e7 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardService.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardService.java @@ -17,8 +17,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import com.alibaba.higress.console.controller.dto.DashboardInfo; -import com.alibaba.higress.console.controller.dto.DashboardType; +import com.alibaba.higress.console.model.DashboardInfo; +import com.alibaba.higress.console.model.DashboardType; /** * @author CH3CHO diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardServiceImpl.java b/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardServiceImpl.java index 742814d1..103e1136 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardServiceImpl.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/DashboardServiceImpl.java @@ -61,8 +61,8 @@ import com.alibaba.higress.console.client.grafana.models.SearchType; import com.alibaba.higress.console.constant.SystemConfigKey; import com.alibaba.higress.console.constant.UserConfigKey; -import com.alibaba.higress.console.controller.dto.DashboardInfo; -import com.alibaba.higress.console.controller.dto.DashboardType; +import com.alibaba.higress.console.model.DashboardInfo; +import com.alibaba.higress.console.model.DashboardType; import com.alibaba.higress.sdk.exception.BusinessException; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ThreadFactoryBuilder; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionService.java b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionService.java index 98383958..e2d6066b 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionService.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionService.java @@ -12,11 +12,11 @@ */ package com.alibaba.higress.console.service; -import com.alibaba.higress.console.controller.dto.User; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.alibaba.higress.console.model.User; + /** * @author CH3CHO */ diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionServiceImpl.java b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionServiceImpl.java index fc14d9cb..955b9664 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionServiceImpl.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionServiceImpl.java @@ -31,7 +31,7 @@ import com.alibaba.higress.console.constant.SystemConfigKey; import com.alibaba.higress.console.constant.UserConfigKey; -import com.alibaba.higress.console.controller.dto.User; +import com.alibaba.higress.console.model.User; import com.alibaba.higress.console.util.AesUtil; import com.alibaba.higress.sdk.exception.BusinessException; import com.alibaba.higress.sdk.exception.ValidationException; diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionUserHelper.java b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionUserHelper.java index 749e988f..6e0709c8 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/SessionUserHelper.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/SessionUserHelper.java @@ -12,7 +12,7 @@ */ package com.alibaba.higress.console.service; -import com.alibaba.higress.console.controller.dto.User; +import com.alibaba.higress.console.model.User; /** * @author CH3CHO diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/SystemService.java b/backend/console/src/main/java/com/alibaba/higress/console/service/SystemService.java index 8aa53b34..9012d275 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/SystemService.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/SystemService.java @@ -12,9 +12,14 @@ */ package com.alibaba.higress.console.service; -import com.alibaba.higress.console.controller.dto.SystemInfo; +import java.util.Map; + +import com.alibaba.higress.console.model.SystemInfo; +import com.alibaba.higress.console.model.User; public interface SystemService { + void initSystem(User adminUser, Map configs); + SystemInfo getSystemInfo(); } diff --git a/backend/console/src/main/java/com/alibaba/higress/console/service/SystemServiceImpl.java b/backend/console/src/main/java/com/alibaba/higress/console/service/SystemServiceImpl.java index 1b84b928..345c09ca 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/service/SystemServiceImpl.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/service/SystemServiceImpl.java @@ -14,30 +14,56 @@ import java.io.IOException; import java.io.InputStream; +import java.security.KeyPair; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import javax.annotation.PostConstruct; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.cert.X509CertificateHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.alibaba.higress.console.constant.CapabilityKey; import com.alibaba.higress.console.constant.SystemConfigKey; -import com.alibaba.higress.console.controller.dto.SystemInfo; +import com.alibaba.higress.console.constant.UserConfigKey; +import com.alibaba.higress.console.model.SystemInfo; +import com.alibaba.higress.console.model.User; +import com.alibaba.higress.console.util.CertificateUtil; +import com.alibaba.higress.sdk.constant.HigressConstants; +import com.alibaba.higress.sdk.exception.ResourceConflictException; +import com.alibaba.higress.sdk.model.Domain; +import com.alibaba.higress.sdk.model.Route; +import com.alibaba.higress.sdk.model.TlsCertificate; +import com.alibaba.higress.sdk.model.route.RewriteConfig; +import com.alibaba.higress.sdk.model.route.RoutePredicate; +import com.alibaba.higress.sdk.model.route.RoutePredicateTypeEnum; +import com.alibaba.higress.sdk.model.route.UpstreamService; +import com.alibaba.higress.sdk.service.DomainService; +import com.alibaba.higress.sdk.service.RouteService; +import com.alibaba.higress.sdk.service.TlsCertificateService; import com.alibaba.higress.sdk.service.kubernetes.KubernetesClientService; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1Ingress; import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class SystemServiceImpl implements SystemService { + private static final String DEFAULT_TLS_CERTIFICATE_NAME = "default"; + private static final String DEFAULT_TLS_CERTIFICATE_HOST = "higress-gateway"; + private static final long DEFAULT_TLS_CERTIFICATE_DURATION = 1000L * 60 * 60 * 24 * 365; + private static final String DEFAULT_ROUTE_NAME = "default"; private static final String UNKNOWN = "unknown"; - private static final String COMMIT_ID; static { @@ -59,18 +85,60 @@ public class SystemServiceImpl implements SystemService { @Value("${" + SystemConfigKey.DEV_BUILD_KEY + ":" + SystemConfigKey.DEV_BUILD_DEFAULT + "}") private boolean devBuild = SystemConfigKey.DEV_BUILD_DEFAULT; + @Value("${" + SystemConfigKey.CONSOLE_SERVICE_HOST_KEY + ":" + SystemConfigKey.DEFAULT_CONSOLE_SERVICE_HOST + "}") + private String consoleServiceHost = SystemConfigKey.DEFAULT_CONSOLE_SERVICE_HOST; + + @Value("${" + SystemConfigKey.CONSOLE_SERVICE_PORT_KEY + ":" + SystemConfigKey.DEFAULT_CONSOLE_SERVICE_PORT + "}") + private int consoleServicePort = SystemConfigKey.DEFAULT_CONSOLE_SERVICE_PORT; + private String fullVersion; private List capabilities; + private DashboardService dashboardService; + private SessionService sessionService; + private ConfigService configService; + private TlsCertificateService tlsCertificateService; + private DomainService domainService; + private RouteService routeService; private KubernetesClientService kubernetesClientService; + @Autowired + public void setDashboardService(DashboardService dashboardService) { + this.dashboardService = dashboardService; + } + + @Autowired + public void setSessionService(SessionService sessionService) { + this.sessionService = sessionService; + } + + @Autowired + public void setConfigService(ConfigService configService) { + this.configService = configService; + } + + @Autowired + public void setTlsCertificateService(TlsCertificateService tlsCertificateService) { + this.tlsCertificateService = tlsCertificateService; + } + + @Autowired + public void setDomainService(DomainService domainService) { + this.domainService = domainService; + } + + @Autowired + public void setRouteService(RouteService routeService) { + this.routeService = routeService; + } + @Autowired public void setKubernetesClientService(KubernetesClientService kubernetesClientService) { this.kubernetesClientService = kubernetesClientService; } @PostConstruct - public void initialize() { + private void initInternalState() { fullVersion = StringUtils.isNotBlank(version) ? version : UNKNOWN; if (devBuild) { fullVersion += "-dev-" + COMMIT_ID; @@ -81,6 +149,98 @@ public void initialize() { capabilities.add(CapabilityKey.CONFIG_INGRESS_V1); } this.capabilities = capabilities; + + Map configs = Map.of(UserConfigKey.SYSTEM_INITIALIZED, sessionService.isAdminInitialized(), + UserConfigKey.DASHBOARD_BUILTIN, dashboardService.isBuiltIn()); + configService.setConfigs(configs); + + initDefaultRoutes(); + } + + @Override + public void initSystem(User adminUser, Map configs) { + if (configService.getBoolean(UserConfigKey.SYSTEM_INITIALIZED)) { + throw new IllegalStateException("System already initialized."); + } + + initAdminUser(adminUser); + initConfigs(configs); + } + + private void initAdminUser(User adminUser) { + if (!sessionService.isAdminInitialized()) { + sessionService.initializeAdmin(adminUser); + } + } + + private void initDefaultRoutes() { + boolean defaultRouteInitialized = configService.getBoolean(UserConfigKey.DEFAULT_ROUTE_INITIALIZED, false); + if (defaultRouteInitialized) { + return; + } + + List ingresses; + try { + ingresses = kubernetesClientService.listAllIngresses(); + } catch (ApiException e) { + log.error("Failed to list all ingresses. Skip default route initialization.", e); + return; + } + + if (CollectionUtils.isNotEmpty(ingresses)) { + log.info("Skip default route initialization because there are existing ingresses."); + configService.setConfig(UserConfigKey.DEFAULT_ROUTE_INITIALIZED, true); + return; + } + + try { + KeyPair keyPair = CertificateUtil.generateRsaKeyPair(4096); + X509CertificateHolder certificateHolder = CertificateUtil.generateSelfSignedCertificate(keyPair, + DEFAULT_TLS_CERTIFICATE_HOST, DEFAULT_TLS_CERTIFICATE_DURATION); + + TlsCertificate defaultCertificate = new TlsCertificate(); + defaultCertificate.setName(DEFAULT_TLS_CERTIFICATE_NAME); + defaultCertificate.setDomains(List.of(DEFAULT_TLS_CERTIFICATE_HOST)); + defaultCertificate.setKey( + CertificateUtil.toPem(CertificateUtil.RSA_PRIVATE_KEY_PEM_TYPE, keyPair.getPrivate().getEncoded())); + defaultCertificate + .setCert(CertificateUtil.toPem(CertificateUtil.CERTIFICATE_PEM_TYPE, certificateHolder.getEncoded())); + tlsCertificateService.add(defaultCertificate); + + Domain domain = new Domain(); + domain.setName(HigressConstants.DEFAULT_DOMAIN); + domain.setEnableHttps(Domain.EnableHttps.ON); + domain.setCertIdentifier(DEFAULT_TLS_CERTIFICATE_NAME); + domainService.add(domain); + } catch (ResourceConflictException e) { + // Ignore it. + } catch (Exception ex) { + log.error("Failed to init the default TLS certificate.", ex); + } + + try { + Route route = new Route(); + route.setName(DEFAULT_ROUTE_NAME); + RoutePredicate routePredicate = + RoutePredicate.builder().matchType(RoutePredicateTypeEnum.PRE.name()).matchValue("/").build(); + route.setPath(routePredicate); + route.setServices(List.of(new UpstreamService(consoleServiceHost, consoleServicePort, null, null))); + route.setRewrite(new RewriteConfig(true, "/landing", null)); + routeService.add(route); + + configService.setConfig(UserConfigKey.DEFAULT_ROUTE_INITIALIZED, true); + } catch (Exception ex) { + log.error("Failed to init the default route.", ex); + } + } + + private void initConfigs(Map configs) { + Map fullConfigs = new HashMap<>(); + if (MapUtils.isNotEmpty(configs)) { + fullConfigs.putAll(configs); + } + fullConfigs.put(UserConfigKey.SYSTEM_INITIALIZED, true); + configService.setConfigs(fullConfigs); } @Override diff --git a/backend/console/src/main/java/com/alibaba/higress/console/util/CertificateUtil.java b/backend/console/src/main/java/com/alibaba/higress/console/util/CertificateUtil.java new file mode 100644 index 00000000..06f194fb --- /dev/null +++ b/backend/console/src/main/java/com/alibaba/higress/console/util/CertificateUtil.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022-2025 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.alibaba.higress.console.util; + +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SecureRandom; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +public class CertificateUtil { + + public static final String RSA_PRIVATE_KEY_PEM_TYPE = "RSA PRIVATE KEY"; + public static final String CERTIFICATE_PEM_TYPE = "CERTIFICATE"; + + public static KeyPair generateRsaKeyPair(int keySize) throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize, new SecureRandom()); + return keyPairGenerator.generateKeyPair(); + } + + public static X509CertificateHolder generateSelfSignedCertificate(KeyPair keyPair, String host, long durationInMs) + throws OperatorException, IOException { + X500Principal subject = new X500Principal("CN=" + host); + + long notBefore = System.currentTimeMillis(); + long notAfter = notBefore + durationInMs; + + ASN1Encodable[] encodableAltNames = new ASN1Encodable[] {new GeneralName(GeneralName.dNSName, host)}; + KeyPurposeId[] purposes = new KeyPurposeId[] {KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}; + + X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(subject, BigInteger.ONE, + new Date(notBefore), new Date(notAfter), subject, keyPair.getPublic()); + + certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + certBuilder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature + KeyUsage.keyEncipherment)); + certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(purposes)); + certBuilder.addExtension(Extension.subjectAlternativeName, false, new DERSequence(encodableAltNames)); + + final ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); + return certBuilder.build(signer); + } + + public static String toPem(String type, byte[] content) throws IOException { + PemObject pemObject = new PemObject(type, content); + StringWriter stringWriter = new StringWriter(); + try (PemWriter pemWriter = new PemWriter(stringWriter)) { + pemWriter.writeObject(pemObject); + } + return stringWriter.toString(); + } +} diff --git a/backend/console/src/main/resources/landing/index.html b/backend/console/src/main/resources/landing/index.html new file mode 100644 index 00000000..e702a060 --- /dev/null +++ b/backend/console/src/main/resources/landing/index.html @@ -0,0 +1,40 @@ + + + + Welcome to Higress! + + + +

Thanks for using Higress!

+

+ Higress is successfully installed and is functioning properly. + Higress Console is available for further configuration. +

+

+ For online documentation, please visit higress.cn + or alibaba/higress on GitHub. +

+

Happy Higressing!

+

+ + Higress + +

+ + diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/config/HigressServiceConfig.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/config/HigressServiceConfig.java index 9b1aceb9..017de474 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/config/HigressServiceConfig.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/config/HigressServiceConfig.java @@ -27,21 +27,31 @@ public class HigressServiceConfig { private final String kubeConfigPath; - private final String ingressClassName; private final String controllerNamespace; + private final String controllerWatchedNamespace; + private final String controllerWatchedIngressClassName; private final String controllerServiceName; private final String controllerServiceHost; private final Integer controllerServicePort; private final String controllerJwtPolicy; private final String controllerAccessToken; + /** + * @deprecated use {@link #getControllerWatchedIngressClassName()} instead + */ + @Deprecated + public String getIngressClassName() { + return controllerWatchedIngressClassName; + } + public static HigressServiceConfig.Builder builder() { return new Builder(); } public static final class Builder { private String kubeConfigPath; - private String ingressClassName = HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT; + private String controllerWatchedNamespace; + private String controllerWatchedIngressClassName = HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT; private String controllerNamespace = HigressConstants.NS_DEFAULT; private String controllerServiceName = HigressConstants.CONTROLLER_SERVICE_NAME_DEFAULT; private String controllerServiceHost = HigressConstants.CONTROLLER_SERVICE_HOST_DEFAULT; @@ -56,16 +66,29 @@ public Builder withKubeConfigPath(String kubeConfigPath) { return this; } - public Builder withIngressClassName(String ingressClassName) { - this.ingressClassName = ingressClassName; + public Builder withControllerWatchedIngressClassName(String controllerWatchedIngressClassName) { + this.controllerWatchedIngressClassName = controllerWatchedIngressClassName; return this; } + /** + * @deprecated use {@link #withControllerWatchedIngressClassName(String)} instead + */ + @Deprecated + public Builder withIngressClassName(String ingressClassName) { + return withControllerWatchedIngressClassName(ingressClassName); + } + public Builder withControllerNamespace(String controllerNamespace) { this.controllerNamespace = controllerNamespace; return this; } + public Builder withControllerWatchedNamespace(String controllerWatchedNamespace) { + this.controllerWatchedNamespace = controllerWatchedNamespace; + return this; + } + public Builder withControllerServiceName(String controllerServiceName) { this.controllerServiceName = controllerServiceName; return this; @@ -93,8 +116,8 @@ public Builder withControllerAccessToken(String controllerAccessToken) { public HigressServiceConfig build() { return new HigressServiceConfig(kubeConfigPath, - StringUtils.firstNonEmpty(ingressClassName, HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT), - StringUtils.firstNonEmpty(controllerNamespace, HigressConstants.NS_DEFAULT), + StringUtils.firstNonEmpty(controllerNamespace, HigressConstants.NS_DEFAULT), controllerWatchedNamespace, + controllerWatchedIngressClassName, StringUtils.firstNonEmpty(controllerServiceName, HigressConstants.CONTROLLER_SERVICE_NAME_DEFAULT), StringUtils.firstNonEmpty(controllerServiceHost, HigressConstants.CONTROLLER_SERVICE_HOST_DEFAULT), Optional.ofNullable(controllerServicePort).orElse(HigressConstants.CONTROLLER_SERVICE_PORT_DEFAULT), diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/constant/HigressConstants.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/constant/HigressConstants.java index 982d76ac..f160c110 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/constant/HigressConstants.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/constant/HigressConstants.java @@ -16,6 +16,7 @@ public class HigressConstants { public static final String NS_DEFAULT = "higress-system"; public static final String CONTROLLER_SERVICE_NAME_DEFAULT = "higress-controller"; public static final String CONTROLLER_INGRESS_CLASS_NAME_DEFAULT = "higress"; + public static final String NGINX_INGRESS_CLASS_NAME = "nginx"; public static final String CONTROLLER_SERVICE_HOST_DEFAULT = "localhost"; public static final int CONTROLLER_SERVICE_PORT_DEFAULT = 15014; public static final String CONTROLLER_JWT_POLICY_DEFAULT = KubernetesConstants.JwtPolicy.THIRD_PARTY_JWT; diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/Route.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/Route.java index 1e505ed7..2acc468e 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/Route.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/Route.java @@ -76,4 +76,6 @@ public class Route implements VersionedDto { private HeaderControlConfig headerControl; private Map customConfigs; + + private Boolean readonly; } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/RoutePageQuery.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/RoutePageQuery.java index ef826661..6c1f9e32 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/RoutePageQuery.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/RoutePageQuery.java @@ -24,4 +24,6 @@ public class RoutePageQuery extends CommonPageQuery { private String domainName; + + private Boolean all; } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/DomainServiceImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/DomainServiceImpl.java index 2ddf4e2a..d92747bc 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/DomainServiceImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/DomainServiceImpl.java @@ -98,7 +98,9 @@ public Domain query(String domainName) { @Override public void delete(String domainName) { - PaginatedResult routes = routeService.list(new RoutePageQuery(domainName)); + RoutePageQuery query = new RoutePageQuery(); + query.setDomainName(domainName); + PaginatedResult routes = routeService.list(query); if (CollectionUtils.isNotEmpty(routes.getData())) { throw new IllegalArgumentException("The domain has routes. Please delete them first."); } @@ -133,9 +135,22 @@ public Domain put(Domain domain) { routes = routeQueryResult.getData().stream().filter(r -> CollectionUtils.isEmpty(r.getDomains())) .collect(Collectors.toList()); } else { - PaginatedResult routeQueryResult = routeService.list(new RoutePageQuery(domain.getName())); + RoutePageQuery query = new RoutePageQuery(); + query.setDomainName(domain.getName()); + PaginatedResult routeQueryResult = routeService.list(query); routes = routeQueryResult.getData(); } + + // TODO: Switch to the new logic after 2025/03/31 + // String domainName = domain.getName(); + // if (HigressConstants.DEFAULT_DOMAIN.equals(domainName)) { + // domainName = HigressConstants.DEFAULT_DOMAIN; + // } + // RoutePageQuery query = new RoutePageQuery(); + // query.setDomainName(domainName); + // PaginatedResult routeQueryResult = routeService.list(query); + // routes = routeQueryResult.getData(); + if (CollectionUtils.isNotEmpty(routes)) { routes.forEach(routeService::update); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/RouteServiceImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/RouteServiceImpl.java index c8e8f7f3..a21f95d9 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/RouteServiceImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/RouteServiceImpl.java @@ -20,6 +20,7 @@ import com.alibaba.higress.sdk.exception.BusinessException; import com.alibaba.higress.sdk.exception.ResourceConflictException; +import com.alibaba.higress.sdk.exception.ValidationException; import com.alibaba.higress.sdk.http.HttpStatus; import com.alibaba.higress.sdk.model.PaginatedResult; import com.alibaba.higress.sdk.model.Route; @@ -35,6 +36,8 @@ @Slf4j class RouteServiceImpl implements RouteService { + private static final RoutePageQuery DEFAULT_QUERY = new RoutePageQuery(); + private final KubernetesClientService kubernetesClientService; private final KubernetesModelConverter kubernetesModelConverter; private final WasmPluginInstanceService wasmPluginInstanceService; @@ -48,18 +51,36 @@ public RouteServiceImpl(KubernetesClientService kubernetesClientService, @Override public PaginatedResult list(RoutePageQuery query) { - List ingresses; - if (query != null && StringUtils.isNotEmpty(query.getDomainName())) { - ingresses = kubernetesClientService.listIngressByDomain(query.getDomainName()); - } else { - ingresses = kubernetesClientService.listIngress(); - } + List ingresses = listIngresses(query); + if (CollectionUtils.isEmpty(ingresses)) { return PaginatedResult.createFromFullList(Collections.emptyList(), query); } - List supportedIngresses = - ingresses.stream().filter(kubernetesModelConverter::isIngressSupported).toList(); - return PaginatedResult.createFromFullList(supportedIngresses, query, kubernetesModelConverter::ingress2Route); + return PaginatedResult.createFromFullList(ingresses, query, kubernetesModelConverter::ingress2Route); + } + + private List listIngresses(RoutePageQuery query) { + try { + if (query == null) { + query = DEFAULT_QUERY; + } + + if (StringUtils.isNotEmpty(query.getDomainName())) { + if (Boolean.TRUE.equals(query.getAll())) { + throw new ValidationException( + "The query parameter 'all' is not supported when querying by domain."); + } + return kubernetesClientService.listIngressByDomain(query.getDomainName()); + } + + if (Boolean.TRUE.equals(query.getAll())) { + return kubernetesClientService.listAllIngresses(); + } else { + return kubernetesClientService.listIngress(); + } + } catch (ApiException e) { + throw new BusinessException("Error occurs when listing Ingresses.", e); + } } @Override diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesClientService.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesClientService.java index be6abd45..7fe5de83 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesClientService.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesClientService.java @@ -28,9 +28,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; -import com.alibaba.higress.sdk.service.kubernetes.crd.istio.V1alpha3EnvoyFilter; -import io.kubernetes.client.util.Yaml; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; @@ -38,11 +37,13 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import com.alibaba.higress.sdk.config.HigressServiceConfig; +import com.alibaba.higress.sdk.constant.HigressConstants; import com.alibaba.higress.sdk.constant.KubernetesConstants; import com.alibaba.higress.sdk.constant.KubernetesConstants.Label; import com.alibaba.higress.sdk.constant.Separators; import com.alibaba.higress.sdk.exception.BusinessException; import com.alibaba.higress.sdk.http.HttpStatus; +import com.alibaba.higress.sdk.service.kubernetes.crd.istio.V1alpha3EnvoyFilter; import com.alibaba.higress.sdk.service.kubernetes.crd.mcp.V1McpBridge; import com.alibaba.higress.sdk.service.kubernetes.crd.mcp.V1McpBridgeList; import com.alibaba.higress.sdk.service.kubernetes.crd.wasm.V1alpha1WasmPlugin; @@ -62,6 +63,7 @@ import io.kubernetes.client.openapi.models.V1ConfigMapList; import io.kubernetes.client.openapi.models.V1Ingress; import io.kubernetes.client.openapi.models.V1IngressList; +import io.kubernetes.client.openapi.models.V1IngressSpec; import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1SecretList; @@ -69,6 +71,8 @@ import io.kubernetes.client.util.ClientBuilder; import io.kubernetes.client.util.KubeConfig; import io.kubernetes.client.util.Strings; +import io.kubernetes.client.util.Yaml; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -97,7 +101,9 @@ public class KubernetesClientService { private final String controllerNamespace; - private final String controllerIngressClassName; + private final String controllerWatchedIngressClassName; + + private final String controllerWatchedNamespace; private final String controllerServiceHost; @@ -107,6 +113,11 @@ public class KubernetesClientService { private final String controllerAccessToken; + private final Predicate isIngressWatched; + + private final String defaultIngressClass; + + @Getter private boolean ingressV1Supported; public KubernetesClientService(HigressServiceConfig config) throws IOException { @@ -117,10 +128,14 @@ public KubernetesClientService(HigressServiceConfig config) throws IOException { this.controllerServiceName = config.getControllerServiceName(); this.controllerServiceHost = config.getControllerServiceHost(); this.controllerServicePort = config.getControllerServicePort(); - this.controllerIngressClassName = config.getIngressClassName(); + this.controllerWatchedIngressClassName = config.getControllerWatchedIngressClassName(); + this.controllerWatchedNamespace = config.getControllerWatchedNamespace(); this.controllerJwtPolicy = config.getControllerJwtPolicy(); this.controllerAccessToken = config.getControllerAccessToken(); this.inClusterMode = Strings.isNullOrEmpty(kubeConfig) && isInCluster(); + this.isIngressWatched = buildIsIngressWatchedPredicate(this.controllerWatchedIngressClassName); + this.defaultIngressClass = StringUtils.firstNonEmpty(this.controllerWatchedIngressClassName, + HigressConstants.CONTROLLER_INGRESS_CLASS_NAME_DEFAULT); if (inClusterMode) { client = ClientBuilder.cluster().build(); @@ -148,14 +163,19 @@ private void initializeK8sCapabilities() { } } - public boolean isIngressV1Supported() { - return ingressV1Supported; - } - public boolean isNamespaceProtected(String namespace) { return KubernetesConstants.KUBE_SYSTEM_NS.equals(namespace) || controllerNamespace.equals(namespace); } + public boolean isDefinedByConsole(KubernetesObject metadata) { + return isDefinedByConsole(metadata.getMetadata()); + } + + public boolean isDefinedByConsole(V1ObjectMeta metadata) { + return metadata != null && controllerNamespace.equals(metadata.getNamespace()) + && Label.RESOURCE_DEFINER_VALUE.equals(KubernetesUtil.getLabel(metadata, Label.RESOURCE_DEFINER_KEY)); + } + public T loadFromYaml(String yaml, Class clazz) { return Yaml.getSnakeYaml(clazz).loadAs(yaml, clazz); } @@ -205,37 +225,49 @@ private static boolean isInCluster() { return new File(POD_SERVICE_ACCOUNT_TOKEN_FILE_PATH).exists(); } - public List listIngress() { + public List listAllIngresses() throws ApiException { + List ingresses = new ArrayList<>(); NetworkingV1Api apiInstance = new NetworkingV1Api(client); - try { - V1IngressList list = apiInstance.listNamespacedIngress(controllerNamespace, null, null, null, null, - DEFAULT_LABEL_SELECTORS, null, null, null, null, null); - if (list == null) { - return Collections.emptyList(); + if (StringUtils.isEmpty(controllerWatchedNamespace)) { + V1IngressList list = + apiInstance.listIngressForAllNamespaces(null, null, null, null, null, null, null, null, null, null); + ingresses.addAll(list.getItems()); + } else { + for (String ns : List.of(controllerNamespace, controllerWatchedNamespace)) { + V1IngressList list = + apiInstance.listNamespacedIngress(ns, null, null, null, null, null, null, null, null, null, null); + if (list != null) { + ingresses.addAll(list.getItems()); + } } - return sortKubernetesObjects(list.getItems()); - } catch (ApiException e) { - log.error("listIngress Status code: " + e.getCode() + "Reason: " + e.getResponseBody() - + "Response headers: " + e.getResponseHeaders(), e); - return null; } + retainWatchedIngress(ingresses); + return sortKubernetesObjects(ingresses); } - public List listIngressByDomain(String domainName) { + public List listIngress() throws ApiException { + NetworkingV1Api apiInstance = new NetworkingV1Api(client); + V1IngressList list = apiInstance.listNamespacedIngress(controllerNamespace, null, null, null, null, + DEFAULT_LABEL_SELECTORS, null, null, null, null, null); + if (list == null) { + return Collections.emptyList(); + } + List ingresses = new ArrayList<>(list.getItems()); + retainWatchedIngress(ingresses); + return sortKubernetesObjects(ingresses); + } + + public List listIngressByDomain(String domainName) throws ApiException { NetworkingV1Api apiInstance = new NetworkingV1Api(client); String labelSelectors = joinLabelSelectors(DEFAULT_LABEL_SELECTORS, buildDomainLabelSelector(domainName)); - try { - V1IngressList list = apiInstance.listNamespacedIngress(controllerNamespace, null, null, null, null, - labelSelectors, null, null, null, null, null); - if (list == null) { - return Collections.emptyList(); - } - return sortKubernetesObjects(list.getItems()); - } catch (ApiException e) { - log.error("listIngressByDomain Status code: " + e.getCode() + "Reason: " + e.getResponseBody() - + "Response headers: " + e.getResponseHeaders(), e); - return null; + V1IngressList list = apiInstance.listNamespacedIngress(controllerNamespace, null, null, null, null, + labelSelectors, null, null, null, null, null); + if (list == null) { + return Collections.emptyList(); } + List ingresses = new ArrayList<>(list.getItems()); + retainWatchedIngress(ingresses); + return sortKubernetesObjects(ingresses); } public V1Ingress readIngress(String name) throws ApiException { @@ -251,8 +283,8 @@ public V1Ingress readIngress(String name) throws ApiException { } public V1Ingress createIngress(V1Ingress ingress) throws ApiException { - Objects.requireNonNull(ingress.getSpec()).setIngressClassName(controllerIngressClassName); renderDefaultLabels(ingress); + fillDefaultIngressClass(ingress); NetworkingV1Api apiInstance = new NetworkingV1Api(client); return apiInstance.createNamespacedIngress(controllerNamespace, ingress, null, null, null, null); } @@ -262,8 +294,8 @@ public V1Ingress replaceIngress(V1Ingress ingress) throws ApiException { if (metadata == null) { throw new IllegalArgumentException("Ingress doesn't have a valid metadata."); } - Objects.requireNonNull(ingress.getSpec()).setIngressClassName(controllerIngressClassName); renderDefaultLabels(ingress); + fillDefaultIngressClass(ingress); NetworkingV1Api apiInstance = new NetworkingV1Api(client); return apiInstance.replaceNamespacedIngress(metadata.getName(), controllerNamespace, ingress, null, null, null, null); @@ -612,6 +644,41 @@ private void renderDefaultLabels(KubernetesObject object) { } } + private void retainWatchedIngress(List ingresses) { + if (CollectionUtils.isNotEmpty(ingresses)) { + ingresses.removeIf(i -> !isIngressWatched.test(i)); + } + } + + private void fillDefaultIngressClass(V1Ingress ingress) { + V1IngressSpec spec = Objects.requireNonNull(ingress.getSpec()); + if (StringUtils.isEmpty(spec.getIngressClassName())) { + spec.setIngressClassName(defaultIngressClass); + } + } + + private static Predicate buildIsIngressWatchedPredicate(String controllerWatchedIngressClassName) { + if (StringUtils.isEmpty(controllerWatchedIngressClassName)) { + return ingress -> true; + } + if (HigressConstants.NGINX_INGRESS_CLASS_NAME.equals(controllerWatchedIngressClassName)) { + return ingress -> { + String ingressClass = getIngressClassName(ingress); + return StringUtils.isEmpty(ingressClass) + || HigressConstants.NGINX_INGRESS_CLASS_NAME.equals(ingressClass); + }; + } + return ingress -> controllerWatchedIngressClassName.equals(getIngressClassName(ingress)); + } + + private static String getIngressClassName(V1Ingress ingress) { + V1IngressSpec spec = ingress.getSpec(); + if (spec == null) { + return null; + } + return spec.getIngressClassName(); + } + private static List sortKubernetesObjects(List objects) { if (CollectionUtils.isNotEmpty(objects)) { objects.sort(Comparator.comparing(o -> o.getMetadata() != null ? o.getMetadata().getName() : null)); diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverter.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverter.java index d627dd85..9679100d 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverter.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverter.java @@ -190,6 +190,7 @@ public Route ingress2Route(V1Ingress ingress) { fillRouteMetadata(route, ingress.getMetadata()); fillRouteInfo(route, ingress.getMetadata(), ingress.getSpec()); fillCustomConfigs(route, ingress.getMetadata()); + route.setReadonly(!kubernetesClientService.isDefinedByConsole(ingress) || !isIngressSupported(ingress)); return route; } @@ -1307,8 +1308,12 @@ private void fillIngressMetadata(V1Ingress ingress, Route route) { metadata.setName(route.getName()); metadata.setResourceVersion(route.getVersion()); - if (CollectionUtils.isNotEmpty(route.getDomains())) { - for (String domain : route.getDomains()) { + { + List domains = route.getDomains(); + if (CollectionUtils.isEmpty(domains)) { + domains = Collections.singletonList(HigressConstants.DEFAULT_DOMAIN); + } + for (String domain : domains) { setDomainLabel(metadata, domain); } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesUtil.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesUtil.java index 4f30e12d..778e0a38 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesUtil.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesUtil.java @@ -57,6 +57,15 @@ public static String getObjectName(KubernetesObject object) { return object.getMetadata().getName(); } + public static String getLabel(KubernetesObject object, String key) { + return getLabel(object.getMetadata(), key); + } + + public static String getLabel(V1ObjectMeta metadata, String key) { + Map labels = metadata.getLabels(); + return labels == null ? null : labels.get(key); + } + public static void setLabel(KubernetesObject object, String key, String value) { setLabel(Objects.requireNonNull(object.getMetadata()), key, value); } @@ -73,6 +82,15 @@ public static void setLabel(V1ObjectMeta metadata, String key, String value) { labels.put(key, value); } + public static String getAnnotation(KubernetesObject object, String key) { + return getAnnotation(object.getMetadata(), key); + } + + public static String getAnnotation(V1ObjectMeta metadata, String key) { + Map annotations = metadata.getAnnotations(); + return annotations == null ? null : annotations.get(key); + } + public static void setAnnotation(KubernetesObject object, String key, String value) { setAnnotation(Objects.requireNonNull(object.getMetadata()), key, value); } diff --git a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverterTest.java b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverterTest.java index e16fc32c..eb0229a3 100644 --- a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverterTest.java +++ b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/KubernetesModelConverterTest.java @@ -13,6 +13,25 @@ package com.alibaba.higress.sdk.service.kubernetes; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; + import com.alibaba.higress.sdk.constant.CommonKey; import com.alibaba.higress.sdk.constant.KubernetesConstants; import com.alibaba.higress.sdk.exception.ValidationException; @@ -37,6 +56,8 @@ import com.alibaba.higress.sdk.service.kubernetes.crd.wasm.V1alpha1WasmPlugin; import com.alibaba.higress.sdk.service.kubernetes.crd.wasm.V1alpha1WasmPluginSpec; import com.alibaba.higress.sdk.util.TypeUtil; + +import io.kubernetes.client.common.KubernetesObject; import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1HTTPIngressPath; import io.kubernetes.client.openapi.models.V1HTTPIngressRuleValue; @@ -49,27 +70,26 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1TypedLocalObjectReference; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mockito.Mockito.mock; public class KubernetesModelConverterTest { + private static final String DEFAULT_NAMESPACE = "higress-system"; + private KubernetesModelConverter converter; @BeforeEach public void setUp() { + final Predicate isDefinedByConsole = metadata -> metadata != null + && DEFAULT_NAMESPACE.equals(metadata.getNamespace()) && KubernetesConstants.Label.RESOURCE_DEFINER_VALUE + .equals(KubernetesUtil.getLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY)); + KubernetesClientService service = mock(KubernetesClientService.class); + when(service.isDefinedByConsole(any(KubernetesObject.class))).thenAnswer((Answer)invocation -> { + KubernetesObject object = invocation.getArgument(0); + return object != null && isDefinedByConsole.test(object.getMetadata()); + }); + when(service.isDefinedByConsole(any(V1ObjectMeta.class))) + .thenAnswer((Answer)invocation -> isDefinedByConsole.test(invocation.getArgument(0))); converter = new KubernetesModelConverter(service); } @@ -451,9 +471,9 @@ void route2IngressTestMethodsSuccess() { V1Ingress ingress = converter.route2Ingress(route); Assertions.assertNotNull(ingress); - Assertions.assertEquals("test-route", ingress.getMetadata().getName()); - Assertions.assertEquals("GET", - ingress.getMetadata().getAnnotations().get(KubernetesConstants.Annotation.METHOD_KEY)); + Assertions.assertEquals("test-route", Objects.requireNonNull(ingress.getMetadata()).getName()); + Assertions.assertEquals("GET", Objects.requireNonNull(ingress.getMetadata().getAnnotations()) + .get(KubernetesConstants.Annotation.METHOD_KEY)); } @Test @@ -513,6 +533,8 @@ public void route2IngressTestPrefixPathSingleService() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local"); @@ -540,6 +562,8 @@ public void route2IngressTestPrefixPathSingleServiceWithWeight() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local"); @@ -567,6 +591,8 @@ public void route2IngressTestPrefixPathSingleServiceWithPort() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local:8080"); @@ -594,6 +620,8 @@ public void route2IngressTestPrefixPathSingleServiceWithPortAndVersion() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local:8080"); @@ -623,6 +651,8 @@ public void route2IngressTestPrefixPathMultipleServices() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "20% hello1.default.svc.cluster.local:8080\n" + "30% hello2.default.svc.cluster.local:18080 v1\n50% hello3.default.svc.cluster.local v2"); @@ -651,6 +681,8 @@ public void route2IngressTestExactPathSingleService() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local:8080"); @@ -678,6 +710,8 @@ public void route2IngressTestRegularPathSingleService() { V1ObjectMeta expectedMetadata = expectedIngress.getMetadata(); expectedMetadata.setName(route.getName()); + expectedMetadata.setNamespace(null); + expectedMetadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.DESTINATION_KEY, "hello.default.svc.cluster.local:8080"); KubernetesUtil.setAnnotation(expectedMetadata, KubernetesConstants.Annotation.USE_REGEX_KEY, @@ -1621,6 +1655,10 @@ void ingress2RouteTestValidIngressWithSingleRule() { V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); + KubernetesUtil.setLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY, + KubernetesConstants.Label.RESOURCE_DEFINER_VALUE); + metadata.setResourceVersion("1"); V1Ingress ingress = new V1Ingress(); @@ -1642,6 +1680,7 @@ void ingress2RouteTestValidIngressWithMultipleRules() { V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); metadata.setResourceVersion("1"); V1IngressRule rule1 = new V1IngressRule(); @@ -1672,6 +1711,7 @@ void ingress2RouteTestValidIngressWithTLS() { V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); metadata.setResourceVersion("1"); V1IngressTLS tls = new V1IngressTLS(); @@ -1699,6 +1739,7 @@ void ingress2RouteTestValidIngressWithAnnotationsReturnsValidRoute() { V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); metadata.setResourceVersion("1"); metadata.setAnnotations(new HashMap() { { @@ -1719,6 +1760,73 @@ void ingress2RouteTestValidIngressWithAnnotationsReturnsValidRoute() { Assertions.assertEquals("annotation-value", route.getCustomConfigs().get("higress.cn")); } + @Test + void ingress2RouteTestNotReadonly() { + V1Ingress ingress = buildBasicSupportedIngress(); + + V1ObjectMeta metadata = ingress.getMetadata(); + metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); + metadata.setResourceVersion("1"); + KubernetesUtil.setLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY, + KubernetesConstants.Label.RESOURCE_DEFINER_VALUE); + + Route route = converter.ingress2Route(ingress); + + Assertions.assertNotNull(route); + Assertions.assertFalse(route.getReadonly()); + } + + @Test + void ingress2RouteTestReadonlyDueToNonSysNs() { + V1Ingress ingress = buildBasicSupportedIngress(); + + V1ObjectMeta metadata = ingress.getMetadata(); + metadata.setName("test-ingress"); + metadata.setNamespace("test-ns"); + metadata.setResourceVersion("1"); + KubernetesUtil.setLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY, + KubernetesConstants.Label.RESOURCE_DEFINER_VALUE); + + Route route = converter.ingress2Route(ingress); + + Assertions.assertNotNull(route); + Assertions.assertTrue(route.getReadonly()); + } + + @Test + void ingress2RouteTestReadonlyDueToIncorrectDefiner() { + V1Ingress ingress = buildBasicSupportedIngress(); + + V1ObjectMeta metadata = ingress.getMetadata(); + metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); + metadata.setResourceVersion("1"); + KubernetesUtil.setLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY, + "bad-definer"); + + Route route = converter.ingress2Route(ingress); + + Assertions.assertNotNull(route); + Assertions.assertTrue(route.getReadonly()); + } + + @Test + void ingress2RouteTestReadonlyDueToMissingDefiner() { + V1Ingress ingress = buildBasicSupportedIngress(); + + V1ObjectMeta metadata = ingress.getMetadata(); + metadata.setName("test-ingress"); + metadata.setNamespace(DEFAULT_NAMESPACE); + metadata.setResourceVersion("1"); + metadata.getLabels().remove(KubernetesConstants.Label.RESOURCE_DEFINER_KEY); + + Route route = converter.ingress2Route(ingress); + + Assertions.assertNotNull(route); + Assertions.assertTrue(route.getReadonly()); + } + @Test void ingress2RouteTestNullMetadataReturnsRouteWithDefaults() { V1IngressBackend backend = new V1IngressBackend(); @@ -1776,6 +1884,10 @@ private V1Ingress buildBasicSupportedIngress() { V1Ingress ingress = new V1Ingress(); V1ObjectMeta metadata = new V1ObjectMeta(); + metadata.setNamespace(DEFAULT_NAMESPACE); + KubernetesUtil.setLabel(metadata, "higress.io/domain_higress-default-domain", "true"); + KubernetesUtil.setLabel(metadata, KubernetesConstants.Label.RESOURCE_DEFINER_KEY, + KubernetesConstants.Label.RESOURCE_DEFINER_VALUE); ingress.setMetadata(metadata); V1IngressSpec spec = new V1IngressSpec(); @@ -1816,6 +1928,7 @@ private static Route buildBasicRoute() { route.setPath(new RoutePredicate()); route.setCors(new CorsConfig()); route.setCustomConfigs(new HashMap<>()); + route.setReadonly(false); return route; } @@ -1852,6 +1965,7 @@ private V1ObjectMeta createMetadata(String name, String pluginName, String plugi labels.put(KubernetesConstants.Label.WASM_PLUGIN_VERSION_KEY, pluginVersion); V1ObjectMeta metadata = new V1ObjectMeta(); metadata.setName(name); + metadata.setNamespace(DEFAULT_NAMESPACE); metadata.setLabels(labels); return metadata; } @@ -1869,6 +1983,7 @@ private V1ObjectMeta createMetadata() { annotations.put(KubernetesConstants.Annotation.WASM_PLUGIN_ICON_KEY, "icon.png"); V1ObjectMeta metadata = new V1ObjectMeta(); + metadata.setNamespace(DEFAULT_NAMESPACE); metadata.setLabels(labels); metadata.setAnnotations(annotations); return metadata; diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index bcd5a871..39cddcdc 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -44,20 +44,28 @@ spec: value: {{ include "higress-console.name" . }} - name: HIGRESS_CONSOLE_CONFIG_MAP_NAME value: {{ include "higress-console.name" . }} + - name: HIGRESS_CONSOLE_SERVICE_HOST + value: {{ include "higress-console.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.proxy.clusterDomain }} + - name: HIGRESS_CONSOLE_SERVICE_PORT + value: {{ .Values.service.port | quote }} - name: HIGRESS_CONSOLE_CONTROLLER_JWT_POLICY value: {{ include "higress-console.controller.jwtPolicy" . }} {{- if $o11y.enabled }} - name: HIGRESS_CONSOLE_DASHBOARD_BASE_URL value: http://{{ include "higress-console-grafana.name" . }}.{{ .Release.Namespace }}:{{ $o11y.grafana.port }}{{ include "higress-console-grafana.path" . }} - - name: HIGRESS_CONSOLE_DASHBOARD_DATASOURCE_URL # Kept for backward-compatibility. To be removed later. - value: http://{{ include "higress-console-prometheus.name" . }}.{{ .Release.Namespace }}:{{ $o11y.prometheus.port }}{{ include "higress-console-prometheus.path" . }} - name: HIGRESS_CONSOLE_DASHBOARD_DATASOURCE_PROM_URL value: http://{{ include "higress-console-prometheus.name" . }}.{{ .Release.Namespace }}:{{ $o11y.prometheus.port }}{{ include "higress-console-prometheus.path" . }} - name: HIGRESS_CONSOLE_DASHBOARD_DATASOURCE_LOKI_URL value: http://{{ include "higress-console-loki.name" . }}.{{ .Release.Namespace }}:{{ $o11y.loki.ports.http }} {{- end }} + {{- if .Values.global.ingressClass }} - name: HIGRESS_CONSOLE_CONTROLLER_INGRESS_CLASS_NAME value: {{ .Values.global.ingressClass }} + {{- end }} + {{- if .Values.global.watchNamespace }} + - name: HIGRESS_CONSOLE_CONTROLLER_WATCHED_NAMESPACE + value: {{ .Values.global.watchNamespace }} + {{- end }} - name: SPRINGDOC_API_DOCS_ENABLED value: "{{ .Values.swagger.enabled }}" - name: SPRINGDOC_SWAGGER_UI_ENABLED diff --git a/helm/values.yaml b/helm/values.yaml index 77287f92..d3e571be 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -2,6 +2,13 @@ global: local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true. kind: false # Deprecated. Please use "global.local" instead. Will be removed later. ingressClass: "higress" + watchNamespace: "" + + proxy: + # -- CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # cluster domain. Default value is "cluster.local". + clusterDomain: "cluster.local" + # Observability (o11y) configurations o11y: enabled: false @@ -41,6 +48,7 @@ global: pvc: storageClassName: "" securityContext: {} + pvc: rwxSupported: true