2 * ========================LICENSE_START=================================
3 * Copyright (C) 2021 Nordix Foundation. All rights reserved.
4 * ======================================================================
5 * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
6 * ======================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ========================LICENSE_END===================================
21 package org.onap.policy.clamp.controlloop.participant.kubernetes.helm;
24 import java.io.IOException;
25 import java.lang.invoke.MethodHandles;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.List;
30 import org.apache.commons.io.IOUtils;
31 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
32 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
33 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.HelmRepository;
34 import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartStore;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.springframework.beans.factory.annotation.Autowired;
38 import org.springframework.stereotype.Component;
41 * Client to talk with Helm cli. Supports helm3 + version
44 public class HelmClient {
47 private ChartStore chartStore;
49 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54 * @param chart name and version.
55 * @throws ServiceException incase of error
57 public void installChart(ChartInfo chart) throws ServiceException {
58 if (! checkNamespaceExists(chart.getNamespace())) {
59 var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
60 executeCommand(processBuilder);
62 var processBuilder = prepareInstallCommand(chart);
63 logger.info("Installing helm chart {} from the repository {} ", chart.getChartId().getName(),
64 chart.getRepository().getRepoName());
65 executeCommand(processBuilder);
66 logger.info("Chart {} installed successfully", chart.getChartId().getName());
70 * Add repository if doesn't exist.
71 * @param repo HelmRepository
72 * @throws ServiceException incase of error
74 public void addRepository(HelmRepository repo) throws ServiceException {
75 String output = executeCommand(prepareVerifyRepoCommand(repo));
76 if (output.isEmpty()) {
77 logger.info("Adding repository to helm client");
78 executeCommand(prepareRepoAddCommand(repo));
79 logger.debug("Added repository {} to the helm client", repo.getRepoName());
81 logger.info("Repository already exists");
87 * Finds helm chart repository for the chart.
89 * @param chart ChartInfo.
90 * @return the chart repository as a string
91 * @throws ServiceException in case of error
92 * @throws IOException in case of IO errors
94 public String findChartRepository(ChartInfo chart) throws ServiceException, IOException {
96 String repository = verifyConfiguredRepo(chart);
97 if (repository != null) {
98 logger.info("Helm chart located in the repository {} ", repository);
101 var localHelmChartDir = chartStore.getAppPath(chart.getChartId()).toString();
102 logger.info("Chart not found in helm repositories, verifying local repo {} ", localHelmChartDir);
103 if (verifyLocalHelmRepo(new File(localHelmChartDir + "/" + chart.getChartId().getName()))) {
104 repository = localHelmChartDir;
110 * Verify helm chart in configured repositories.
111 * @param chart chartInfo
113 * @throws IOException incase of error
114 * @throws ServiceException incase of error
116 public String verifyConfiguredRepo(ChartInfo chart) throws IOException, ServiceException {
117 logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartId().getName());
118 String repository = null;
119 var builder = helmRepoVerifyCommand(chart.getChartId().getName());
120 String output = executeCommand(builder);
121 repository = verifyOutput(output, chart.getChartId().getName());
128 * @param chart name and version.
129 * @throws ServiceException incase of error
131 public void uninstallChart(ChartInfo chart) throws ServiceException {
132 executeCommand(prepareUnInstallCommand(chart));
137 * Execute helm cli bash commands .
138 * @param processBuilder processbuilder
139 * @return string output
140 * @throws ServiceException incase of error.
142 public static String executeCommand(ProcessBuilder processBuilder) throws ServiceException {
143 var commandStr = toString(processBuilder);
146 var process = processBuilder.start();
148 int exitValue = process.exitValue();
150 if (exitValue != 0) {
151 var error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
152 if (! error.isEmpty()) {
153 throw new ServiceException("Command execution failed: " + commandStr + " " + error);
157 var output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
158 logger.debug("Command <{}> execution, output: {}", commandStr, output);
161 } catch (InterruptedException ie) {
162 Thread.currentThread().interrupt();
163 throw new ServiceException("Failed to execute the Command: " + commandStr + ", the command was interrupted",
165 } catch (Exception exc) {
166 throw new ServiceException("Failed to execute the Command: " + commandStr, exc);
170 private boolean checkNamespaceExists(String namespace) throws ServiceException {
171 logger.info("Check if namespace {} exists on the cluster", namespace);
172 String output = executeCommand(prepareVerifyNamespaceCommand(namespace));
173 return !output.isEmpty();
176 private String verifyOutput(String output, String value) {
177 for (var line: output.split("\\R")) {
178 if (line.contains(value)) {
179 return line.split("/")[0];
185 private ProcessBuilder prepareRepoAddCommand(HelmRepository repo) {
186 var url = repo.getProtocol() + "://" + repo.getAddress();
187 if (repo.getPort() != null) {
188 url = url + ":" + repo.getPort();
191 List<String> helmArguments = new ArrayList<>(
195 "add", repo.getRepoName(), url
197 if (repo.getUserName() != null && repo.getPassword() != null) {
198 helmArguments.addAll(List.of("--username", repo.getUserName(), "--password", repo.getPassword()));
200 return new ProcessBuilder().command(helmArguments);
203 private ProcessBuilder prepareVerifyRepoCommand(HelmRepository repo) {
204 List<String> helmArguments = List.of("sh", "-c", "helm repo ls | grep " + repo.getRepoName());
205 return new ProcessBuilder().command(helmArguments);
208 private ProcessBuilder prepareVerifyNamespaceCommand(String namespace) {
209 List<String> helmArguments = List.of("sh", "-c", "kubectl get ns | grep " + namespace);
210 return new ProcessBuilder().command(helmArguments);
213 private ProcessBuilder prepareInstallCommand(ChartInfo chart) {
216 List<String> helmArguments = new ArrayList<>(
219 "install", chart.getReleaseName(), chart.getRepository().getRepoName() + "/"
220 + chart.getChartId().getName(),
221 "--version", chart.getChartId().getVersion(),
222 "--namespace", chart.getNamespace()
226 // Verify if values.yaml/override parameters available for the chart
227 var localOverrideYaml = chartStore.getOverrideFile(chart);
229 if (verifyLocalHelmRepo(localOverrideYaml)) {
230 logger.info("Override yaml available for the helm chart");
231 helmArguments.addAll(List.of("--values", localOverrideYaml.getPath()));
234 if (chart.getOverrideParams() != null) {
235 for (Map.Entry<String, String> entry : chart.getOverrideParams().entrySet()) {
236 helmArguments.addAll(List.of("--set", entry.getKey() + "=" + entry.getValue()));
239 return new ProcessBuilder().command(helmArguments);
242 private ProcessBuilder prepareUnInstallCommand(ChartInfo chart) {
243 return new ProcessBuilder("helm", "delete", chart.getReleaseName(), "--namespace",
244 chart.getNamespace());
247 private ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
248 return new ProcessBuilder().command("kubectl", "create", "namespace", namespace);
251 private ProcessBuilder helmRepoVerifyCommand(String chartName) {
252 return new ProcessBuilder().command("sh", "-c", "helm search repo | grep " + chartName);
256 private void updateHelmRepo() throws ServiceException {
257 logger.info("Updating local helm repositories before verifying the chart");
258 executeCommand(new ProcessBuilder().command("helm", "repo", "update"));
259 logger.debug("Helm repositories updated successfully");
262 private boolean verifyLocalHelmRepo(File localFile) {
263 return localFile.exists();
266 protected static String toString(ProcessBuilder processBuilder) {
267 return String.join(" ", processBuilder.command());