This is an automated email from the ASF dual-hosted git repository. gongchao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push: new da484aef26 [fix] Fixed `springboot3` template yaml has the jexl inner conflict keyword(#3629) da484aef26 is described below commit da484aef26e05fa473abd6008da00764983cebd3 Author: Duansg <siguod...@gmail.com> AuthorDate: Mon Aug 4 23:32:02 2025 +0800 [fix] Fixed `springboot3` template yaml has the jexl inner conflict keyword(#3629) Co-authored-by: tomsun28 <tomsu...@outlook.com> --- .../common/constants/JexlKeywordsEnum.java | 62 +++++++++++++++ .../manager/service/impl/AppServiceImpl.java | 5 ++ .../manager/service/impl/MonitorServiceImpl.java | 22 ++++++ .../src/main/resources/define/app-springboot3.yml | 6 +- .../hertzbeat/manager/script/YamlCheckScript.java | 91 ++++++++++++++++++++++ .../hertzbeat/manager/service/AppServiceTest.java | 62 +++++++++++++++ .../manager/service/MonitorServiceTest.java | 28 +++++++ 7 files changed, 273 insertions(+), 3 deletions(-) diff --git a/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/JexlKeywordsEnum.java b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/JexlKeywordsEnum.java new file mode 100644 index 0000000000..f54103ca0a --- /dev/null +++ b/hertzbeat-common/src/main/java/org/apache/hertzbeat/common/constants/JexlKeywordsEnum.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.hertzbeat.common.constants; + +import java.util.Arrays; + +/** + * Jexl keywords enum + */ +public enum JexlKeywordsEnum { + + SIZE("size"), + EMPTY("empty"), + NEW("new"), + VAR("var"), + RETURN("return"), + IF("if"), + ELSE("else"), + ELSEIF("elseif"), + WHILE("while"), + DO("do"), + FOR("for"), + CONTINUE("continue"), + BREAK("break"), + TRUE("true"), + FALSE("false"), + NULL("null"), + UNDEFINED("undefined"); + + private final String keyword; + + JexlKeywordsEnum(String keyword) { + this.keyword = keyword; + } + + public String getKeyword() { + return keyword; + } + + public static boolean match(String word) { + if (word == null || word.trim().isEmpty()) { + return false; + } + return Arrays.stream(values()).anyMatch(t -> t.keyword.equals(word)); + } + +} \ No newline at end of file diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java index b8f62e4235..470bfb3c45 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AppServiceImpl.java @@ -41,6 +41,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hertzbeat.collector.dispatch.DispatchConstants; import org.apache.hertzbeat.collector.util.CollectUtil; +import org.apache.hertzbeat.common.constants.JexlKeywordsEnum; import org.apache.hertzbeat.common.entity.job.Configmap; import org.apache.hertzbeat.common.entity.job.Job; import org.apache.hertzbeat.common.entity.job.Metrics; @@ -458,6 +459,10 @@ public class AppServiceImpl implements AppService, InitializingBean { throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " " + field.getField() + " can not duplicated."); } + if (JexlKeywordsEnum.match(field.getField())) { + throw new IllegalArgumentException(app.getApp() + " " + metrics.getName() + " " + + field.getField() + " prohibited keywords."); + } fieldsSet.add(field.getField()); } } diff --git a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java index e80c25f1c2..ab18501937 100644 --- a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java +++ b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/MonitorServiceImpl.java @@ -27,6 +27,7 @@ import org.apache.hertzbeat.alert.dao.AlertDefineBindDao; import org.apache.hertzbeat.collector.dispatch.DispatchConstants; import org.apache.hertzbeat.common.constants.CommonConstants; import org.apache.hertzbeat.common.constants.ExportFileConstants; +import org.apache.hertzbeat.common.constants.JexlKeywordsEnum; import org.apache.hertzbeat.common.constants.NetworkConstants; import org.apache.hertzbeat.common.constants.SignConstants; import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard; @@ -436,6 +437,27 @@ public class MonitorServiceImpl implements MonitorService { } } } + checkJobFields(monitorDto.getMonitor().getApp()); + } + + private void checkJobFields(String app) { + if (null == app || app.trim().isEmpty()) { + return; + } + Job job = appService.getAppDefine(app); + if (null != job && !CollectionUtils.isEmpty(job.getMetrics())) { + for (Metrics metrics : job.getMetrics()) { + if (null == metrics.getFields() || metrics.getFields().isEmpty()) { + continue; + } + for (Metrics.Field field : metrics.getFields()) { + if (JexlKeywordsEnum.match(field.getField())) { + throw new IllegalArgumentException(job.getApp() + " " + metrics.getName() + " " + + field.getField() + " prohibited keywords, please modify the template information."); + } + } + } + } } @Override diff --git a/hertzbeat-manager/src/main/resources/define/app-springboot3.yml b/hertzbeat-manager/src/main/resources/define/app-springboot3.yml index 90ffe25f42..09d6424cf7 100644 --- a/hertzbeat-manager/src/main/resources/define/app-springboot3.yml +++ b/hertzbeat-manager/src/main/resources/define/app-springboot3.yml @@ -184,16 +184,16 @@ metrics: en-US: state type: 1 label: true - - field: size + - field: Size i18n: zh-CN: 大小 - en-US: size + en-US: Size type: 0 aliasFields: - $.measurements[?(@.statistic == "VALUE")].value calculates: - state='^o^state^o^' - - size=$.measurements[?(@.statistic == "VALUE")].value + - Size=$.measurements[?(@.statistic == "VALUE")].value protocol: http http: host: ^_^host^_^ diff --git a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/script/YamlCheckScript.java b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/script/YamlCheckScript.java new file mode 100644 index 0000000000..54f98b3c6d --- /dev/null +++ b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/script/YamlCheckScript.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.hertzbeat.manager.script; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hertzbeat.common.constants.JexlKeywordsEnum; +import org.apache.hertzbeat.common.entity.job.Job; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; + +/** + * Yaml check script + */ +public class YamlCheckScript { + + static final String YML_PATH = "src/main/resources/define"; + + @Test + public void checkYaml() throws IOException { + Path definePath = Paths.get(YML_PATH); + if (!Files.exists(definePath)) { + throw new IllegalStateException("Define directory not found: " + YML_PATH); + } + try (Stream<Path> paths = Files.walk(definePath)) { + paths.filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".yml")) + .forEach(this::validateYmlFile); + } + } + + private void validateYmlFile(Path filePath) { + var yaml = new Yaml(); + Job app; + try { + app = yaml.loadAs(Files.readString(filePath), Job.class); + } catch (Exception e) { + throw new IllegalArgumentException("parse yml error in file " + filePath.getFileName() + ": " + e.getMessage()); + } + if (app == null) { + throw new IllegalArgumentException("Failed to load Job from file: " + filePath.getFileName()); + } + try { + validateJexlKeywords(app.getMetrics()); + } catch (Exception e) { + System.out.printf("file: %s , msg: %s%n", filePath.getFileName(), e.getMessage()); + } + } + + private void validateJexlKeywords(List<Metrics> metrics) { + if (null == metrics || metrics.isEmpty()) { + return; + } + for (Metrics metric : metrics) { + if (null == metric.getFields() || metric.getFields().isEmpty()) { + continue; + } + for (Metrics.Field field : metric.getFields()) { + if (null == field || StringUtils.isBlank(field.getField())) { + continue; + } + if (JexlKeywordsEnum.match(field.getField())) { + throw new IllegalArgumentException("check jexl keywords failed. field:" + field.getField()); + } + } + } + + } +} \ No newline at end of file diff --git a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/AppServiceTest.java b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/AppServiceTest.java index be9b0746f1..a54d59cef1 100644 --- a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/AppServiceTest.java +++ b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/AppServiceTest.java @@ -17,7 +17,10 @@ package org.apache.hertzbeat.manager.service; +import org.apache.hertzbeat.common.entity.job.Job; +import org.apache.hertzbeat.common.entity.job.Metrics; import org.apache.hertzbeat.common.entity.manager.Monitor; +import org.apache.hertzbeat.common.entity.manager.ParamDefine; import org.apache.hertzbeat.manager.dao.DefineDao; import org.apache.hertzbeat.manager.dao.MonitorDao; import org.apache.hertzbeat.manager.service.impl.AppServiceImpl; @@ -30,11 +33,16 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -94,4 +102,58 @@ class AppServiceTest { when(warehouseService.queryMonitorMetricsData(anyLong())).thenReturn(Collections.emptyList()); assertDoesNotThrow(() -> appService.getAllAppHierarchy("en-US")); } + + @Test + void appDefineJexl() throws NoSuchMethodException { + Job job = new Job(); + job.setApp("test-app"); + job.setCategory("service"); + job.setName(Map.of("k", "v")); + + List<ParamDefine> params = new ArrayList<>(); + ParamDefine hostParam = new ParamDefine(); + hostParam.setField("host"); + hostParam.setType("host"); + hostParam.setRequired(true); + params.add(hostParam); + + ParamDefine portParam = new ParamDefine(); + portParam.setField("port"); + portParam.setType("number"); + portParam.setRequired(true); + portParam.setDefaultValue("8080"); + params.add(portParam); + + job.setParams(params); + + List<Metrics> metrics = new ArrayList<>(); + + Metrics otherMetrics = new Metrics(); + otherMetrics.setName("details"); + otherMetrics.setPriority((byte) 0); + otherMetrics.setProtocol("http"); + + List<Metrics.Field> fields = new ArrayList<>(); + fields.add(Metrics.Field.builder().field("size").build()); + otherMetrics.setFields(fields); + + metrics.add(otherMetrics); + job.setMetrics(metrics); + + Method verifyMethod = AppServiceImpl.class.getDeclaredMethod("verifyDefineAppContent", Job.class, boolean.class); + verifyMethod.setAccessible(true); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + try { + verifyMethod.invoke(appService, job, false); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof RuntimeException) { + throw e.getCause(); + } + throw new RuntimeException(e.getCause()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + assertTrue(exception.getMessage().contains("prohibited keywords")); + } } diff --git a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java index 711ddfb03f..7db847a451 100644 --- a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java +++ b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/MonitorServiceTest.java @@ -34,6 +34,7 @@ import java.util.Set; import org.apache.hertzbeat.alert.dao.AlertDefineBindDao; import org.apache.hertzbeat.common.constants.CommonConstants; import org.apache.hertzbeat.common.entity.job.Job; +import org.apache.hertzbeat.common.entity.job.Metrics; import org.apache.hertzbeat.common.entity.manager.Monitor; import org.apache.hertzbeat.common.entity.manager.Param; import org.apache.hertzbeat.common.entity.manager.ParamDefine; @@ -774,4 +775,31 @@ class MonitorServiceTest { // Test the exportAll method assertDoesNotThrow(() -> monitorService.exportAll("JSON", mockResponse)); } + + @Test + void jexlKeyword() { + + List<Metrics.Field> fields = new ArrayList<>(); + fields.add(Metrics.Field.builder().field("size").build()); + + List<Metrics> metrics = new ArrayList<>(); + metrics.add(Metrics.builder().name("metricsName").fields(fields).build()); + + Job job = new Job(); + job.setApp("testJob"); + job.setMetrics(metrics); + Monitor monitor = Monitor.builder().jobId(1L).intervals(1).app(job.getApp()).name(job.getApp()).host("host").build(); + + + List<Param> params = new ArrayList<>(); + params.add(Param.builder().field("field").paramValue("value").build()); + + MonitorDto dto = new MonitorDto(); + dto.setMonitor(monitor); + dto.setParams(params); + + when(appService.getAppDefine(monitor.getApp())).thenReturn(job); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> monitorService.validate(dto, null)); + assertEquals("testJob metricsName size prohibited keywords, please modify the template information.", exception.getMessage()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@hertzbeat.apache.org For additional commands, e-mail: notifications-h...@hertzbeat.apache.org