90d7218da15e428c84f99141264b7ee3f6e7ed53
[policy/clamp.git] /
1 /*-
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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===================================
17  */
18
19 package org.onap.policy.clamp.controlloop.participant.kubernetes.helm;
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.lang.invoke.MethodHandles;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.List;
29 import org.apache.commons.io.IOUtils;
30 import org.onap.policy.clamp.controlloop.participant.kubernetes.exception.ServiceException;
31 import org.onap.policy.clamp.controlloop.participant.kubernetes.models.ChartInfo;
32 import org.onap.policy.clamp.controlloop.participant.kubernetes.service.ChartStore;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.springframework.beans.factory.annotation.Autowired;
36 import org.springframework.stereotype.Component;
37
38 /**
39  * Client to talk with Helm cli. Supports helm3 + version
40  */
41 @Component
42 public class HelmClient {
43
44     @Autowired
45     private ChartStore chartStore;
46
47     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
48
49     /**
50      * Install a chart.
51      *
52      * @param chart name and version.
53      * @throws ServiceException incase of error
54      */
55     public void installChart(ChartInfo chart) throws ServiceException {
56         var processBuilder = prepareCreateNamespaceCommand(chart.getNamespace());
57         try {
58             executeCommand(processBuilder);
59         } catch (ServiceException e) {
60             logger.warn("Namespace not created", e);
61         }
62         processBuilder = prepareInstallCommand(chart);
63         logger.info("Installing helm chart {} from the repository {} ", chart.getChartName(), chart.getRepository());
64         executeCommand(processBuilder);
65         logger.info("Chart {} installed successfully", chart.getChartName());
66     }
67
68     /**
69      * Finds helm chart repository for the chart.
70      *
71      * @param chart ChartInfo.
72      * @return the chart repository as a string
73      * @throws ServiceException in case of error
74      * @throws IOException in case of IO errors
75      */
76     public String findChartRepository(ChartInfo chart) throws ServiceException, IOException {
77         updateHelmRepo();
78         String repository = verifyConfiguredRepo(chart);
79         if (repository != null) {
80             return repository;
81         }
82         var localHelmChartDir = chartStore.getAppPath(chart.getChartName(), chart.getVersion()).toString();
83         logger.info("Chart not found in helm repositories, verifying local repo {} ", localHelmChartDir);
84         if (verifyLocalHelmRepo(localHelmChartDir + "/" + chart.getChartName())) {
85             repository = localHelmChartDir;
86         }
87
88         return repository;
89     }
90
91     /**
92      * Verify helm chart in configured repositories.
93      * @param chart chartInfo
94      * @return repo name
95      * @throws IOException incase of error
96      * @throws ServiceException incase of error
97      */
98     public String verifyConfiguredRepo(ChartInfo chart) throws IOException, ServiceException {
99         logger.info("Looking for helm chart {} in all the configured helm repositories", chart.getChartName());
100         String repository = null;
101         var builder = helmRepoVerifyCommand(chart.getChartName());
102         String output = executeCommand(builder);
103         try (var reader = new BufferedReader(new InputStreamReader(IOUtils.toInputStream(output,
104                 StandardCharsets.UTF_8)))) {
105             String line = reader.readLine();
106             while (line != null) {
107                 if (line.contains(chart.getChartName())) {
108                     repository = line.split("/")[0];
109                     logger.info("Helm chart located in the repository {} ", repository);
110                     return repository;
111                 }
112                 line = reader.readLine();
113             }
114         }
115         return repository;
116     }
117
118     /**
119      * Uninstall a chart.
120      *
121      * @param chart name and version.
122      * @throws ServiceException incase of error
123      */
124     public void uninstallChart(ChartInfo chart) throws ServiceException {
125         executeCommand(prepareUnInstallCommand(chart));
126     }
127
128     /**
129      * Execute helm cli bash commands .
130      * @param processBuilder processbuilder
131      * @return string output
132      * @throws ServiceException incase of error.
133      */
134     public static String executeCommand(ProcessBuilder processBuilder) throws ServiceException {
135         var commandStr = toString(processBuilder);
136
137         try {
138             var process = processBuilder.start();
139             process.waitFor();
140             int exitValue = process.exitValue();
141
142             if (exitValue != 0) {
143                 var error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
144                 throw new ServiceException("Command execution failed: " + commandStr + " " + error);
145             }
146
147             var output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
148             logger.debug("Command <{}> execution, output: {}", commandStr, output);
149             return output;
150
151         } catch (InterruptedException ie) {
152             Thread.currentThread().interrupt();
153             throw new ServiceException("Failed to execute the Command: " + commandStr + ", the command was interrupted",
154                     ie);
155         } catch (Exception exc) {
156             throw new ServiceException("Failed to execute the Command: " + commandStr, exc);
157         }
158     }
159
160     private ProcessBuilder prepareInstallCommand(ChartInfo chart) {
161
162         // @formatter:off
163         List<String> helmArguments = new ArrayList<>(
164                 Arrays.asList(
165                         "helm",
166                         "install", chart.getReleaseName(), chart.getRepository() + "/" + chart.getChartName(),
167                         "--version", chart.getVersion(),
168                         "--namespace", chart.getNamespace()
169                 )
170         );
171         // @formatter:on
172
173         // Verify if values.yaml available for the chart
174         var overrideFile = chartStore.getOverrideFile(chart).getPath();
175         if (verifyLocalHelmRepo(overrideFile)) {
176             logger.info("Override yaml file available for the helm chart");
177             helmArguments.addAll(Arrays.asList("--values", overrideFile));
178         }
179
180         return new ProcessBuilder().command(helmArguments);
181     }
182
183     private ProcessBuilder prepareUnInstallCommand(ChartInfo chart) {
184         return new ProcessBuilder("helm", "delete", chart.getReleaseName(), "--namespace", chart.getNamespace());
185     }
186
187     private ProcessBuilder prepareCreateNamespaceCommand(String namespace) {
188         return new ProcessBuilder().command("kubectl", "create", "namespace", namespace);
189     }
190
191     private ProcessBuilder helmRepoVerifyCommand(String chartName) {
192         return new ProcessBuilder().command("bash", "-c", "helm search repo | grep " + chartName);
193     }
194
195     private ProcessBuilder localRepoVerifyCommand(String localFile) {
196         return new ProcessBuilder().command("bash", "-c", "ls " + localFile);
197     }
198
199     private void updateHelmRepo() throws ServiceException {
200         logger.info("Updating local helm repositories before verifying the chart");
201         executeCommand(new ProcessBuilder().command("helm", "repo", "update"));
202         logger.debug("Helm repositories updated successfully");
203     }
204
205     private boolean verifyLocalHelmRepo(String localFile) {
206         var isVerified = false;
207         var processBuilder = localRepoVerifyCommand(localFile);
208         try {
209             executeCommand(processBuilder);
210             isVerified = true;
211         } catch (ServiceException e) {
212             logger.error("Unable to verify file in local repository", e);
213         }
214         return isVerified;
215     }
216
217     protected static String toString(ProcessBuilder processBuilder) {
218         return String.join(" ", processBuilder.command());
219     }
220 }