This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 6eea1da1a8 [Improve][Connector-V2] Validate SLS required options
(#12352)
6eea1da1a8 is described below
commit 6eea1da1a8c10e1ed048ce59db34d2f13ae685dc
Author: Nikhil kumar <[email protected]>
AuthorDate: Wed Sep 23 10:23:15 2026 +0000
[Improve][Connector-V2] Validate SLS required options (#12352)
---
docs/en/connectors/sink/Sls.md | 10 +--
docs/en/connectors/source/Sls.md | 10 +--
docs/zh/connectors/sink/Sls.md | 10 +--
docs/zh/connectors/source/Sls.md | 10 +--
.../seatunnel/sls/sink/SlsSinkFactory.java | 13 +--
.../seatunnel/sls/source/SlsSourceFactory.java | 13 +--
.../connectors/seatunnel/sls/SlsFactoryTest.java | 94 +++++++++++++++++++++-
7 files changed, 127 insertions(+), 33 deletions(-)
diff --git a/docs/en/connectors/sink/Sls.md b/docs/en/connectors/sink/Sls.md
index 3c18322d45..77bc18be18 100644
--- a/docs/en/connectors/sink/Sls.md
+++ b/docs/en/connectors/sink/Sls.md
@@ -34,11 +34,11 @@ Maven central repository.
| Name | Type | Required | Default | Description
|
|-------------------|--------|----------|--------------------|---------------------------------------------------------------------------------------------------------|
-| endpoint | String | Yes | - | Alibaba Cloud
SLS endpoint, for example `cn-hangzhou.log.aliyuncs.com` or an intranet
endpoint. |
-| project | String | Yes | - | [Alibaba Cloud
SLS project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project).
|
-| logstore | String | Yes | - | [Alibaba Cloud
SLS logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore).
|
-| access_key_id | String | Yes | - | Alibaba Cloud
AccessKey ID.
|
-| access_key_secret | String | Yes | - | Alibaba Cloud
AccessKey secret.
|
+| endpoint | String | Yes | - | Alibaba Cloud
SLS endpoint, for example `cn-hangzhou.log.aliyuncs.com` or an intranet
endpoint. Must not be empty or whitespace-only. |
+| project | String | Yes | - | [Alibaba Cloud
SLS project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project). Must
not be empty or whitespace-only. |
+| logstore | String | Yes | - | [Alibaba Cloud
SLS logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore).
Must not be empty or whitespace-only. |
+| access_key_id | String | Yes | - | Alibaba Cloud
AccessKey ID. Must not be empty or whitespace-only.
|
+| access_key_secret | String | Yes | - | Alibaba Cloud
AccessKey secret. Must not be empty or whitespace-only.
|
| source | String | No | `SeaTunnel-Source` | Source tag
written to SLS log groups.
|
| topic | String | No | `SeaTunnel-Topic` | Topic tag
written to SLS log groups.
|
diff --git a/docs/en/connectors/source/Sls.md b/docs/en/connectors/source/Sls.md
index 161b00ca6a..0e03a529c0 100644
--- a/docs/en/connectors/source/Sls.md
+++ b/docs/en/connectors/source/Sls.md
@@ -43,11 +43,11 @@ Maven central repository.
| Name | Type
| Required | Default | Description
|
|-------------------------------------|-------------------------------------------|----------|--------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
-| endpoint | String
| Yes | - | Alibaba Cloud SLS endpoint, for
example `cn-hangzhou.log.aliyuncs.com` or an intranet endpoint.
|
-| project | String
| Yes | - | [Alibaba Cloud SLS
project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project).
|
-| logstore | String
| Yes | - | [Alibaba Cloud SLS
logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore).
|
-| access_key_id | String
| Yes | - | Alibaba Cloud AccessKey ID.
|
-| access_key_secret | String
| Yes | - | Alibaba Cloud AccessKey secret.
|
+| endpoint | String
| Yes | - | Alibaba Cloud SLS endpoint, for
example `cn-hangzhou.log.aliyuncs.com` or an intranet endpoint. Must not be
empty or whitespace-only. |
+| project | String
| Yes | - | [Alibaba Cloud SLS
project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project). Must not
be empty or whitespace-only. |
+| logstore | String
| Yes | - | [Alibaba Cloud SLS
logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore). Must
not be empty or whitespace-only. |
+| access_key_id | String
| Yes | - | Alibaba Cloud AccessKey ID. Must not
be empty or whitespace-only.
|
+| access_key_secret | String
| Yes | - | Alibaba Cloud AccessKey secret. Must
not be empty or whitespace-only.
|
| start_mode | `earliest`, `group_cursor`, `latest`
| No | `group_cursor` | Initial cursor mode. `earliest`
starts from the beginning, `latest` starts from the end, and `group_cursor`
uses the consumer group's checkpoint. |
| consumer_group | String
| No | `SeaTunnel-Consumer-Group` | SLS consumer group name. Use
different values when separate jobs must keep independent cursors.
|
| auto_cursor_reset | `begin`, `end`
| No | `end` | Cursor position used when
`start_mode = group_cursor` but the consumer group has no checkpoint yet.
|
diff --git a/docs/zh/connectors/sink/Sls.md b/docs/zh/connectors/sink/Sls.md
index e398948446..3985bf21d0 100644
--- a/docs/zh/connectors/sink/Sls.md
+++ b/docs/zh/connectors/sink/Sls.md
@@ -33,11 +33,11 @@ JSON,然后作为 SLS 日志项写入,日志内容的 key 为 `content`。
| 名称 | 类型 | 是否必填 | 默认值 | 描述
|
|-------------------|--------|----------|--------------------|--------------------------------------------------------------------------------------------------------|
-| endpoint | String | 是 | - | 阿里云 SLS 访问地址,例如
`cn-hangzhou.log.aliyuncs.com` 或内网访问地址。 |
-| project | String | 是 | - | [阿里云 SLS
Project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project)。
|
-| logstore | String | 是 | - | [阿里云 SLS
Logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore)。
|
-| access_key_id | String | 是 | - | 阿里云 AccessKey
ID。
|
-| access_key_secret | String | 是 | - | 阿里云 AccessKey
Secret。
|
+| endpoint | String | 是 | - | 阿里云 SLS 访问地址,例如
`cn-hangzhou.log.aliyuncs.com` 或内网访问地址。值不能为空或仅包含空白字符。 |
+| project | String | 是 | - | [阿里云 SLS
Project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project)。值不能为空或仅包含空白字符。
|
+| logstore | String | 是 | - | [阿里云 SLS
Logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore)。值不能为空或仅包含空白字符。
|
+| access_key_id | String | 是 | - | 阿里云 AccessKey
ID。值不能为空或仅包含空白字符。 |
+| access_key_secret | String | 是 | - | 阿里云 AccessKey
Secret。值不能为空或仅包含空白字符。 |
| source | String | 否 | `SeaTunnel-Source` | 写入 SLS log group
的 source 标记。
|
| topic | String | 否 | `SeaTunnel-Topic` | 写入 SLS log group
的 topic 标记。
|
diff --git a/docs/zh/connectors/source/Sls.md b/docs/zh/connectors/source/Sls.md
index 54db0f1538..16424c3fa2 100644
--- a/docs/zh/connectors/source/Sls.md
+++ b/docs/zh/connectors/source/Sls.md
@@ -42,11 +42,11 @@ SLS shard 并行读取。流处理模式下,SeaTunnel 会在 checkpoint 完成
| 名称 | 类型
| 是否必填 | 默认值 | 描述
|
|-------------------------------------|-------------------------------------------|----------|----------------------------|----------------------------------------------------------------------------------------------------------------|
-| endpoint | String
| 是 | - | 阿里云 SLS 访问地址,例如
`cn-hangzhou.log.aliyuncs.com` 或内网访问地址。 |
-| project | String
| 是 | - | [阿里云 SLS
Project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project)。
|
-| logstore | String
| 是 | - | [阿里云 SLS
Logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore)。
|
-| access_key_id | String
| 是 | - | 阿里云 AccessKey ID。
|
-| access_key_secret | String
| 是 | - | 阿里云 AccessKey Secret。
|
+| endpoint | String
| 是 | - | 阿里云 SLS 访问地址,例如
`cn-hangzhou.log.aliyuncs.com` 或内网访问地址。值不能为空或仅包含空白字符。 |
+| project | String
| 是 | - | [阿里云 SLS
Project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project)。值不能为空或仅包含空白字符。
|
+| logstore | String
| 是 | - | [阿里云 SLS
Logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore)。值不能为空或仅包含空白字符。|
+| access_key_id | String
| 是 | - | 阿里云 AccessKey ID。值不能为空或仅包含空白字符。
|
+| access_key_secret | String
| 是 | - | 阿里云 AccessKey Secret。值不能为空或仅包含空白字符。
|
| start_mode | `earliest`, `group_cursor`, `latest`
| 否 | `group_cursor` | 初始读取位置。`earliest` 从最早位置读取,`latest`
从最新位置读取,`group_cursor` 使用消费者组已提交的游标。 |
| consumer_group | String
| 否 | `SeaTunnel-Consumer-Group` | SLS
消费者组名称。不同任务如果需要独立保存读取位置,应该使用不同的消费者组。 |
| auto_cursor_reset | `begin`, `end`
| 否 | `end` | 当 `start_mode = group_cursor`
但消费者组还没有已提交游标时,使用的初始化位置。 |
diff --git
a/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/sink/SlsSinkFactory.java
b/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/sink/SlsSinkFactory.java
index 099da96baa..a869bc8a90 100644
---
a/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/sink/SlsSinkFactory.java
+++
b/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/sink/SlsSinkFactory.java
@@ -26,6 +26,8 @@ import
org.apache.seatunnel.connectors.seatunnel.sls.config.SlsSinkOptions;
import com.google.auto.service.AutoService;
+import static org.apache.seatunnel.api.configuration.util.Conditions.notBlank;
+
@AutoService(Factory.class)
public class SlsSinkFactory implements TableSinkFactory {
@Override
@@ -36,12 +38,13 @@ public class SlsSinkFactory implements TableSinkFactory {
@Override
public OptionRule optionRule() {
return OptionRule.builder()
+ .required(SlsSinkOptions.ENDPOINT,
notBlank(SlsSinkOptions.ENDPOINT))
+ .required(SlsSinkOptions.PROJECT,
notBlank(SlsSinkOptions.PROJECT))
+ .required(SlsSinkOptions.LOGSTORE,
notBlank(SlsSinkOptions.LOGSTORE))
+ .required(SlsSinkOptions.ACCESS_KEY_ID,
notBlank(SlsSinkOptions.ACCESS_KEY_ID))
.required(
- SlsSinkOptions.ENDPOINT,
- SlsSinkOptions.PROJECT,
- SlsSinkOptions.LOGSTORE,
- SlsSinkOptions.ACCESS_KEY_ID,
- SlsSinkOptions.ACCESS_KEY_SECRET)
+ SlsSinkOptions.ACCESS_KEY_SECRET,
+ notBlank(SlsSinkOptions.ACCESS_KEY_SECRET))
.optional(SlsSinkOptions.SOURCE, SlsSinkOptions.TOPIC)
.build();
}
diff --git
a/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/source/SlsSourceFactory.java
b/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/source/SlsSourceFactory.java
index 5e698bc498..bce62586d2 100644
---
a/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/source/SlsSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-sls/src/main/java/org/apache/seatunnel/connectors/seatunnel/sls/source/SlsSourceFactory.java
@@ -30,6 +30,8 @@ import com.google.auto.service.AutoService;
import java.io.Serializable;
+import static org.apache.seatunnel.api.configuration.util.Conditions.notBlank;
+
@AutoService(Factory.class)
public class SlsSourceFactory implements TableSourceFactory {
@Override
@@ -45,12 +47,13 @@ public class SlsSourceFactory implements TableSourceFactory
{
@Override
public OptionRule optionRule() {
return OptionRule.builder()
+ .required(SlsSourceOptions.ENDPOINT,
notBlank(SlsSourceOptions.ENDPOINT))
+ .required(SlsSourceOptions.PROJECT,
notBlank(SlsSourceOptions.PROJECT))
+ .required(SlsSourceOptions.LOGSTORE,
notBlank(SlsSourceOptions.LOGSTORE))
+ .required(SlsSourceOptions.ACCESS_KEY_ID,
notBlank(SlsSourceOptions.ACCESS_KEY_ID))
.required(
- SlsSourceOptions.ENDPOINT,
- SlsSourceOptions.PROJECT,
- SlsSourceOptions.LOGSTORE,
- SlsSourceOptions.ACCESS_KEY_ID,
- SlsSourceOptions.ACCESS_KEY_SECRET)
+ SlsSourceOptions.ACCESS_KEY_SECRET,
+ notBlank(SlsSourceOptions.ACCESS_KEY_SECRET))
.optional(
SlsSourceOptions.BATCH_SIZE,
SlsSourceOptions.START_MODE,
diff --git
a/seatunnel-connectors-v2/connector-sls/src/test/java/org/apache/seatunnel/connectors/seatunnel/sls/SlsFactoryTest.java
b/seatunnel-connectors-v2/connector-sls/src/test/java/org/apache/seatunnel/connectors/seatunnel/sls/SlsFactoryTest.java
index 84780c77dd..e8130e3c0c 100644
---
a/seatunnel-connectors-v2/connector-sls/src/test/java/org/apache/seatunnel/connectors/seatunnel/sls/SlsFactoryTest.java
+++
b/seatunnel-connectors-v2/connector-sls/src/test/java/org/apache/seatunnel/connectors/seatunnel/sls/SlsFactoryTest.java
@@ -17,17 +17,105 @@
package org.apache.seatunnel.connectors.seatunnel.sls;
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import org.apache.seatunnel.api.configuration.util.ConfigValidator;
+import org.apache.seatunnel.api.configuration.util.OptionRule;
+import org.apache.seatunnel.api.configuration.util.OptionValidationException;
+import org.apache.seatunnel.connectors.seatunnel.sls.config.SlsBaseOptions;
import org.apache.seatunnel.connectors.seatunnel.sls.sink.SlsSinkFactory;
import org.apache.seatunnel.connectors.seatunnel.sls.source.SlsSourceFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
public class SlsFactoryTest {
+ private static final OptionRule SOURCE_OPTION_RULE = new
SlsSourceFactory().optionRule();
+ private static final OptionRule SINK_OPTION_RULE = new
SlsSinkFactory().optionRule();
+ private static final List<String> REQUIRED_STRING_KEYS =
+ Arrays.asList(
+ SlsBaseOptions.ENDPOINT.key(),
+ SlsBaseOptions.PROJECT.key(),
+ SlsBaseOptions.LOGSTORE.key(),
+ SlsBaseOptions.ACCESS_KEY_ID.key(),
+ SlsBaseOptions.ACCESS_KEY_SECRET.key());
+
+ @Test
+ void testValidSourceConfiguration() {
+ assertValidRequiredStrings(SOURCE_OPTION_RULE, "SlsSource");
+ }
+
+ @Test
+ void testInvalidSourceConfiguration() {
+ assertInvalidRequiredStrings(SOURCE_OPTION_RULE, "SlsSource");
+ }
+
+ @Test
+ void testValidSinkConfiguration() {
+ assertValidRequiredStrings(SINK_OPTION_RULE, "SlsSink");
+ }
+
@Test
- void optionRule() {
- Assertions.assertNotNull((new SlsSourceFactory()).optionRule());
- Assertions.assertNotNull((new SlsSinkFactory()).optionRule());
+ void testInvalidSinkConfiguration() {
+ assertInvalidRequiredStrings(SINK_OPTION_RULE, "SlsSink");
+ }
+
+ private void assertValidRequiredStrings(OptionRule optionRule, String
factoryName) {
+ Assertions.assertDoesNotThrow(() -> validate(validConfig(),
optionRule, factoryName));
+
+ Map<String, Object> valuesWithSurroundingSpaces = validConfig();
+ for (String key : REQUIRED_STRING_KEYS) {
+ valuesWithSurroundingSpaces.put(key, " " +
valuesWithSurroundingSpaces.get(key) + " ");
+ }
+ Assertions.assertDoesNotThrow(
+ () -> validate(valuesWithSurroundingSpaces, optionRule,
factoryName));
+ }
+
+ private void assertInvalidRequiredStrings(OptionRule optionRule, String
factoryName) {
+ for (String key : REQUIRED_STRING_KEYS) {
+ Map<String, Object> missing = validConfig();
+ missing.remove(key);
+ assertInvalid(missing, optionRule, factoryName, key);
+
+ Map<String, Object> empty = validConfig();
+ empty.put(key, "");
+ assertInvalid(empty, optionRule, factoryName, key);
+
+ Map<String, Object> whitespaceOnly = validConfig();
+ whitespaceOnly.put(key, " \t\n ");
+ assertInvalid(whitespaceOnly, optionRule, factoryName, key);
+ }
+ }
+
+ private void assertInvalid(
+ Map<String, Object> config,
+ OptionRule optionRule,
+ String factoryName,
+ String optionKey) {
+ Assertions.assertThrows(
+ OptionValidationException.class,
+ () -> validate(config, optionRule, factoryName),
+ optionKey);
+ }
+
+ private void validate(Map<String, Object> config, OptionRule optionRule,
String factoryName) {
+ ReadonlyConfig readonlyConfig = ReadonlyConfig.fromMap(config);
+ ConfigValidator.validateUnknownKeys(readonlyConfig, optionRule,
factoryName);
+ ConfigValidator.of(readonlyConfig).validate(optionRule);
+ }
+
+ private Map<String, Object> validConfig() {
+ Map<String, Object> config = new HashMap<>();
+ config.put(SlsBaseOptions.ENDPOINT.key(),
"cn-hangzhou.log.aliyuncs.com");
+ config.put(SlsBaseOptions.PROJECT.key(), "seatunnel-project");
+ config.put(SlsBaseOptions.LOGSTORE.key(), "seatunnel-logstore");
+ config.put(SlsBaseOptions.ACCESS_KEY_ID.key(), "access-key-id");
+ config.put(SlsBaseOptions.ACCESS_KEY_SECRET.key(),
"access-key-secret");
+ return config;
}
}