Skip to content

Commit

Permalink
feat: Support configuring custom annotations (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
CH3CHO authored Oct 20, 2023
1 parent 9c3f849 commit 1f703a9
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class KubernetesConstants {

public static class Annotation {
public static final String KEY_PREFIX = "higress.io/";
public static final String NGINX_INGRESS_KEY_PREFIX = "nginx.ingress.kubernetes.io/";
public final static String DISABLED_KEY_EXTRA_PREFIX = "disabled.";
public static final String TRUE_VALUE = "true";
public static final String USE_REGEX_KEY = "higress.io/use-regex";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package com.alibaba.higress.console.controller.dto;

import java.util.List;
import java.util.Map;

import com.alibaba.higress.console.controller.dto.route.CorsConfig;
import com.alibaba.higress.console.controller.dto.route.HeaderControlConfig;
Expand Down Expand Up @@ -70,8 +71,9 @@ public class Route implements VersionedDto {

private ProxyNextUpstreamConfig proxyNextUpstream;

// TODO: Not supported yet.
private CorsConfig cors;

private HeaderControlConfig headerControl;

private Map<String, String> customConfigs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,6 @@ public Route add(Route route) {
public Route update(Route route) {
V1Ingress ingress = kubernetesModelConverter.route2Ingress(route);

V1Ingress existedIngress = kubernetesClientService.readIngress(ingress.getMetadata().getName());
if (existedIngress != null) {
kubernetesModelConverter.migrateCustomAnnotations(existedIngress, ingress);
}

V1Ingress updatedIngress;
try {
updatedIngress = kubernetesClientService.replaceIngress(ingress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.alibaba.higress.console.controller.dto.route.RoutePredicateTypeEnum;
import com.alibaba.higress.console.controller.dto.route.UpstreamService;
import com.alibaba.higress.console.controller.exception.BusinessException;
import com.alibaba.higress.console.controller.exception.ValidationException;
import com.alibaba.higress.console.service.kubernetes.crd.mcp.V1McpBridge;
import com.alibaba.higress.console.service.kubernetes.crd.mcp.V1McpBridgeSpec;
import com.alibaba.higress.console.service.kubernetes.crd.mcp.V1RegistryConfig;
Expand All @@ -75,7 +76,6 @@
import com.alibaba.higress.console.util.TypeUtil;
import com.google.common.base.Splitter;

import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1HTTPIngressPath;
Expand Down Expand Up @@ -138,44 +138,6 @@ public void setKubernetesClientService(KubernetesClientService kubernetesClientS
this.kubernetesClientService = kubernetesClientService;
}

public void migrateCustomAnnotations(KubernetesObject src, KubernetesObject dst) {
V1ObjectMeta srcMetadata = src.getMetadata();
if (srcMetadata == null) {
return;
}

if (MapUtils.isEmpty(srcMetadata.getAnnotations())) {
return;
}

V1ObjectMeta dstMetadata = Objects.requireNonNull(dst.getMetadata());
Map<String, String> dstAnnotations = dstMetadata.getAnnotations();
if (dstAnnotations == null) {
dstAnnotations = new HashMap<>();
dstMetadata.setAnnotations(dstAnnotations);
}

for (Map.Entry<String, String> entry : srcMetadata.getAnnotations().entrySet()) {
if (isCustomAnnotation(entry.getKey())) {
dstAnnotations.put(entry.getKey(), entry.getValue());
}
}
}

private static boolean isCustomAnnotation(String key) {
if (SUPPORTED_ANNOTATIONS.contains(key)) {
return false;
}
if (!key.startsWith(KubernetesConstants.Annotation.KEY_PREFIX)) {
return true;
}
if (key.contains(KubernetesConstants.Annotation.HEADER_MATCH_KEYWORD)
|| key.contains(KubernetesConstants.Annotation.QUERY_MATCH_KEYWORD)) {
return false;
}
return true;
}

public boolean isIngressSupported(V1Ingress ingress) {
if (ingress.getMetadata() == null) {
return false;
Expand Down Expand Up @@ -220,6 +182,7 @@ public Route ingress2Route(V1Ingress ingress) {
Route route = new Route();
fillRouteMetadata(route, ingress.getMetadata());
fillRouteInfo(route, ingress.getMetadata(), ingress.getSpec());
fillCustomConfigs(route, ingress.getMetadata());
return route;
}

Expand All @@ -230,6 +193,7 @@ public V1Ingress route2Ingress(Route route) {
fillIngressMetadata(ingress, route);
fillIngressSpec(ingress, route);
fillIngressCors(ingress, route);
fillIngressAnnotations(ingress, route);
return ingress;
}

Expand Down Expand Up @@ -810,6 +774,19 @@ private static void fillRouteInfo(Route route, V1ObjectMeta metadata, V1IngressS
fillRouteCors(route, metadata);
}

private static void fillCustomConfigs(Route route, V1ObjectMeta metadata) {
if (metadata == null || MapUtils.isEmpty(metadata.getAnnotations())) {
return;
}
Map<String, String> customConfigs = new HashMap<>();
for (Map.Entry<String, String> annotation : metadata.getAnnotations().entrySet()) {
if (isCustomAnnotation(annotation.getKey())) {
customConfigs.put(annotation.getKey(), annotation.getValue());
}
}
route.setCustomConfigs(customConfigs);
}

private static void fillPathRoute(Route route, V1ObjectMeta metadata, V1HTTPIngressPath path) {
fillPathPredicates(route, metadata, path);
fillRouteDestinations(route, metadata, path.getBackend());
Expand Down Expand Up @@ -852,6 +829,28 @@ private void fillIngressCors(V1Ingress ingress, Route route) {
}
}

private void fillIngressAnnotations(V1Ingress ingress, Route route) {
if (MapUtils.isEmpty(route.getCustomConfigs())) {
return;
}
for (Map.Entry<String, String> config : route.getCustomConfigs().entrySet()) {
String key = config.getKey();
if (!isCustomAnnotation(key)) {
throw new ValidationException("Annotation [" + key + "] is already supported by Console. "
+ "Please configure it in the corresponding section instead of using custom annotations.");
}
if (key.startsWith(KubernetesConstants.Annotation.NGINX_INGRESS_KEY_PREFIX)) {
String higressKey = KubernetesConstants.Annotation.KEY_PREFIX
+ key.substring(KubernetesConstants.Annotation.NGINX_INGRESS_KEY_PREFIX.length());
if (!isCustomAnnotation(higressKey)) {
throw new ValidationException("Annotation [" + key + "] is already supported by Console. "
+ "Please configure it in the corresponding section instead of using custom annotations.");
}
}
KubernetesUtil.setAnnotation(ingress, config.getKey(), config.getValue());
}
}

private static void fillPathPredicates(Route route, V1ObjectMeta metadata, V1HTTPIngressPath path) {
RoutePredicate pathPredicate = new RoutePredicate();
route.setPath(pathPredicate);
Expand Down Expand Up @@ -1619,4 +1618,18 @@ private static void setFunctionalAnnotation(V1ObjectMeta metadata, String key, S
String actualKey = functionEnabled ? key : KubernetesConstants.Annotation.DISABLED_KEY_EXTRA_PREFIX + key;
KubernetesUtil.setAnnotation(metadata, actualKey, value);
}

private static boolean isCustomAnnotation(String key) {
if (SUPPORTED_ANNOTATIONS.contains(key)) {
return false;
}
if (!key.startsWith(KubernetesConstants.Annotation.KEY_PREFIX)) {
return true;
}
if (key.contains(KubernetesConstants.Annotation.HEADER_MATCH_KEYWORD)
|| key.contains(KubernetesConstants.Annotation.QUERY_MATCH_KEYWORD)) {
return false;
}
return true;
}
}
3 changes: 3 additions & 0 deletions frontend/src/interfaces/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export interface WasmPluginData {
imageVersion?: string;
phase?: string;
priority?: number;
customConfigs?: {
[key: string]: string;
};

resKey?: string;
key?: string;
Expand Down
48 changes: 31 additions & 17 deletions frontend/src/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@
"index": {
"title": "Higress Console"
},
"login": {
"title": "Login",
"buttonText": "Login",
"loginSuccess": "Login successful!",
"loginFailed": "Login failed. Please try again.",
"incorrectCredentials": "Incorrect username or password.",
"autoLogin": "Auto login",
"forgotPassword": "Forgot password",
"usernamePlaceholder": "Username",
"usernameRequired": "Please input username",
"passwordPlaceholder": "Password",
"passwordRequired": "Please input password"
},
"init": {
"title": "System Setup",
"header": "Setup Admin Account",
Expand All @@ -46,6 +33,19 @@
"initSuccess": "Setup completed. Redirecting to the login page.",
"initFailed": "Setup operation failed."
},
"login": {
"title": "Login",
"buttonText": "Login",
"loginSuccess": "Login successful!",
"loginFailed": "Login failed. Please try again.",
"incorrectCredentials": "Incorrect username or password.",
"autoLogin": "Auto login",
"forgotPassword": "Forgot password",
"usernamePlaceholder": "Username",
"usernameRequired": "Please input username",
"passwordPlaceholder": "Password",
"passwordRequired": "Please input password"
},
"domain": {
"columns": {
"name": "Domain",
Expand Down Expand Up @@ -307,6 +307,18 @@
},
"parameter": "Parameter"
},
"keyValueGroup": {
"columns": {
"key": "Key",
"value": "ֵValue",
"operation": "Action"
},
"required": {
"key": "Please input annotation key.",
"value": "Please input annotation value."
},
"config": "Annotation"
},
"routeForm": {
"routeName": "Route Name",
"routeNameTip": "Using a name related to the business scenario is recommended. e.g.: user-default, user-gray, etc.",
Expand All @@ -322,12 +334,14 @@
"pathMatcherRequired": "Please input the path match target.",
"pathMatcherPlacedholder": "Path match target. e.g.: /user",
"caseInsensitive": "Case Insensitive",
"method": "Method",
"methodMatcherPlaceholder": "HTTP method to match. Multiple selections are allowed. If left blank, it will match all the HTTP methods.",
"header": "Request Header",
"method": "Methods",
"methodMatcherPlaceholder": "HTTP methods to match. Multiple selections are allowed. If left blank, it will match all the HTTP methods.",
"header": "Request Headers",
"headerTooltip": "The relation among different arguments is \"and\".",
"query": "Query Parameter",
"query": "Query Parameters",
"queryTooltip": "The relation among different arguments is \"and\".",
"customConfigs": "Custom Annotations",
"customConfigsTip": "Click this \"?\" to view the configuration guide.",
"targetService": "Target Service",
"targetServiceRequired": "Please choose the target service.",
"targetServiceNamedPlaceholder": "Search target service by name"
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/locales/zh-CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,18 @@
},
"parameter": "参数"
},
"keyValueGroup": {
"columns": {
"key": "Key",
"value": "",
"operation": "操作"
},
"required": {
"key": "请输入Key",
"value": "请输入值"
},
"config": "注解"
},
"routeForm": {
"routeName": "路由名称",
"routeNameTip": "推荐结合业务场景命名,例如user-default、user-gray等",
Expand All @@ -328,6 +340,8 @@
"headerTooltip": "多个参数之间是“与”关系",
"query": "请求参数(Query)",
"queryTooltip": "多个参数之间是“与”关系",
"customConfigs": "附加注解(Annotation)",
"customConfigsTip": "点击问号查看注解配置说明",
"targetService": "目标服务",
"targetServiceRequired": "请选择目标服务",
"targetServiceNamedPlaceholder": "搜索服务名称选择服务"
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/pages/route/components/FactorGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,6 @@ interface DataType {

type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;

interface FactorGroupProps {
id: string;
onChange: (record) => void;
}

const FactorGroup: React.FC = ({ value, onChange }) => {
const { t } = useTranslation();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.factor :global {
.ant-empty-normal {
margin: 0;
}
}
Loading

0 comments on commit 1f703a9

Please sign in to comment.