diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 1595870a..6f6d3c5c 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "cloud" -version = "3.0.0" +version = "3.0.1" repository = "https://github.com/ballerina-platform/module-ballerina-c2c" license = ["Apache-2.0"] keywords = ["cloud", "kubernetes", "docker", "k8s", "c2c"] diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 2761deb6..8e780045 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "code2cloud" class = "io.ballerina.c2c.C2CCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/cloud-compiler-plugin-3.0.0.jar" +path = "../compiler-plugin/build/libs/cloud-compiler-plugin-3.0.1-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index db615a1c..9f950d9f 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.9.0" [[package]] org = "ballerina" name = "cloud" -version = "3.0.0" +version = "3.0.1" modules = [ {org = "ballerina", packageName = "cloud", moduleName = "cloud"} ] diff --git a/cloud-util/src/main/java/io/ballerina/c2c/util/C2CVisitor.java b/cloud-util/src/main/java/io/ballerina/c2c/util/C2CVisitor.java index 5bddc9a7..8cac4efa 100644 --- a/cloud-util/src/main/java/io/ballerina/c2c/util/C2CVisitor.java +++ b/cloud-util/src/main/java/io/ballerina/c2c/util/C2CVisitor.java @@ -315,10 +315,14 @@ private Optional getListenerInfo(String path, ExpressionNode expre } return Optional.of(new ListenerInfo(path, portNumber)); } - } else { + } else if (expression instanceof BasicLiteralNode) { //on new http:Listener(9091) int port = Integer.parseInt(((BasicLiteralNode) expression).literalToken().text()); return Optional.of(new ListenerInfo(path, port)); + } else { + diagnostics.add(C2CDiagnosticCodes + .createDiagnostic(C2CDiagnosticCodes.FAILED_PORT_RETRIEVAL, expression.location())); + return Optional.empty(); } } diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/c2c/test/samples/AdditionalPortTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/c2c/test/samples/AdditionalPortTest.java new file mode 100644 index 00000000..9b7aa755 --- /dev/null +++ b/compiler-plugin-tests/src/test/java/io/ballerina/c2c/test/samples/AdditionalPortTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 io.ballerina.c2c.test.samples; + +import io.ballerina.c2c.KubernetesConstants; +import io.ballerina.c2c.exceptions.KubernetesPluginException; +import io.ballerina.c2c.test.utils.DockerTestException; +import io.ballerina.c2c.test.utils.KubernetesTestUtils; +import io.ballerina.c2c.utils.KubernetesUtils; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static io.ballerina.c2c.KubernetesConstants.DOCKER; +import static io.ballerina.c2c.KubernetesConstants.KUBERNETES; + +/** + * Test cases for sample 1. + */ +public class AdditionalPortTest extends SampleTest { + + private static final Path SOURCE_DIR_PATH = SAMPLE_DIR.resolve("additional_ports"); + private static final Path DOCKER_TARGET_PATH = SOURCE_DIR_PATH.resolve("target").resolve(DOCKER) + .resolve("diff_ports"); + private static final Path KUBERNETES_TARGET_PATH = SOURCE_DIR_PATH.resolve("target").resolve(KUBERNETES) + .resolve("diff_ports"); + private static final String DOCKER_IMAGE = "xlight05/diff_port:latest"; + private Deployment deployment; + private Service service; + + @BeforeClass + public void compileSample() throws IOException, InterruptedException { + Assert.assertEquals(KubernetesTestUtils.compileBallerinaProject(SOURCE_DIR_PATH, "k8s"), 0); + File artifactYaml = KUBERNETES_TARGET_PATH.resolve("diff_ports.yaml").toFile(); + Assert.assertTrue(artifactYaml.exists()); + KubernetesClient client = new KubernetesClientBuilder().build(); + List k8sItems = client.load(new FileInputStream(artifactYaml)).items(); + for (HasMetadata data : k8sItems) { + switch (data.getKind()) { + case "Deployment": + deployment = (Deployment) data; + break; + case "Service": + service = (Service) data; + break; + } + } + } + + @Test + public void validateDeployment() { + Assert.assertNotNull(deployment); + Assert.assertEquals(deployment.getMetadata().getName(), "diff-ports-deployment"); + Assert.assertEquals(deployment.getSpec().getReplicas().intValue(), 1); + Assert.assertEquals(deployment.getMetadata().getLabels().get(KubernetesConstants + .KUBERNETES_SELECTOR_KEY), "diff_ports"); + Assert.assertEquals(deployment.getSpec().getTemplate().getSpec().getContainers().size(), 1); + Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); + Assert.assertEquals(container.getImage(), DOCKER_IMAGE); + Assert.assertEquals(container.getPorts().size(), 1); + Assert.assertEquals(container.getEnv().size(), 0); + } + + @Test + public void validateK8SService() { + Assert.assertNotNull(service); + Assert.assertEquals(service.getMetadata().getName(), "diff-ports-svc"); + Assert.assertEquals(service.getMetadata().getLabels().get(KubernetesConstants + .KUBERNETES_SELECTOR_KEY), "diff_ports"); + Assert.assertEquals(service.getSpec().getType(), KubernetesConstants.ServiceType.ClusterIP.name()); + Assert.assertEquals(service.getSpec().getPorts().size(), 1); + Assert.assertEquals(service.getSpec().getPorts().get(0).getPort().intValue(), 9092); + } + + @Test + public void validateDockerfile() throws IOException { + File dockerFile = DOCKER_TARGET_PATH.resolve("Dockerfile").toFile(); + String dockerFileContent = new String(Files.readAllBytes(dockerFile.toPath())); + Assert.assertTrue(dockerFileContent.contains("EXPOSE 9092")); + Assert.assertTrue(dockerFileContent.contains("USER ballerina")); + Assert.assertTrue(dockerFile.exists()); + } + + @Test + public void validateDockerImage() throws DockerTestException, InterruptedException { + List ports = KubernetesTestUtils.getExposedPorts(DOCKER_IMAGE); + Assert.assertEquals(ports.size(), 1); + Assert.assertEquals(ports.get(0), "9092/tcp"); + } + + @Test(groups = { "integration" }) + public void deploySample() throws IOException, InterruptedException { + Assert.assertEquals(0, KubernetesTestUtils.loadImage(DOCKER_IMAGE)); + Assert.assertEquals(0, KubernetesTestUtils.deployK8s(KUBERNETES_TARGET_PATH)); + Assert.assertEquals(0, KubernetesTestUtils.deleteK8s(KUBERNETES_TARGET_PATH)); + } + + @AfterClass + public void cleanUp() throws KubernetesPluginException { + KubernetesUtils.deleteDirectory(KUBERNETES_TARGET_PATH); + KubernetesUtils.deleteDirectory(DOCKER_TARGET_PATH); + KubernetesTestUtils.deleteDockerImage(DOCKER_IMAGE); + } +} diff --git a/compiler-plugin-tests/src/test/resources/completion/cloud/main/config/config1.json b/compiler-plugin-tests/src/test/resources/completion/cloud/main/config/config1.json index d7dd9010..27e53949 100644 --- a/compiler-plugin-tests/src/test/resources/completion/cloud/main/config/config1.json +++ b/compiler-plugin-tests/src/test/resources/completion/cloud/main/config/config1.json @@ -1,111 +1,119 @@ { - "position": { - "line": 4, - "character": 0 + "position": { + "line": 4, + "character": 0 + }, + "source": "main/source/Cloud.toml", + "items": [ + { + "label": "cloud.config.maps", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.config.maps]]" }, - "source": "main/source/Cloud.toml", - "items": [ - { - "label": "cloud.config.maps", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.config.maps]]" - }, - { - "label": "settings", - "kind": "Snippet", - "detail": "Table", - "sortText": "C", - "insertText": "[settings]" - }, - { - "label": "container.copy.files", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[container.copy.files]]" - }, - { - "label": "cloud.deployment.probes.readiness", - "kind": "Snippet", - "detail": "Table", - "sortText": "C", - "insertText": "[cloud.deployment.probes.readiness]" - }, - { - "label": "cloud.config.files", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.config.files]]" - }, - { - "label": "cloud.config.secrets", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.config.secrets]]" - }, - { - "label": "repository", - "kind": "Snippet", - "detail": "String", - "sortText": "A", - "insertText": "repository=\"${1:}\"", - "insertTextFormat": "Snippet" - }, - { - "label": "cloud.deployment.storage.volumes", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.deployment.storage.volumes]]" - }, - { - "label": "graalvm.builder", - "kind": "Snippet", - "detail": "Table", - "sortText": "C", - "insertText": "[graalvm.builder]" - }, - { - "label": "entrypoint", - "kind": "Snippet", - "detail": "String", - "sortText": "A", - "insertText": "entrypoint=\"${1:}\"", - "insertTextFormat": "Snippet" - }, - { - "label": "cloud.deployment.autoscaling", - "kind": "Snippet", - "detail": "Table", - "sortText": "C", - "insertText": "[cloud.deployment.autoscaling]" - }, - { - "label": "cloud.config.envs", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.config.envs]]" - }, - { - "label": "cloud.secret.envs", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.secret.envs]]" - }, - { - "label": "cloud.secret.files", - "kind": "Snippet", - "detail": "Table Array", - "sortText": "C", - "insertText": "[[cloud.secret.files]]" - } - ] + { + "label": "settings", + "kind": "Snippet", + "detail": "Table", + "sortText": "C", + "insertText": "[settings]" + }, + { + "label": "container.copy.files", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[container.copy.files]]" + }, + { + "label": "cloud.deployment.probes.readiness", + "kind": "Snippet", + "detail": "Table", + "sortText": "C", + "insertText": "[cloud.deployment.probes.readiness]" + }, + { + "label": "cloud.config.files", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.config.files]]" + }, + { + "label": "cloud.config.secrets", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.config.secrets]]" + }, + { + "label": "repository", + "kind": "Snippet", + "detail": "String", + "sortText": "A", + "insertText": "repository=\"${1:}\"", + "insertTextFormat": "Snippet" + }, + { + "label": "cloud.secret.envs", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.secret.envs]]" + }, + { + "label": "additionalPorts", + "kind": "Snippet", + "detail": "Array", + "sortText": "A", + "insertText": "additionalPorts=[${1:}]", + "insertTextFormat": "Snippet" + }, + { + "label": "cloud.deployment.storage.volumes", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.deployment.storage.volumes]]" + }, + { + "label": "cloud.secret.files", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.secret.files]]" + }, + { + "label": "entrypoint", + "kind": "Snippet", + "detail": "String", + "sortText": "A", + "insertText": "entrypoint=\"${1:}\"", + "insertTextFormat": "Snippet" + }, + { + "label": "graalvm.builder", + "kind": "Snippet", + "detail": "Table", + "sortText": "C", + "insertText": "[graalvm.builder]" + }, + { + "label": "cloud.deployment.autoscaling", + "kind": "Snippet", + "detail": "Table", + "sortText": "C", + "insertText": "[cloud.deployment.autoscaling]" + }, + { + "label": "cloud.config.envs", + "kind": "Snippet", + "detail": "Table Array", + "sortText": "C", + "insertText": "[[cloud.config.envs]]" + } + ] } diff --git a/compiler-plugin-tests/src/test/resources/testng-integration.xml b/compiler-plugin-tests/src/test/resources/testng-integration.xml index 4983a934..ac3cee78 100644 --- a/compiler-plugin-tests/src/test/resources/testng-integration.xml +++ b/compiler-plugin-tests/src/test/resources/testng-integration.xml @@ -37,6 +37,7 @@ + diff --git a/compiler-plugin-tests/src/test/resources/testng.xml b/compiler-plugin-tests/src/test/resources/testng.xml index b75b5f3d..6044ab85 100644 --- a/compiler-plugin-tests/src/test/resources/testng.xml +++ b/compiler-plugin-tests/src/test/resources/testng.xml @@ -42,6 +42,7 @@ + diff --git a/compiler-plugin/src/main/java/io/ballerina/c2c/KubernetesConstants.java b/compiler-plugin/src/main/java/io/ballerina/c2c/KubernetesConstants.java index 0ab77b94..5719f608 100644 --- a/compiler-plugin/src/main/java/io/ballerina/c2c/KubernetesConstants.java +++ b/compiler-plugin/src/main/java/io/ballerina/c2c/KubernetesConstants.java @@ -57,6 +57,7 @@ public class KubernetesConstants { public static final String MEMORY = "memory"; public static final String CPU = "cpu"; public static final String CHOREO = "choreo"; + public static final String CONTAINER_IMAGE = "container.image"; /** * Restart policy enum. diff --git a/compiler-plugin/src/main/java/io/ballerina/c2c/tasks/C2CAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/c2c/tasks/C2CAnalysisTask.java index 388fd05f..6cb3e005 100644 --- a/compiler-plugin/src/main/java/io/ballerina/c2c/tasks/C2CAnalysisTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/c2c/tasks/C2CAnalysisTask.java @@ -30,19 +30,24 @@ import io.ballerina.c2c.util.ServiceInfo; import io.ballerina.c2c.util.Task; import io.ballerina.c2c.utils.KubernetesUtils; +import io.ballerina.projects.CloudToml; import io.ballerina.projects.Package; import io.ballerina.projects.Project; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.CompilationAnalysisContext; +import io.ballerina.toml.api.Toml; import io.ballerina.tools.diagnostics.Diagnostic; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import static io.ballerina.c2c.KubernetesConstants.CONTAINER_IMAGE; import static io.ballerina.c2c.KubernetesConstants.DOCKER_CERT_PATH; import static io.ballerina.c2c.KubernetesConstants.DOCKER_HOST; import static io.ballerina.c2c.KubernetesConstants.SVC_POSTFIX; import static io.ballerina.c2c.utils.KubernetesUtils.getValidName; +import static io.ballerina.c2c.utils.TomlHelper.getNumberArray; /** * An {@code AnalysisTask} that is triggered for code to cloud. @@ -53,6 +58,7 @@ public class C2CAnalysisTask implements AnalysisTask @Override public void perform(CompilationAnalysisContext compilationAnalysisContext) { + Package currentPackage = compilationAnalysisContext.currentPackage(); final Project project = compilationAnalysisContext.currentPackage().project(); String cloud = project.buildOptions().cloud(); @@ -65,8 +71,9 @@ public void perform(CompilationAnalysisContext compilationAnalysisContext) { List c2cDiagnostics = new ArrayList<>(); ProjectServiceInfo projectServiceInfo = new ProjectServiceInfo(currentPackage.project(), c2cDiagnostics); List serviceList = projectServiceInfo.getServiceList(); + try { - addServices(serviceList); + addServices(serviceList, currentPackage); } catch (KubernetesPluginException e) { compilationAnalysisContext.reportDiagnostic(e.getDiagnostic()); } @@ -80,6 +87,7 @@ public void perform(CompilationAnalysisContext compilationAnalysisContext) { } private void addJobs(ProjectServiceInfo projectServiceInfo) { + if (projectServiceInfo.getTask().isPresent()) { Task task = projectServiceInfo.getTask().get(); JobModel jobModel = new JobModel(); @@ -99,36 +107,59 @@ private void addJobs(ProjectServiceInfo projectServiceInfo) { } } - private void addServices(List serviceList) throws KubernetesPluginException { + private void addServices(List serviceList, Package currentPackage) throws KubernetesPluginException { + + KubernetesDataHolder dataHolder = KubernetesContext.getInstance().getDataHolder(); + for (ServiceInfo serviceInfo : serviceList) { List listeners = serviceInfo.getListeners(); for (ListenerInfo listener : listeners) { - ServiceModel serviceModel = new ServiceModel(); - if (KubernetesUtils.isBlank(serviceModel.getName())) { - serviceModel.setName(getValidName(serviceInfo.getServicePath() + SVC_POSTFIX)); - } - - int port = listener.getPort(); - if (serviceModel.getPort() == -1) { - serviceModel.setPort(port); - } - if (serviceModel.getTargetPort() == -1) { - serviceModel.setTargetPort(port); - } - - serviceModel.setProtocol("http"); - - KubernetesContext.getInstance().getDataHolder().addServiceModel(serviceModel); + addService(getValidName(serviceInfo.getServicePath() + SVC_POSTFIX), listener.getPort(), dataHolder); } } + + Optional cloudToml = currentPackage.cloudToml(); + if (cloudToml.isPresent()) { + List numberArray = getNumberArray(new Toml(cloudToml.get().tomlAstNode()), + CONTAINER_IMAGE + ".additionalPorts"); + for (int i = 0, numberArraySize = numberArray.size(); i < numberArraySize; i++) { + Long port = numberArray.get(i); + addService("custom-port-" + i + SVC_POSTFIX, port.intValue(), dataHolder); + } + } + } + + private static void addService(String svcName, int port, KubernetesDataHolder dataHolder) { + + List serviceModelList = dataHolder.getServiceModelList(); + if (serviceModelList.stream().anyMatch(serviceModel -> serviceModel.getPort() == port)) { + return; + } + ServiceModel serviceModel = new ServiceModel(); + if (KubernetesUtils.isBlank(serviceModel.getName())) { + serviceModel.setName(getValidName(svcName)); + } + + if (serviceModel.getPort() == -1) { + serviceModel.setPort(port); + } + if (serviceModel.getTargetPort() == -1) { + serviceModel.setTargetPort(port); + } + + serviceModel.setProtocol("http"); + + dataHolder.addServiceModel(serviceModel); } private void addHPA() { + PodAutoscalerModel podAutoscalerModel = new PodAutoscalerModel(); KubernetesContext.getInstance().getDataHolder().setPodAutoscalerModel(podAutoscalerModel); } private void addDeployments() { + DeploymentModel deploymentModel = new DeploymentModel(); String dockerHost = System.getenv(DOCKER_HOST); diff --git a/compiler-plugin/src/main/java/io/ballerina/c2c/utils/KubernetesUtils.java b/compiler-plugin/src/main/java/io/ballerina/c2c/utils/KubernetesUtils.java index 64cf72d6..81a60dd2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/c2c/utils/KubernetesUtils.java +++ b/compiler-plugin/src/main/java/io/ballerina/c2c/utils/KubernetesUtils.java @@ -66,6 +66,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static io.ballerina.c2c.KubernetesConstants.CONTAINER_IMAGE; import static io.ballerina.c2c.KubernetesConstants.DEPLOYMENT_POSTFIX; import static io.ballerina.c2c.KubernetesConstants.EXECUTABLE_JAR; import static io.ballerina.c2c.KubernetesConstants.JOB_POSTFIX; @@ -91,6 +92,7 @@ public class KubernetesUtils { * @throws IOException If an error occurs when writing to a file */ public static void writeToFile(String context, String outputFileName) throws IOException { + KubernetesDataHolder dataHolder = KubernetesContext.getInstance().getDataHolder(); writeToFile(dataHolder.getK8sArtifactOutputPath(), context, outputFileName); } @@ -104,6 +106,7 @@ public static void writeToFile(String context, String outputFileName) throws IOE * @throws IOException If an error occurs when writing to a file */ public static void writeToFile(Path outputDir, String context, String fileSuffix) throws IOException { + KubernetesDataHolder dataHolder = KubernetesContext.getInstance().getDataHolder(); final String outputName = dataHolder.getOutputName(); Path artifactFileName = outputDir.resolve(outputName + fileSuffix); @@ -138,6 +141,7 @@ public static void writeToFile(Path outputDir, String context, String fileSuffix * @throws KubernetesPluginException If an error occurs when reading file */ public static byte[] readFileContent(Path targetFilePath) throws KubernetesPluginException { + return readFileContent(targetFilePath, C2CDiagnosticCodes.PATH_CONTENT_READ_FAILED); } @@ -149,6 +153,7 @@ public static byte[] readFileContent(Path targetFilePath) throws KubernetesPlugi */ public static byte[] readFileContent(Path targetFilePath, C2CDiagnosticCodes code) throws KubernetesPluginException { + File file = targetFilePath.toFile(); // append if file exists if (file.exists() && !file.isDirectory()) { @@ -169,6 +174,7 @@ public static byte[] readFileContent(Path targetFilePath, C2CDiagnosticCodes cod * @param msg message to be printed */ public static void printError(String msg) { + ERR.println("error [k8s plugin]: " + msg); } @@ -178,6 +184,7 @@ public static void printError(String msg) { * @param msg message to be printed */ public static void printInstruction(String msg) { + OUT.println(msg); } @@ -188,6 +195,7 @@ public static void printInstruction(String msg) { * @throws KubernetesPluginException if an error occurs while deleting */ public static void deleteDirectory(Path path) throws KubernetesPluginException { + Path pathToBeDeleted = path.toAbsolutePath(); if (!Files.exists(pathToBeDeleted)) { return; @@ -211,6 +219,7 @@ public static void deleteDirectory(Path path) throws KubernetesPluginException { * @return true if the String is empty or null */ public static boolean isBlank(String str) { + int strLen; if (str != null && (strLen = str.length()) != 0) { for (int i = 0; i < strLen; ++i) { @@ -229,6 +238,7 @@ public static boolean isBlank(String str) { * @return valid name */ public static String getValidName(String name) { + if (name.startsWith("/")) { name = name.substring(1); } @@ -243,30 +253,34 @@ public static String getValidName(String name) { } public static PackageID getProjectID(Package currentPackage) { + return new PackageID(new Name(currentPackage.packageOrg().value()), new Name(currentPackage.packageName().value()), new Name(currentPackage.packageVersion().value().toString())); } public static Optional getExtension(String filename) { + return Optional.ofNullable(filename) .filter(f -> f.contains(".")) .map(f -> f.substring(filename.lastIndexOf(".") + 1)); } public static String getFileNameOfSecret(SecretModel secretModel) { + Map data = secretModel.getData(); return data.keySet().iterator().next(); } public static String getFileNameOfConfigMap(ConfigMapModel configMapModel) { + Map data = configMapModel.getData(); return data.keySet().iterator().next(); } public static void resolveDockerToml(KubernetesModel model) throws KubernetesPluginException { + KubernetesDataHolder dataHolder = KubernetesContext.getInstance().getDataHolder(); - final String containerImage = "container.image"; Toml toml = dataHolder.getBallerinaCloud(); DockerModel dockerModel = dataHolder.getDockerModel(); dockerModel.setJarFileName(extractJarName(dataHolder.getJarPath()) + EXECUTABLE_JAR); @@ -293,21 +307,22 @@ public static void resolveDockerToml(KubernetesModel model) throws KubernetesPlu dockerModel.setBaseImage(defaultBaseImage); if (toml != null) { dockerModel - .setRegistry(TomlHelper.getString(toml, containerImage + ".repository", null)); - dockerModel.setTag(TomlHelper.getString(toml, containerImage + ".tag", dockerModel.getTag())); - dockerModel.setBaseImage(TomlHelper.getString(toml, containerImage + ".base", defaultBaseImage)); - dockerModel.setEntryPoint(TomlHelper.getString(toml, containerImage + ".entrypoint", + .setRegistry(TomlHelper.getString(toml, CONTAINER_IMAGE + ".repository", null)); + dockerModel.setTag(TomlHelper.getString(toml, CONTAINER_IMAGE + ".tag", dockerModel.getTag())); + dockerModel.setBaseImage(TomlHelper.getString(toml, CONTAINER_IMAGE + ".base", defaultBaseImage)); + dockerModel.setEntryPoint(TomlHelper.getString(toml, CONTAINER_IMAGE + ".entrypoint", dockerModel.getEntryPoint())); - if (model instanceof DeploymentModel) { + if (model instanceof DeploymentModel deploymentModel) { - dockerModel.setName(TomlHelper.getString(toml, containerImage + ".name", + dockerModel.setName(TomlHelper.getString(toml, CONTAINER_IMAGE + ".name", model.getName().replace(DEPLOYMENT_POSTFIX, ""))); String imageName = isBlank(dockerModel.getRegistry()) ? dockerModel.getName() + ":" + dockerModel.getTag() : dockerModel.getRegistry() + "/" + dockerModel.getName() + ":" + dockerModel.getTag(); - ((DeploymentModel) model).setImage(imageName); + deploymentModel.setImage(imageName); + } else { - dockerModel.setName(TomlHelper.getString(toml, containerImage + ".name", + dockerModel.setName(TomlHelper.getString(toml, CONTAINER_IMAGE + ".name", model.getName().replace(JOB_POSTFIX, ""))); String imageName = isBlank(dockerModel.getRegistry()) ? dockerModel.getName() + ":" + dockerModel.getTag() : @@ -345,6 +360,7 @@ public static void resolveDockerToml(KubernetesModel model) throws KubernetesPlu * @param deploymentModel Deployment model */ public static DockerModel getDockerModel(DeploymentModel deploymentModel) { + KubernetesDataHolder dataHolder = KubernetesContext.getInstance().getDataHolder(); DockerModel dockerModel = dataHolder.getDockerModel(); String dockerImage = deploymentModel.getImage(); @@ -367,6 +383,7 @@ public static DockerModel getDockerModel(DeploymentModel deploymentModel) { } public static boolean isBuildOptionDockerOrK8s(String buildOption) { + switch (buildOption) { case "k8s": case "docker": @@ -376,6 +393,7 @@ public static boolean isBuildOptionDockerOrK8s(String buildOption) { } public static String asYaml(T object) throws KubernetesPluginException { + try { return YAML_MAPPER.writeValueAsString(object); } catch (JsonProcessingException e) { @@ -386,6 +404,7 @@ public static String asYaml(T object) throws KubernetesPluginException { } public static boolean isThinJar(Toml ballerinaCloud, DockerModel dockerModel) { + if (!TomlHelper.getBoolean(ballerinaCloud, "settings.thinJar", true)) { return false; } @@ -395,6 +414,7 @@ public static boolean isThinJar(Toml ballerinaCloud, DockerModel dockerModel) { } public static List generateConfigMapVolumeMounts(Collection configMapModels) { + List volumeMounts = new ArrayList<>(); for (ConfigMapModel configMapModel : configMapModels) { final String mountPath = configMapModel.getMountPath(); @@ -412,6 +432,7 @@ public static List generateConfigMapVolumeMounts(Collection generateSecretVolumeMounts(Collection secretModels) { + List volumeMounts = new ArrayList<>(); for (SecretModel secretModel : secretModels) { VolumeMountBuilder volumeMountBuilder = new VolumeMountBuilder() @@ -428,6 +449,7 @@ public static List generateSecretVolumeMounts(Collection getNumberArray(Toml toml, String key) { + Optional valueNode = toml.get(key); + List values = new ArrayList<>(); + if (valueNode.isEmpty()) { + return values; + } + TomlValueNode tomlValueNode = valueNode.get(); + if (tomlValueNode.kind() == TomlType.ARRAY) { + TomlArrayValueNode tomlArrayValueNode = (TomlArrayValueNode) tomlValueNode; + for (TomlValueNode element : tomlArrayValueNode.elements()) { + if (element.kind() == TomlType.INTEGER) { + TomlLongValueNode longValueNode = (TomlLongValueNode) element; + values.add(longValueNode.getValue()); + } + } + } + return values; + } + public static Long getLong(Toml toml, String key) { Optional valueNode = toml.get(key); if (valueNode.isEmpty()) { diff --git a/compiler-plugin/src/main/resources/c2c-schema.json b/compiler-plugin/src/main/resources/c2c-schema.json index 453d0b73..b173c1f8 100644 --- a/compiler-plugin/src/main/resources/c2c-schema.json +++ b/compiler-plugin/src/main/resources/c2c-schema.json @@ -80,6 +80,13 @@ "pattern": "`entrypoint` should not be empty" } }, + "additionalPorts": { + "description": "Additional ports to expose in the container", + "type": "array", + "items": { + "type": "integer" + } + }, "user": { "description": "Sets the username to use when running the image", "type": "object", diff --git a/examples/additional_ports/Ballerina.toml b/examples/additional_ports/Ballerina.toml new file mode 100644 index 00000000..8d9b9033 --- /dev/null +++ b/examples/additional_ports/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "hello" +name= "diff_ports" +version = "0.0.1" + +[build-options] +observabilityIncluded = true +cloud = "k8s" diff --git a/examples/additional_ports/Cloud.toml b/examples/additional_ports/Cloud.toml new file mode 100644 index 00000000..65f22eda --- /dev/null +++ b/examples/additional_ports/Cloud.toml @@ -0,0 +1,8 @@ +[container.image] +repository="xlight05" +name="diff_port" +additionalPorts = [9092] + +[[container.copy.files]] +sourceFile="Config.toml" +target="./Config.toml" diff --git a/examples/additional_ports/Config.toml b/examples/additional_ports/Config.toml new file mode 100644 index 00000000..742367c8 --- /dev/null +++ b/examples/additional_ports/Config.toml @@ -0,0 +1 @@ +port = 9092 diff --git a/examples/additional_ports/hello_file.bal b/examples/additional_ports/hello_file.bal new file mode 100644 index 00000000..4ea222ca --- /dev/null +++ b/examples/additional_ports/hello_file.bal @@ -0,0 +1,11 @@ +import ballerina/http; + +configurable int port = ?; + +listener http:Listener helloEP = new (port); + +service /helloWorld on helloEP { + resource function get sayHello() returns string|error? { + return "Kubernetes!"; + } +}