diff --git a/backend/console/src/main/java/com/alibaba/higress/console/controller/WasmPluginsController.java b/backend/console/src/main/java/com/alibaba/higress/console/controller/WasmPluginsController.java index 07dc8397..fd9ffb7f 100644 --- a/backend/console/src/main/java/com/alibaba/higress/console/controller/WasmPluginsController.java +++ b/backend/console/src/main/java/com/alibaba/higress/console/controller/WasmPluginsController.java @@ -107,8 +107,7 @@ public ResponseEntity> update(@PathVariable("name") @NotBla throw new ValidationException("Plugin name in the URL doesn't match the one in the body."); } plugin.validate(); - WasmPlugin updatedPlugin = Boolean.TRUE.equals(plugin.getBuiltIn()) - ? wasmPluginService.updateBuiltIn(plugin.getName(), plugin.getImageVersion()) + WasmPlugin updatedPlugin = Boolean.TRUE.equals(plugin.getBuiltIn()) ? wasmPluginService.updateBuiltIn(plugin) : wasmPluginService.updateCustom(plugin); return ControllerUtil.buildResponseEntity(updatedPlugin); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/WasmPlugin.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/WasmPlugin.java index 5503c827..1b723ec2 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/WasmPlugin.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/WasmPlugin.java @@ -86,9 +86,5 @@ public void validate() { if (StringUtils.isBlank(imageRepository)) { throw new ValidationException("imageRepository cannot be blank."); } - - if (StringUtils.isBlank(imageVersion)) { - throw new ValidationException("imageVersion cannot be blank."); - } } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginService.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginService.java index ec936e64..2dc2cbd8 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginService.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginService.java @@ -30,7 +30,7 @@ public interface WasmPluginService { String queryReadme(String name, String language); - WasmPlugin updateBuiltIn(String name, String imageVersion); + WasmPlugin updateBuiltIn(WasmPlugin plugin); WasmPlugin addCustom(WasmPlugin plugin); diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginServiceImpl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginServiceImpl.java index b5eb7094..6777562e 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginServiceImpl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/WasmPluginServiceImpl.java @@ -39,7 +39,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import com.alibaba.higress.sdk.constant.Separators; import com.alibaba.higress.sdk.exception.BusinessException; import com.alibaba.higress.sdk.exception.NotFoundException; import com.alibaba.higress.sdk.exception.ResourceConflictException; @@ -61,7 +60,7 @@ import com.alibaba.higress.sdk.service.kubernetes.crd.wasm.V1alpha1WasmPlugin; import com.alibaba.higress.sdk.service.kubernetes.crd.wasm.V1alpha1WasmPluginSpec; import com.fasterxml.jackson.core.JsonProcessingException; -import com.google.common.base.Preconditions; +import com.google.common.annotations.VisibleForTesting; import io.kubernetes.client.openapi.ApiException; import io.swagger.v3.core.util.Json; @@ -98,6 +97,12 @@ class WasmPluginServiceImpl implements WasmPluginService { private static final String OPEN_API_V3_SCHEMA_PROPERTY_KEY = "openAPIV3Schema:"; private static final String YAML_EXAMPLE_PROPERTY_KEY = "example:"; + private static final String NAME_PLACEHOLDER = "${name}"; + private static final String VERSION_PLACEHOLDER = "${version}"; + + private static final String CUSTOM_IMAGE_URL_PATTERN_ENV = "HIGRESS_ADMIN_WASM_PLUGIN_CUSTOM_IMAGE_URL_PATTERN"; + private static final String CUSTOM_IMAGE_URL_PATTERN_PROPERTY = "higress-admin.wasmplugin.custom-image-url-pattern"; + private volatile List builtInPlugins = Collections.emptyList(); private final KubernetesClientService kubernetesClientService; @@ -121,14 +126,16 @@ public void initialize() { final List plugins = new ArrayList<>(properties.size()); + final String customImageUrlPattern = loadCustomImageUrlPattern(); + for (Object key : properties.keySet()) { String pluginName = (String)key; - String imageRepository = properties.getProperty(pluginName); - if (StringUtils.isEmpty(imageRepository)) { + String imageUrl = properties.getProperty(pluginName); + if (StringUtils.isEmpty(imageUrl)) { continue; } - PluginCacheItem cacheItem = new PluginCacheItem(pluginName, imageRepository); + PluginCacheItem cacheItem = new PluginCacheItem(pluginName); final String pluginFolder = PLUGINS_RESOURCE_FOLDER + pluginName + "/"; try (InputStream stream = getResourceAsStream(pluginFolder + SPEC_FILE)) { @@ -145,6 +152,9 @@ public void initialize() { ex); } + cacheItem.imageUrl = StringUtils.isBlank(customImageUrlPattern) ? imageUrl + : formatImageUrl(customImageUrlPattern, cacheItem.plugin.getInfo()); + cacheItem.setDefaultReadme(loadPluginReadme(pluginName, README_FILE)); cacheItem.setReadme(Language.ZH_CN.getCode(), loadPluginReadme(pluginName, README_CN_FILE)); cacheItem.setReadme(Language.EN_US.getCode(), loadPluginReadme(pluginName, README_EN_FILE)); @@ -165,6 +175,24 @@ public void initialize() { this.builtInPlugins = plugins; } + @VisibleForTesting + static String loadCustomImageUrlPattern() { + String value = System.getProperty(CUSTOM_IMAGE_URL_PATTERN_PROPERTY); + if (StringUtils.isEmpty(value)) { + value = System.getenv(CUSTOM_IMAGE_URL_PATTERN_ENV); + } + return value; + } + + @VisibleForTesting + static String formatImageUrl(String pattern, PluginInfo pluginInfo) { + if (StringUtils.isEmpty(pattern)) { + return pattern; + } + return pattern.replace(NAME_PLACEHOLDER, pluginInfo.getName()).replace(VERSION_PLACEHOLDER, + pluginInfo.getVersion()); + } + private void fillPluginConfigExample(Plugin plugin, String content) { String example; try { @@ -386,19 +414,18 @@ public String queryReadme(String name, String language) { } @Override - public WasmPlugin updateBuiltIn(String name, String imageVersion) { - Preconditions.checkArgument(StringUtils.isNotEmpty(name), "name cannot be blank."); - Preconditions.checkArgument(StringUtils.isNotEmpty(imageVersion), "imageVersion cannot be blank."); + public WasmPlugin updateBuiltIn(WasmPlugin plugin) { + final String name = plugin.getName(); - PluginCacheItem builtInPlugin = + PluginCacheItem cachedBuiltInPlugin = builtInPlugins.stream().filter(p -> p.getName().equals(name)).findFirst().orElse(null); - if (builtInPlugin == null) { + if (cachedBuiltInPlugin == null) { throw new ResourceConflictException("No built-in plugin is found with the given name: " + name); } List existedCrs; try { - final String pluginVersion = builtInPlugin.getPlugin().getInfo().getVersion(); + final String pluginVersion = cachedBuiltInPlugin.getPlugin().getInfo().getVersion(); existedCrs = kubernetesClientService.listWasmPlugin(name, pluginVersion, true); } catch (ApiException e) { throw new BusinessException("Error occurs when checking existed Wasm plugins with name " + name, e); @@ -407,9 +434,10 @@ public WasmPlugin updateBuiltIn(String name, String imageVersion) { V1alpha1WasmPlugin updatedCr = null; if (existedCrs.stream().allMatch(KubernetesUtil::isInternalResource)) { - WasmPlugin plugin = builtInPlugin.buildWasmPlugin(); - plugin.setImageVersion(imageVersion); - V1alpha1WasmPlugin cr = kubernetesModelConverter.wasmPluginToCr(plugin); + WasmPlugin builtInPlugin = cachedBuiltInPlugin.buildWasmPlugin(); + builtInPlugin.setImageRepository(plugin.getImageRepository()); + builtInPlugin.setImageVersion(plugin.getImageVersion()); + V1alpha1WasmPlugin cr = kubernetesModelConverter.wasmPluginToCr(builtInPlugin); // Make sure it is disabled by default. cr.getSpec().setDefaultConfigDisable(true); try { @@ -428,14 +456,8 @@ public WasmPlugin updateBuiltIn(String name, String imageVersion) { if (spec == null) { continue; } - String url = spec.getUrl(); - if (StringUtils.isEmpty(url)) { - continue; - } - ImageUrl imageUrl = ImageUrl.parse(url); - imageUrl.setTag(imageVersion); + ImageUrl imageUrl = new ImageUrl(plugin.getImageRepository(), plugin.getImageVersion()); spec.setUrl(imageUrl.toUrlString()); - try { updatedCr = kubernetesClientService.replaceWasmPlugin(existedCr); } catch (ApiException e) { @@ -604,7 +626,7 @@ private static class PluginCacheItem { private static final String DEFAULT_README_KEY = "_default_"; private final String name; - private final String imageUrl; + private String imageUrl; private Plugin plugin; private String iconData; @@ -612,9 +634,8 @@ private static class PluginCacheItem { @Setter(AccessLevel.NONE) private final Map readmes = new HashMap<>(4); - public PluginCacheItem(String name, String imageUrl) { + public PluginCacheItem(String name) { this.name = name; - this.imageUrl = imageUrl; } public String getDefaultReadme() { @@ -642,13 +663,9 @@ public WasmPlugin buildWasmPlugin() { public WasmPlugin buildWasmPlugin(String language) { WasmPlugin wasmPlugin = new WasmPlugin(); wasmPlugin.setName(name); - int colonIndex = imageUrl.lastIndexOf(Separators.COLON); - if (colonIndex != -1) { - wasmPlugin.setImageRepository(imageUrl.substring(0, colonIndex)); - wasmPlugin.setImageVersion(imageUrl.substring(colonIndex + 1)); - } else { - wasmPlugin.setImageRepository(imageUrl); - } + ImageUrl imageUrlObj = ImageUrl.parse(imageUrl); + wasmPlugin.setImageRepository(imageUrlObj.getRepository()); + wasmPlugin.setImageVersion(imageUrlObj.getTag()); wasmPlugin.setBuiltIn(true); PluginInfo info = plugin.getInfo(); @@ -670,7 +687,8 @@ public WasmPlugin buildWasmPlugin(String language) { PluginSpec spec = plugin.getSpec(); if (spec != null) { - wasmPlugin.setPhase("default".equals(spec.getPhase()) ? PluginPhase.UNSPECIFIED.getName() : spec.getPhase()); + wasmPlugin + .setPhase("default".equals(spec.getPhase()) ? PluginPhase.UNSPECIFIED.getName() : spec.getPhase()); wasmPlugin.setPriority(spec.getPriority()); } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrl.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrl.java index 0c8696f5..7145eb7d 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrl.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrl.java @@ -12,8 +12,11 @@ */ package com.alibaba.higress.sdk.service.kubernetes; +import org.apache.commons.lang3.StringUtils; + import com.alibaba.higress.sdk.constant.CommonKey; import com.alibaba.higress.sdk.constant.Separators; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -29,7 +32,7 @@ public class ImageUrl { private String tag; public String toUrlString() { - return tag != null ? repository + ":" + tag : repository; + return StringUtils.isNotBlank(tag) ? repository + ":" + tag : repository; } public static ImageUrl parse(String url) { @@ -38,6 +41,10 @@ public static ImageUrl parse(String url) { return new ImageUrl(url, null); } int protocolIndex = url.indexOf(CommonKey.PROTOCOL_KEYWORD); + if (protocolIndex != -1 && !url.startsWith(CommonKey.OCI_PROTOCOL)) { + // Not an OCI image URL, maybe an http:// or file:// URL + return new ImageUrl(url, null); + } if (colonIndex <= protocolIndex) { return new ImageUrl(url, null); } diff --git a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/WasmPluginServiceTest.java b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/WasmPluginServiceTest.java index 8667011e..22c9856a 100644 --- a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/WasmPluginServiceTest.java +++ b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/WasmPluginServiceTest.java @@ -58,6 +58,8 @@ public class WasmPluginServiceTest { private static final String TEST_BUILT_IN_PLUGIN_INTERNAL_CR_NAME = TEST_BUILT_IN_PLUGIN_NAME + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX; + private static final String CUSTOM_IMAGE_URL_PATTERN_PROPERTY = "higress-admin.wasmplugin.custom-image-url-pattern"; + private KubernetesClientService kubernetesClientService; private KubernetesModelConverter kubernetesModelConverter; private WasmPluginServiceImpl service; @@ -67,11 +69,12 @@ public void setUp() { kubernetesClientService = mock(KubernetesClientService.class); kubernetesModelConverter = new KubernetesModelConverter(kubernetesClientService); service = new WasmPluginServiceImpl(kubernetesClientService, kubernetesModelConverter); - service.initialize(); } @AfterEach public void tearDown() { + System.clearProperty(CUSTOM_IMAGE_URL_PATTERN_PROPERTY); + service = null; kubernetesModelConverter = null; kubernetesClientService = null; @@ -79,13 +82,14 @@ public void tearDown() { @Test public void listPluginsTest() throws Exception { + service.initialize(); + PaginatedResult plugins = service.list(null); when(kubernetesClientService.listWasmPlugin()).thenReturn(Collections.emptyList()); System.out.println(plugins.getTotal()); Properties properties = new Properties(); - try (InputStream stream = - getClass().getClassLoader().getResourceAsStream("plugins/plugins.properties")) { + try (InputStream stream = getClass().getClassLoader().getResourceAsStream("plugins/plugins.properties")) { properties.load(stream); } Assertions.assertEquals(properties.size(), plugins.getTotal()); @@ -95,27 +99,84 @@ public void listPluginsTest() throws Exception { Assertions.assertEquals(0, pluginNames.size(), "Missing built-in plugins: " + pluginNames); } + @Test + public void listPluginsTestWithCustomImageUrlNameAndVersion() throws Exception { + System.setProperty(CUSTOM_IMAGE_URL_PATTERN_PROPERTY, "http://foo.bar.com/plugins/${name}/${version}.wasm"); + service.initialize(); + + listPluginsTest(); + + PaginatedResult plugins = service.list(null); + for (WasmPlugin plugin : plugins.getData()) { + String expectedUrl = + "http://foo.bar.com/plugins/" + plugin.getName() + "/" + plugin.getPluginVersion() + ".wasm"; + Assertions.assertEquals(expectedUrl, plugin.getImageRepository()); + Assertions.assertNull(null, plugin.getImageVersion()); + } + } + + @Test + public void listPluginsTestWithCustomImageUrlNameOnly() throws Exception { + System.setProperty(CUSTOM_IMAGE_URL_PATTERN_PROPERTY, "https://foo.bar.com/plugins/${name}.wasm"); + service.initialize(); + + listPluginsTest(); + + PaginatedResult plugins = service.list(null); + for (WasmPlugin plugin : plugins.getData()) { + String expectedUrl = "https://foo.bar.com/plugins/" + plugin.getName() + ".wasm"; + Assertions.assertEquals(expectedUrl, plugin.getImageRepository()); + Assertions.assertNull(null, plugin.getImageVersion()); + } + } + + @Test + public void listPluginsTestWithCustomImageUrlFixed() throws Exception { + System.setProperty(CUSTOM_IMAGE_URL_PATTERN_PROPERTY, "file:///opt/plugins/main.wasm"); + service.initialize(); + + listPluginsTest(); + + PaginatedResult plugins = service.list(null); + for (WasmPlugin plugin : plugins.getData()) { + String expectedUrl = "file:///opt/plugins/main.wasm"; + Assertions.assertEquals(expectedUrl, plugin.getImageRepository()); + Assertions.assertNull(null, plugin.getImageVersion()); + } + } + @Test public void updateBuiltInTestNotConfigured() throws Exception { - when(kubernetesClientService.listWasmPlugin(eq(TEST_BUILT_IN_PLUGIN_NAME), anyString(), anyBoolean())).thenReturn(Collections.emptyList()); + service.initialize(); + + when(kubernetesClientService.listWasmPlugin(eq(TEST_BUILT_IN_PLUGIN_NAME), anyString(), anyBoolean())) + .thenReturn(Collections.emptyList()); when(kubernetesClientService.createWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(kubernetesClientService.replaceWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); + final String newRepo = "oci://docker.io/plugins/" + TEST_BUILT_IN_PLUGIN_NAME; final String newVersion = "1.0.1"; - WasmPlugin updatedPlugin = service.updateBuiltIn(TEST_BUILT_IN_PLUGIN_NAME,newVersion); + WasmPlugin plugin = new WasmPlugin(); + plugin.setName(TEST_BUILT_IN_PLUGIN_NAME); + plugin.setImageRepository(newRepo); + plugin.setImageVersion(newVersion); + WasmPlugin updatedPlugin = service.updateBuiltIn(plugin); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_NAME, updatedPlugin.getName()); + Assertions.assertEquals(newRepo, updatedPlugin.getImageRepository()); Assertions.assertEquals(newVersion, updatedPlugin.getImageVersion()); ArgumentCaptor crCaptor = ArgumentCaptor.forClass(V1alpha1WasmPlugin.class); verify(kubernetesClientService, times(1)).createWasmPlugin(crCaptor.capture()); V1alpha1WasmPlugin cr = crCaptor.getValue(); - Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_USER_CR_NAME,cr.getMetadata().getName()); - Assertions.assertTrue(cr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_USER_CR_NAME, cr.getMetadata().getName()); + Assertions.assertEquals(newRepo + ":" + newVersion, cr.getSpec().getUrl()); verify(kubernetesClientService, never()).replaceWasmPlugin(any()); } @Test public void updateBuiltInTestUserConfigured() throws Exception { + service.initialize(); + V1alpha1WasmPlugin existedCr = buildWasmPluginResource(TEST_BUILT_IN_PLUGIN_NAME, true, false); kubernetesModelConverter.setWasmPluginInstanceToCr(existedCr, WasmPluginInstance.builder() .scope(WasmPluginInstanceScope.GLOBAL).configurations(Map.of("k", "v")).build()); @@ -129,17 +190,23 @@ public void updateBuiltInTestUserConfigured() throws Exception { when(kubernetesClientService.createWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(kubernetesClientService.replaceWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); - final String newVersion = "1.0.1"; - WasmPlugin updatedPlugin = service.updateBuiltIn(TEST_BUILT_IN_PLUGIN_NAME, newVersion); + final String newRepo = "http://192.168.0.1:8080/plugins/" + TEST_BUILT_IN_PLUGIN_NAME + ".wasm"; + final String newVersion = ""; + WasmPlugin plugin = new WasmPlugin(); + plugin.setName(TEST_BUILT_IN_PLUGIN_NAME); + plugin.setImageRepository(newRepo); + plugin.setImageVersion(newVersion); + WasmPlugin updatedPlugin = service.updateBuiltIn(plugin); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_NAME, updatedPlugin.getName()); - Assertions.assertEquals(newVersion, updatedPlugin.getImageVersion()); + Assertions.assertEquals(newRepo, updatedPlugin.getImageRepository()); + Assertions.assertNull(updatedPlugin.getImageVersion()); verify(kubernetesClientService, never()).createWasmPlugin(any()); ArgumentCaptor crCaptor = ArgumentCaptor.forClass(V1alpha1WasmPlugin.class); verify(kubernetesClientService, times(1)).replaceWasmPlugin(crCaptor.capture()); V1alpha1WasmPlugin cr = crCaptor.getValue(); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_USER_CR_NAME, cr.getMetadata().getName()); - Assertions.assertTrue(cr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(newRepo, cr.getSpec().getUrl()); Assertions.assertEquals(cr.getSpec().getDefaultConfig(), existedCr.getSpec().getDefaultConfig()); Assertions.assertEquals(cr.getSpec().getDefaultConfigDisable(), existedCr.getSpec().getDefaultConfigDisable()); Assertions.assertEquals(cr.getSpec().getMatchRules(), existedCr.getSpec().getMatchRules()); @@ -147,6 +214,8 @@ public void updateBuiltInTestUserConfigured() throws Exception { @Test public void updateBuiltInTestInternalConfigured() throws Exception { + service.initialize(); + V1alpha1WasmPlugin existedCr = buildWasmPluginResource(TEST_BUILT_IN_PLUGIN_NAME, true, true); kubernetesModelConverter.setWasmPluginInstanceToCr(existedCr, WasmPluginInstance.builder() .scope(WasmPluginInstanceScope.GLOBAL).configurations(Map.of("k", "v")).build()); @@ -160,16 +229,22 @@ public void updateBuiltInTestInternalConfigured() throws Exception { when(kubernetesClientService.createWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(kubernetesClientService.replaceWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); - final String newVersion = "1.0.1"; - WasmPlugin updatedPlugin = service.updateBuiltIn(TEST_BUILT_IN_PLUGIN_NAME, newVersion); + final String newRepo = "files:///opt/plugins/" + TEST_BUILT_IN_PLUGIN_NAME + ".wasm"; + final String newVersion = ""; + WasmPlugin plugin = new WasmPlugin(); + plugin.setName(TEST_BUILT_IN_PLUGIN_NAME); + plugin.setImageRepository(newRepo); + plugin.setImageVersion(newVersion); + WasmPlugin updatedPlugin = service.updateBuiltIn(plugin); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_NAME, updatedPlugin.getName()); - Assertions.assertEquals(newVersion, updatedPlugin.getImageVersion()); + Assertions.assertEquals(newRepo, updatedPlugin.getImageRepository()); + Assertions.assertNull(updatedPlugin.getImageVersion()); ArgumentCaptor newCrCaptor = ArgumentCaptor.forClass(V1alpha1WasmPlugin.class); verify(kubernetesClientService, times(1)).createWasmPlugin(newCrCaptor.capture()); V1alpha1WasmPlugin newCr = newCrCaptor.getValue(); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_USER_CR_NAME, newCr.getMetadata().getName()); - Assertions.assertTrue(newCr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(newRepo, newCr.getSpec().getUrl()); Assertions.assertTrue(newCr.getSpec().getDefaultConfigDisable()); Assertions.assertTrue(MapUtils.isEmpty(newCr.getSpec().getDefaultConfig())); Assertions.assertTrue(CollectionUtils.isEmpty(newCr.getSpec().getMatchRules())); @@ -178,7 +253,7 @@ public void updateBuiltInTestInternalConfigured() throws Exception { verify(kubernetesClientService, times(1)).replaceWasmPlugin(updatedCrCaptor.capture()); V1alpha1WasmPlugin updatedCr = updatedCrCaptor.getValue(); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_INTERNAL_CR_NAME, updatedCr.getMetadata().getName()); - Assertions.assertTrue(updatedCr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(newRepo, updatedCr.getSpec().getUrl()); Assertions.assertEquals(updatedCr.getSpec().getDefaultConfig(), existedCr.getSpec().getDefaultConfig()); Assertions.assertEquals(updatedCr.getSpec().getDefaultConfigDisable(), existedCr.getSpec().getDefaultConfigDisable()); @@ -187,6 +262,8 @@ public void updateBuiltInTestInternalConfigured() throws Exception { @Test public void updateBuiltInTestUserAndInternalConfigured() throws Exception { + service.initialize(); + V1alpha1WasmPlugin userCr = buildWasmPluginResource(TEST_BUILT_IN_PLUGIN_NAME, true, false); kubernetesModelConverter.setWasmPluginInstanceToCr(userCr, WasmPluginInstance.builder() .scope(WasmPluginInstanceScope.GLOBAL).configurations(Map.of("kf", "vf")).build()); @@ -211,9 +288,15 @@ public void updateBuiltInTestUserAndInternalConfigured() throws Exception { when(kubernetesClientService.createWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(kubernetesClientService.replaceWasmPlugin(any())).thenAnswer(invocation -> invocation.getArgument(0)); - final String newVersion = "1.0.1"; - WasmPlugin updatedPlugin = service.updateBuiltIn(TEST_BUILT_IN_PLUGIN_NAME, newVersion); + final String newRepo = "https://foo.bar.com/plugins/" + TEST_BUILT_IN_PLUGIN_NAME + ".wasm"; + final String newVersion = null; + WasmPlugin plugin = new WasmPlugin(); + plugin.setName(TEST_BUILT_IN_PLUGIN_NAME); + plugin.setImageRepository(newRepo); + plugin.setImageVersion(newVersion); + WasmPlugin updatedPlugin = service.updateBuiltIn(plugin); Assertions.assertEquals(TEST_BUILT_IN_PLUGIN_NAME, updatedPlugin.getName()); + Assertions.assertEquals(newRepo, updatedPlugin.getImageRepository()); Assertions.assertEquals(newVersion, updatedPlugin.getImageVersion()); verify(kubernetesClientService, never()).createWasmPlugin(any()); @@ -225,7 +308,7 @@ public void updateBuiltInTestUserAndInternalConfigured() throws Exception { .filter(cr -> TEST_BUILT_IN_PLUGIN_INTERNAL_CR_NAME.equals(cr.getMetadata().getName())).findFirst() .orElse(null); Assertions.assertNotNull(updatedInternalCr); - Assertions.assertTrue(updatedInternalCr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(newRepo, updatedInternalCr.getSpec().getUrl()); Assertions.assertEquals(updatedInternalCr.getSpec().getDefaultConfig(), internalCr.getSpec().getDefaultConfig()); Assertions.assertEquals(updatedInternalCr.getSpec().getDefaultConfigDisable(), @@ -236,7 +319,7 @@ public void updateBuiltInTestUserAndInternalConfigured() throws Exception { .filter(cr -> TEST_BUILT_IN_PLUGIN_USER_CR_NAME.equals(cr.getMetadata().getName())).findFirst() .orElse(null); Assertions.assertNotNull(updatedUserCr); - Assertions.assertTrue(updatedUserCr.getSpec().getUrl().endsWith(":" + newVersion)); + Assertions.assertEquals(newRepo, updatedUserCr.getSpec().getUrl()); Assertions.assertEquals(updatedUserCr.getSpec().getDefaultConfig(), userCr.getSpec().getDefaultConfig()); Assertions.assertEquals(updatedUserCr.getSpec().getDefaultConfigDisable(), userCr.getSpec().getDefaultConfigDisable()); diff --git a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrlTest.java b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrlTest.java index 47b715fd..b11ef5b3 100644 --- a/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrlTest.java +++ b/backend/sdk/src/test/java/com/alibaba/higress/sdk/service/kubernetes/ImageUrlTest.java @@ -34,7 +34,7 @@ public void parseImageUrlTestEmptyTag() { Assertions.assertEquals("oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block", urlObj.getRepository()); Assertions.assertEquals("", urlObj.getTag()); - Assertions.assertEquals(url, urlObj.toUrlString()); + Assertions.assertEquals(url.substring(0, url.length() - 1), urlObj.toUrlString()); } @Test @@ -56,4 +56,54 @@ public void parseImageUrlTestNoProtocolNoTag() { Assertions.assertNull(urlObj.getTag()); Assertions.assertEquals(url, urlObj.toUrlString()); } + + @Test + public void parseImageUrlHttpsProtocolNoPort() { + String url = "https://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block.wasm"; + ImageUrl urlObj = ImageUrl.parse(url); + Assertions.assertEquals("https://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block.wasm", + urlObj.getRepository()); + Assertions.assertNull(urlObj.getTag()); + Assertions.assertEquals(url, urlObj.toUrlString()); + } + + @Test + public void parseImageUrlHttpsProtocolWithPort() { + String url = "https://higress-registry.cn-hangzhou.cr.aliyuncs.com:443/plugins/request-block.wasm"; + ImageUrl urlObj = ImageUrl.parse(url); + Assertions.assertEquals("https://higress-registry.cn-hangzhou.cr.aliyuncs.com:443/plugins/request-block.wasm", + urlObj.getRepository()); + Assertions.assertNull(urlObj.getTag()); + Assertions.assertEquals(url, urlObj.toUrlString()); + } + + @Test + public void parseImageUrlHttpProtocolNoPort() { + String url = "http://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block.wasm"; + ImageUrl urlObj = ImageUrl.parse(url); + Assertions.assertEquals("http://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block.wasm", + urlObj.getRepository()); + Assertions.assertNull(urlObj.getTag()); + Assertions.assertEquals(url, urlObj.toUrlString()); + } + + @Test + public void parseImageUrlHttpProtocolWithPort() { + String url = "http://higress-registry.cn-hangzhou.cr.aliyuncs.com:80/plugins/request-block.wasm"; + ImageUrl urlObj = ImageUrl.parse(url); + Assertions.assertEquals("http://higress-registry.cn-hangzhou.cr.aliyuncs.com:80/plugins/request-block.wasm", + urlObj.getRepository()); + Assertions.assertNull(urlObj.getTag()); + Assertions.assertEquals(url, urlObj.toUrlString()); + } + + @Test + public void parseImageUrlFileProtocol() { + String url = "files://opt/plugins/request-block.wasm"; + ImageUrl urlObj = ImageUrl.parse(url); + Assertions.assertEquals("files://opt/plugins/request-block.wasm", + urlObj.getRepository()); + Assertions.assertNull(urlObj.getTag()); + Assertions.assertEquals(url, urlObj.toUrlString()); + } } diff --git a/frontend/src/pages/plugin/components/Wasm/index.tsx b/frontend/src/pages/plugin/components/Wasm/index.tsx index e36ea99b..a167f0aa 100644 --- a/frontend/src/pages/plugin/components/Wasm/index.tsx +++ b/frontend/src/pages/plugin/components/Wasm/index.tsx @@ -87,7 +87,7 @@ const WasmForm = forwardRef((props: { editData?: WasmPluginData }, ref) => { const isEdit = !!editData; if (editData) { - editData.imageUrl = `${editData.imageRepository}:${editData.imageVersion}`; + editData.imageUrl = editData.imageVersion ? `${editData.imageRepository}:${editData.imageVersion}` : editData.imageRepository; } const builtIn = !!(editData && editData.builtIn); @@ -100,12 +100,19 @@ const WasmForm = forwardRef((props: { editData?: WasmPluginData }, ref) => { category: 'custom', builtIn: false, }, editData, await form.validateFields()); + const imageUrl: string = data.imageUrl || ''; + const protocolIndex = imageUrl.indexOf('://'); + const lastColonIndex = imageUrl.lastIndexOf(':'); + const isOciImage = protocolIndex === -1 || imageUrl.startsWith('oci://'); + if (isOciImage && lastColonIndex > protocolIndex) { + data.imageRepository = imageUrl.substring(0, lastColonIndex); + data.imageVersion = imageUrl.substring(lastColonIndex + 1); + } else { + data.imageRepository = imageUrl; + data.imageVersion = ''; + } + delete data.imageUrl; if (!builtIn) { - const imageUrl: string = data.imageUrl || ''; - const lastColonIndex = imageUrl.lastIndexOf(':'); - data.imageRepository = lastColonIndex === -1 ? imageUrl : imageUrl.substring(0, lastColonIndex); - data.imageVersion = lastColonIndex === -1 ? '' : imageUrl.substring(lastColonIndex + 1); - delete data.imageUrl; data.title = data.name; } return data; @@ -134,33 +141,16 @@ const WasmForm = forwardRef((props: { editData?: WasmPluginData }, ref) => { - { - builtIn - ? ( - - - - - - ) - : ( - - - - ) - } + + +