2 * ========================LICENSE_START=================================
3 * Copyright (C) 2021 Nordix Foundation. All rights reserved.
4 * ======================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ========================LICENSE_END===================================
19 package org.onap.policy.clamp.controlloop.participant.kubernetes.helm;
21 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.lang.invoke.MethodHandles;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
31 import org.apache.commons.io.IOUtils;
32 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
33 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
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 var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
60 executeCommand(processBuilder);
61 } catch (ServiceException e) {
62 logger.warn("Namespace not created", e);
64 processBuilder = prepareInstallCommand(chart);
65 logger.info("Installing helm chart {} from the repository {} ", chart.getChartId().getName(),
66 chart.getRepository());
67 executeCommand(processBuilder);
68 logger.info("Chart {} installed successfully", chart.getChartId().getName());
72 * Finds helm chart repository for the chart.
74 * @param chart ChartInfo.
75 * @return the chart repository as a string
76 * @throws ServiceException in case of error
77 * @throws IOException in case of IO errors
79 public String findChartRepository(ChartInfo chart) throws ServiceException, IOException {
81 String repository = verifyConfiguredRepo(chart);
82 if (repository != null) {
85 var localHelmChartDir = chartStore.getAppPath(chart.getChartId()).toString();
86 logger.info("Chart not found in helm repositories, verifying local repo {} ", localHelmChartDir);
87 if (verifyLocalHelmRepo(new File(localHelmChartDir + "/" + chart.getChartId().getName()))) {
88 repository = localHelmChartDir;
95 * Verify helm chart in configured repositories.
96 * @param chart chartInfo
98 * @throws IOException incase of error
99 * @throws ServiceException incase of error
101 public String verifyConfiguredRepo(ChartInfo chart) throws IOException, ServiceException {
102 logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartId().getName());
103 String repository = null;
104 var builder = helmRepoVerifyCommand(chart.getChartId().getName());
105 String output = executeCommand(builder);
106 try (var reader = new BufferedReader(new InputStreamReader(IOUtils.toInputStream(output,
107 StandardCharsets.UTF_8)))) {
108 String line = reader.readLine();
109 while (line != null) {
110 if (line.contains(chart.getChartId().getName())) {
111 repository = line.split("/")[0];
112 logger.info("Helm chart located in the repository {} ", repository);
115 line = reader.readLine();
124 * @param chart name and version.
125 * @throws ServiceException incase of error
127 public void uninstallChart(ChartInfo chart) throws ServiceException {
128 executeCommand(prepareUnInstallCommand(chart));
133 * Execute helm cli bash commands .
134 * @param processBuilder processbuilder
135 * @return string output
136 * @throws ServiceException incase of error.
138 public static String executeCommand(ProcessBuilder processBuilder) throws ServiceException {
139 var commandStr = toString(processBuilder);
142 var process = processBuilder.start();
144 int exitValue = process.exitValue();
146 if (exitValue != 0) {
147 var error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
148 if (! error.isEmpty()) {
149 throw new ServiceException("Command execution failed: " + commandStr + " " + error);
153 var output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
154 logger.debug("Command <{}> execution, output: {}", commandStr, output);
157 } catch (InterruptedException ie) {
158 Thread.currentThread().interrupt();
159 throw new ServiceException("Failed to execute the Command: " + commandStr + ", the command was interrupted",
161 } catch (Exception exc) {
162 throw new ServiceException("Failed to execute the Command: " + commandStr, exc);
166 private ProcessBuilder prepareInstallCommand(ChartInfo chart) {
169 List<String> helmArguments = new ArrayList<>(
172 "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartId().getName(),
173 "--version", chart.getChartId().getVersion(),
174 "--namespace", chart.getNamespace()
179 // Verify if values.yaml/override parameters available for the chart
180 var localOverrideYaml = chartStore.getOverrideFile(chart);
182 if (verifyLocalHelmRepo(localOverrideYaml)) {
183 logger.info("Override yaml available for the helm chart");
184 helmArguments.addAll(List.of("--values", localOverrideYaml.getPath()));
187 if (chart.getOverrideParams() != null) {
188 for (Map.Entry<String, String> entry : chart.getOverrideParams().entrySet()) {
189 helmArguments.addAll(List.of("--set", entry.getKey() + "=" + entry.getValue()));
192 return new ProcessBuilder().command(helmArguments);
195 private ProcessBuilder prepareUnInstallCommand(ChartInfo chart) {
196 return new ProcessBuilder("helm", "delete", chart.getReleaseName(), "--namespace",
197 chart.getNamespace());
200 private ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
201 return new ProcessBuilder().command("kubectl", "create", "namespace", namespace);
204 private ProcessBuilder helmRepoVerifyCommand(String chartName) {
205 return new ProcessBuilder().command("sh", "-c", "helm search repo | grep " + chartName);
209 private void updateHelmRepo() throws ServiceException {
210 logger.info("Updating local helm repositories before verifying the chart");
211 executeCommand(new ProcessBuilder().command("helm", "repo", "update"));
212 logger.debug("Helm repositories updated successfully");
215 private boolean verifyLocalHelmRepo(File localFile) {
216 return localFile.exists();
219 protected static String toString(ProcessBuilder processBuilder) {
220 return String.join(" ", processBuilder.command());