Add support for configuring new helm repository 65/123465/3
authorrameshiyer27 <ramesh.murugan.iyer@est.tech>
Mon, 23 Aug 2021 15:34:05 +0000 (16:34 +0100)
committerrameshiyer27 <ramesh.murugan.iyer@est.tech>
Mon, 23 Aug 2021 19:42:07 +0000 (20:42 +0100)
Issue-ID: POLICY-3480
Signed-off-by: zrrmmua <ramesh.murugan.iyer@est.tech>
Change-Id: I2499cd8ab9a4cf6390c2c4d834264b3754855d23

participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/SpringFoxConfig.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/controller/ChartController.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/handler/ControlLoopElementHandler.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/helm/HelmClient.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/ChartInfo.java
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/HelmRepository.java [new file with mode: 0644]
participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/service/ChartService.java
participant/participant-impl/participant-impl-kubernetes/src/main/resources/config/application.yaml

diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/SpringFoxConfig.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/configurations/SpringFoxConfig.java
new file mode 100644 (file)
index 0000000..09a4977
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.configurations;
+
+import org.onap.policy.clamp.controlloop.participant.kubernetes.controller.ChartController;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+@Configuration
+public class SpringFoxConfig {
+
+    /**
+     * Docket Spring Fox Config.
+     *
+     * @return Docket
+     */
+    @Bean
+    public Docket api() {
+        return new Docket(DocumentationType.SWAGGER_2).select()
+                .apis(RequestHandlerSelectors.basePackage(ChartController.class.getPackageName()))
+                .paths(PathSelectors.any()).build();
+    }
+}
index 23605e6..d041300 100644 (file)
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartList;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.models.HelmRepository;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.InstallationInfo;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartService;
 import org.onap.policy.common.utils.coder.CoderException;
@@ -48,7 +49,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 @RestController("chartController")
 @RequestMapping("helm")
-@Api(tags = {"chart"})
+@Api(tags = {"k8s-participant"})
 public class ChartController {
 
     @Autowired
@@ -124,7 +125,7 @@ public class ChartController {
      * @throws ServiceException in case of error
      * @throws IOException in case of IO error
      */
-    @PostMapping(path = "/charts", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+    @PostMapping(path = "/onboard/chart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
             produces = MediaType.APPLICATION_JSON_VALUE)
     @ApiOperation(value = "Onboard the Chart")
     @ApiResponses(value = {@ApiResponse(code = 201, message = "Chart Onboarded")})
@@ -150,7 +151,7 @@ public class ChartController {
      * @param version version of the chart
      * @return Status of operation
      */
-    @DeleteMapping(path = "/charts/{name}/{version}")
+    @DeleteMapping(path = "/chart/{name}/{version}")
     @ApiOperation(value = "Delete the chart")
     @ApiResponses(value = {@ApiResponse(code = 204, message = "Chart Deleted")})
     public ResponseEntity<Object> deleteChart(@PathVariable("name") String name,
@@ -164,4 +165,29 @@ public class ChartController {
         chartService.deleteChart(chart);
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
+
+    /**
+     * REST endpoint to configure a helm Repository.
+     *
+     * @param repo Helm repository to be configured
+     * @return Status of the operation
+     * @throws ServiceException in case of error
+     * @throws IOException in case of IO error
+     */
+    @PostMapping(path = "/repo", consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    @ApiOperation(value = "Configure helm repository")
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Repository added")})
+    public ResponseEntity<Object> configureRepo(@RequestBody String repo)
+            throws ServiceException, IOException {
+        HelmRepository repository;
+        try {
+            repository = CODER.decode(repo, HelmRepository.class);
+        } catch (CoderException e) {
+            throw new ServiceException("Error parsing the repository information", e);
+        }
+        chartService.configureRepository(repository);
+
+        return new ResponseEntity<>(HttpStatus.CREATED);
+    }
 }
index 3f59c08..8aa74f3 100644 (file)
@@ -134,10 +134,6 @@ public class ControlLoopElementHandler implements ControlLoopElementListener {
         LOGGER.info("Installation request received for the Helm Chart {} ", chartData);
         try {
             var chartInfo =  CODER.convert(chartData, ChartInfo.class);
-            var repositoryValue = chartData.get("repository");
-            if (repositoryValue != null) {
-                chartInfo.setRepository(repositoryValue.toString());
-            }
             chartService.installChart(chartInfo);
             chartMap.put(element.getId(), chartInfo);
 
index 6a1b986..7954dbb 100644 (file)
 
 package org.onap.policy.clamp.controlloop.participant.kubernetes.helm;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -32,6 +30,7 @@ import java.util.Map;
 import org.apache.commons.io.IOUtils;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.models.HelmRepository;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -56,19 +55,34 @@ public class HelmClient {
      * @throws ServiceException incase of error
      */
     public void installChart(ChartInfo chart) throws ServiceException {
-        var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
-        try {
+        if (! checkNamespaceExists(chart.getNamespace())) {
+            var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
             executeCommand(processBuilder);
-        } catch (ServiceException e) {
-            logger.warn("Namespace not created", e);
         }
-        processBuilder = prepareInstallCommand(chart);
+        var processBuilder = prepareInstallCommand(chart);
         logger.info("Installing helm chart {} from the repository {} ", chart.getChartId().getName(),
-            chart.getRepository());
+            chart.getRepository().getRepoName());
         executeCommand(processBuilder);
         logger.info("Chart {} installed successfully", chart.getChartId().getName());
     }
 
+    /**
+     * Add repository if doesn't exist.
+     * @param repo HelmRepository
+     * @throws ServiceException incase of error
+     */
+    public void addRepository(HelmRepository repo) throws ServiceException {
+        String output = executeCommand(prepareVerifyRepoCommand(repo));
+        if (output.isEmpty()) {
+            logger.info("Adding repository to helm client");
+            executeCommand(prepareRepoAddCommand(repo));
+            logger.debug("Added repository {} to the helm client", repo.getRepoName());
+        } else {
+            logger.info("Repository already exists");
+        }
+    }
+
+
     /**
      * Finds helm chart repository for the chart.
      *
@@ -81,6 +95,7 @@ public class HelmClient {
         updateHelmRepo();
         String repository = verifyConfiguredRepo(chart);
         if (repository != null) {
+            logger.info("Helm chart located in the repository {} ", repository);
             return repository;
         }
         var localHelmChartDir = chartStore.getAppPath(chart.getChartId()).toString();
@@ -88,7 +103,6 @@ public class HelmClient {
         if (verifyLocalHelmRepo(new File(localHelmChartDir + "/" + chart.getChartId().getName()))) {
             repository = localHelmChartDir;
         }
-
         return repository;
     }
 
@@ -104,18 +118,7 @@ public class HelmClient {
         String repository = null;
         var builder = helmRepoVerifyCommand(chart.getChartId().getName());
         String output = executeCommand(builder);
-        try (var reader = new BufferedReader(new InputStreamReader(IOUtils.toInputStream(output,
-            StandardCharsets.UTF_8)))) {
-            String line = reader.readLine();
-            while (line != null) {
-                if (line.contains(chart.getChartId().getName())) {
-                    repository = line.split("/")[0];
-                    logger.info("Helm chart located in the repository {} ", repository);
-                    return repository;
-                }
-                line = reader.readLine();
-            }
-        }
+        repository = verifyOutput(output, chart.getChartId().getName());
         return repository;
     }
 
@@ -164,17 +167,56 @@ public class HelmClient {
         }
     }
 
+    private boolean checkNamespaceExists(String namespace) throws ServiceException {
+        logger.info("Check if namespace {} exists on the cluster", namespace);
+        String output = executeCommand(prepareVerifyNamespaceCommand(namespace));
+        return !output.isEmpty();
+    }
+
+    private String verifyOutput(String output, String value) {
+        for (var line: output.split("\\R")) {
+            if (line.contains(value)) {
+                return line.split("/")[0];
+            }
+        }
+        return null;
+    }
+
+    private ProcessBuilder prepareRepoAddCommand(HelmRepository repo) {
+        // @formatter:off
+        List<String> helmArguments = new ArrayList<>(
+                List.of(
+                        "helm",
+                        "repo",
+                        "add", repo.getRepoName(), repo.getProtocol() + "://" + repo.getAddress() + ":" + repo.getPort()
+                ));
+        if (repo.getUserName() != null && repo.getPassword() != null) {
+            helmArguments.addAll(List.of("--username", repo.getUserName(), "--password",  repo.getPassword()));
+        }
+        return new ProcessBuilder().command(helmArguments);
+    }
+
+    private ProcessBuilder prepareVerifyRepoCommand(HelmRepository repo) {
+        List<String> helmArguments = List.of("sh", "-c", "helm repo ls | grep " + repo.getRepoName());
+        return new ProcessBuilder().command(helmArguments);
+    }
+
+    private ProcessBuilder prepareVerifyNamespaceCommand(String namespace) {
+        List<String> helmArguments = List.of("sh", "-c", "kubectl get ns | grep " + namespace);
+        return new ProcessBuilder().command(helmArguments);
+    }
+
     private ProcessBuilder prepareInstallCommand(ChartInfo chart) {
 
         // @formatter:off
         List<String> helmArguments = new ArrayList<>(
             List.of(
                 "helm",
-                "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartId().getName(),
+                "install", chart.getReleaseName(), chart.getRepository().getRepoName() + "/"
+                            + chart.getChartId().getName(),
                 "--version", chart.getChartId().getVersion(),
                 "--namespace", chart.getNamespace()
-            )
-        );
+            ));
         // @formatter:on
 
         // Verify if values.yaml/override parameters available for the chart
diff --git a/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/HelmRepository.java b/participant/participant-impl/participant-impl-kubernetes/src/main/java/org/onap/policy/clamp/controlloop/participant/kubernetes/models/HelmRepository.java
new file mode 100644 (file)
index 0000000..a495c7b
--- /dev/null
@@ -0,0 +1,39 @@
+/*-
+ * ========================LICENSE_START=================================
+ * Copyright (C) 2021 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.policy.clamp.controlloop.participant.kubernetes.models;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class HelmRepository {
+
+    private String repoName;
+
+    private String protocol;
+
+    private String address;
+
+    private String port;
+
+    private String userName;
+
+    private String password;
+}
index a152218..770bbb2 100644 (file)
@@ -24,6 +24,7 @@ import java.util.Collection;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.helm.HelmClient;
 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
+import org.onap.policy.clamp.controlloop.participant.kubernetes.models.HelmRepository;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -88,18 +89,34 @@ public class ChartService {
      */
     public void installChart(ChartInfo chart) throws ServiceException, IOException {
         if (chart.getRepository() == null) {
-            String repository = findChartRepo(chart);
-            if (repository == null) {
+            String repoName = findChartRepo(chart);
+            if (repoName == null) {
                 logger.error("Chart repository could not be found. Skipping chart Installation "
                     + "for the chart {} ", chart.getChartId().getName());
                 return;
             } else {
-                chart.setRepository(repository);
+                HelmRepository repo = HelmRepository.builder().repoName(repoName).build();
+                chart.setRepository(repo);
             }
+        } else {
+            // Add remote repository if passed via TOSCA
+            configureRepository(chart.getRepository());
         }
         helmClient.installChart(chart);
     }
 
+
+    /**
+     * Configure remote repository.
+     * @param repo HelmRepository
+     * @throws ServiceException incase of error
+     */
+    public void configureRepository(HelmRepository repo) throws ServiceException {
+        if (repo.getAddress() != null && repo.getPort() != null) {
+            helmClient.addRepository(repo);
+        }
+    }
+
     /**
      * Finds helm chart repository for a given chart.
      * @param chart chartInfo.
index a27c33d..713b072 100644 (file)
@@ -13,8 +13,8 @@ participant:
     reportingTimeIntervalMs: 120000
     description: Participant Description
     participantId:
-      name: K8sParticipant0
-      version: 1.0.0
+      name: org.onap.k8s.controlloop.K8SControlLoopParticipant
+      version: 2.3.4
     participantType:
       name: org.onap.k8s.controlloop.K8SControlLoopParticipant
       version: 2.3.4
@@ -42,6 +42,8 @@ server:
   # Configuration of the HTTP/REST server. The parameters are defined and handled by the springboot framework.
   # See springboot documentation.
   port: 8083
+  servlet:
+    context-path: /onap/k8sparticipant
 
 logging:
   # Configuration of logging