3d2d4e4fb0c6cc99273cb84ce43838d4df80cfa3
[cli.git] / profiles / command / src / main / java / org / onap / cli / fw / cmd / cmd / OpenCommandShellCmd.java
1 /*
2  * Copyright 2018 Huawei Technologies Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.cli.fw.cmd.cmd;
18
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29
30 import org.apache.commons.io.FileUtils;
31 import org.onap.cli.fw.cmd.OnapCommand;
32 import org.onap.cli.fw.cmd.conf.OnapCommandCmdConstants;
33 import org.onap.cli.fw.cmd.error.OnapCommandCmdFailure;
34 import org.onap.cli.fw.cmd.schema.OnapCommandSchemaCmdLoader;
35 import org.onap.cli.fw.conf.OnapCommandConfig;
36 import org.onap.cli.fw.error.OnapCommandException;
37 import org.onap.cli.fw.error.OnapCommandExecutionFailed;
38 import org.onap.cli.fw.error.OnapCommandResultEmpty;
39 import org.onap.cli.fw.error.OnapCommandResultMapProcessingFailed;
40 import org.onap.cli.fw.input.OnapCommandParameter;
41 import org.onap.cli.fw.output.OnapCommandResultAttribute;
42 import org.onap.cli.fw.output.OnapCommandResultType;
43 import org.onap.cli.fw.registrar.OnapCommandRegistrar;
44 import org.onap.cli.fw.schema.OnapCommandSchema;
45 import org.onap.cli.fw.store.OnapCommandExecutionStore;
46 import org.onap.cli.fw.utils.OnapCommandUtils;
47 import org.onap.cli.fw.utils.ProcessRunner;
48
49 import com.google.gson.Gson;
50 import com.jayway.jsonpath.JsonPath;
51 import com.jayway.jsonpath.PathNotFoundException;
52
53 import net.minidev.json.JSONArray;
54 import net.minidev.json.JSONObject;
55
56 /**
57  * Hello world.
58  */
59 @OnapCommandSchema(type = "cmd")
60 public class OpenCommandShellCmd extends OnapCommand {
61
62     public OpenCommandShellCmd() {
63         super.addDefaultSchemas(OnapCommandCmdConstants.DEFAULT_PARAMETER_CMD_FILE_NAME);
64     }
65
66     private Map<String, String> resultMap = new HashMap<>();
67
68     private List<String> command;
69
70     private Map<String, String> envs = new HashMap<>();
71
72     private String wd = null;
73
74     private List<Integer> successStatusCodes = new ArrayList<>();
75
76     private List<Integer> passCodes = new ArrayList<>();
77
78     private String output = "$stdout";
79
80     private String error = "$stderr";
81
82     public List<Integer> getSuccessStatusCodes() {
83         return successStatusCodes;
84     }
85
86     public void setSuccessStatusCodes(ArrayList<Integer> successStatusCodes) {
87         this.successStatusCodes = successStatusCodes;
88     }
89
90     public String getWd() {
91         return wd;
92     }
93
94     public void setWd(String wd) {
95         this.wd = wd;
96     }
97
98     public Map<String, String> getEnvs() {
99         return envs;
100     }
101
102     public void setEnvs(Map<String, String> envs) {
103         this.envs = envs;
104     }
105
106
107     public List<String> getCommand() {
108         return command;
109     }
110
111     public void setCommand(List<String> command) {
112         this.command = command;
113     }
114
115     public Map<String, String> getResultMap() {
116         return resultMap;
117     }
118
119     public void setResultMap(Map<String, String> resultMap) {
120         this.resultMap = resultMap;
121     }
122
123     private String getStdoutPath() {
124         String storePath = this.getExecutionContext().getStorePath();
125         storePath = storePath + File.separator + "stdout";
126         return storePath;
127     }
128
129     private String getStderrPath() {
130         String storePath = this.getExecutionContext().getStorePath();
131         storePath = storePath + File.separator + "stderr";
132         return storePath;
133     }
134
135     private String getOutputAttributeFilePath(String attrName, boolean temp) {
136         String storePath = (!temp) ? this.getExecutionContext().getStorePath() : OnapCommandConfig.getPropertyValue("cli.tmp.dir");
137         String randomId = (this.getExecutionContext() != null) ? this.getExecutionContext().getExecutionId() : ("" + System.currentTimeMillis());
138         storePath = storePath + File.separator + randomId + "_" + attrName;
139         return storePath;
140     }
141
142     @Override
143     protected List<String> initializeProfileSchema(Map<String, ?> schemaMap, boolean validate) throws OnapCommandException {
144         return OnapCommandSchemaCmdLoader.parseCmdSchema(this, schemaMap, validate);
145     }
146
147     @Override
148     protected void run() throws OnapCommandException {
149         //Read the input arguments
150         Map<String, OnapCommandParameter> paramMap = this.getParametersMap();
151
152         //process command section
153         Map<String, String> tmpFiles = new HashMap<>();
154         List<String> commandLine = new ArrayList<>();
155         for (String cmdTkn: this.getCommand()) {
156             tmpFiles.putAll(this.formTmpFiles(cmdTkn));
157             String commandLine1 = OnapCommandUtils.replaceLineForSpecialValues(
158                     cmdTkn, tmpFiles);
159             commandLine1 = OnapCommandUtils.replaceLineFromInputParameters(commandLine1, paramMap);
160
161             commandLine.add(commandLine1);
162         }
163
164         long timeout = Long.parseLong(this.getParametersMap().get(OnapCommandCmdConstants.TIMEOUT).getValue().toString());
165
166         //Process command
167         String []cmd = commandLine.toArray(new String []{});
168         String cwd = this.getWd();
169         List <String> envs = new ArrayList<>();
170
171         //add current process environments to sub process
172         for (Map.Entry<String, String> env: System.getenv().entrySet()) {
173             envs.add(env.getKey() + "=" + env.getValue());
174         }
175
176         //add oclip specific environment variables
177         if (this.getExecutionContext() != null) {
178             envs.add("OPEN_CLI_REQUEST_ID=" + this.getExecutionContext().getRequestId());
179             if (this.getExecutionContext().getProfile() != null) {
180                 envs.add("OPEN_CLI_PROFILE=" + this.getExecutionContext().getProfile());
181             }
182             if (OnapCommandRegistrar.getRegistrar().getHost() != null) {
183                 envs.add("OPEN_CLI_RPC_HOST=" + OnapCommandRegistrar.getRegistrar().getHost());
184                 envs.add("OPEN_CLI_RPC_PORT=" + OnapCommandRegistrar.getRegistrar().getPort());
185             }
186         }
187
188         for (String env: this.getEnvs().keySet()) {
189             envs.add(env + "=" + this.getEnvs().get(env));
190         }
191
192         ProcessRunner pr = new ProcessRunner(
193                 cmd,
194                 (envs.size() > 0) ? envs.toArray(new String []{}) : null,
195                 cwd);
196         FileOutputStream stdoutStream = null;
197         FileOutputStream stderrStream = null;
198         String outputValue = "";
199
200         try {
201             pr.setTimeout(timeout);
202
203             if (this.getExecutionContext() != null) {
204
205                 stdoutStream = new FileOutputStream(this.getStdoutPath());
206                 stderrStream = new FileOutputStream(this.getStderrPath());
207
208                 pr.setStdout(stdoutStream);
209                 pr.setStderr(stderrStream);
210
211                 OnapCommandExecutionStore.getStore().storeExectutionDebug(this.getExecutionContext(), pr.toString());
212             }
213
214             this.getResult().setDebugInfo(pr.toString());
215
216             pr.run();
217         } catch (Exception e) {
218             throw new OnapCommandExecutionFailed(this.getName(), e);
219         } finally {
220             if (stdoutStream != null) {
221                 try {
222                     stdoutStream.close();
223                 } catch (IOException e) {
224                     //never occurs  // NOSONAR
225                 }
226             }
227             if (stderrStream != null) {
228                 try {
229                     stderrStream.close();
230                 } catch (IOException e) {
231                     //never occurs  // NOSONAR
232                 }
233             }
234         }
235
236         if (!this.successStatusCodes.contains(pr.getExitCode())) {
237             throw new OnapCommandExecutionFailed(this.getName(), pr.getError(), pr.getExitCode());
238         }
239
240         if (this.output.equals("$stdout")) {
241             if (this.getExecutionContext() != null) {
242                 try (FileInputStream is = new FileInputStream(this.getStdoutPath())){
243                     outputValue = pr.streamToString(is);
244                 } catch (IOException e) {
245                     //never occurs  // NOSONAR
246                 }
247             } else
248                 outputValue = pr.getOutput();
249
250         } else if (this.output.equals("$stderr")) {
251             if (this.getExecutionContext() != null) {
252                 try (FileInputStream is = new FileInputStream(this.getStderrPath())) {
253                     outputValue = pr.streamToString(is);
254                 } catch (IOException e) {
255                     //never occurs  // NOSONAR
256                 }
257             } else
258                 outputValue = pr.getError();
259
260         } else {
261             //remove ${tmp: and closing }
262             String tmpName = this.output.substring(7, this.output.length()-1);
263             String tmpFile = tmpFiles.get("tmp:" + tmpName);
264             if (tmpFile != null) {
265                 try (FileInputStream is = new FileInputStream(tmpFile)) {
266                     outputValue = pr.streamToString(is);
267                 } catch (IOException e) {
268                     //never occurs  // NOSONAR
269                 }
270             }
271         }
272
273         this.getResult().setDebugInfo(pr.toString() + "\n" + outputValue);
274         this.getResult().setOutput(outputValue);
275
276         //populate results
277         if (!this.getResult().getType().name().equalsIgnoreCase(OnapCommandResultType.TEXT.name()))
278             for (Entry<String, String> resultMapEntry : this.getResultMap().entrySet()) {
279                 String attrName = resultMapEntry.getKey();
280                 OnapCommandResultAttribute attr = this.getResult().getRecordsMap().get(attrName);
281
282                 String value = OnapCommandUtils.replaceLineFromInputParameters(resultMapEntry.getValue(), paramMap);
283                 value = OnapCommandUtils.replaceLineForSpecialValues(value);
284                 attr.setValues(this.replaceLineFromOutputResults(value, outputValue));
285             }
286
287         //check for pass/failure
288         if (!this.passCodes.isEmpty() && !this.passCodes.contains(pr.getExitCode())) {
289             this.getResult().setPassed(false);
290         } else {
291             this.getResult().setPassed(true);
292         }
293    }
294
295     public String getOutput() {
296         return output;
297     }
298
299     public void setOutput(String output) {
300         this.output = output;
301     }
302
303     private Map<String, String> formTmpFiles(String line){
304
305         Map<String, String> result = new HashMap<>();
306
307         if (!line.contains("$s{tmp")) {
308             return result;
309         }
310
311         int currentIdx = 0;
312         while (currentIdx < line.length()) {
313             int idxS = line.indexOf("$s{tmp:", currentIdx); //check for output stream
314             if (idxS == -1) {
315                 break;
316             }
317
318             int idxE = line.indexOf("}", idxS);
319             String tmpName = line.substring(idxS + 7, idxE);
320             tmpName = tmpName.trim();
321             String tmpTkns[] = tmpName.split(":");
322             String tmpFileName;
323             String paramName;
324             if (tmpTkns.length == 2) {
325                 tmpFileName = tmpTkns[0];
326                 paramName = tmpTkns[1];
327             } else {
328                 tmpFileName = tmpTkns[0];
329                 paramName = null;
330             }
331
332             String tmpFilePath = this.getOutputAttributeFilePath(tmpFileName, true);
333             if (paramName != null) {
334                 //Write the value of input params into file before passing to command
335                 try {
336                     FileUtils.touch(new File(tmpFilePath));
337                     FileUtils.writeStringToFile(new File(tmpFilePath),
338                             this.getParametersMap().get(paramName).getValue().toString());
339                 } catch (IOException e) {
340                     // NO SONAR
341                 }
342             }
343
344             result.put("tmp:" + tmpFileName, tmpFilePath); //used in output parsing
345             result.put("tmp:" + tmpName, tmpFilePath); //used in line replacement
346
347             currentIdx = idxE + 1;
348         }
349         return result;
350     }
351
352    private ArrayList<String> replaceLineFromOutputResults(String line, String output)
353             throws OnapCommandException {
354
355
356         ArrayList<String> result = new ArrayList<>();
357         if (!line.contains("$o{")) {
358             result.add(line);
359             return result;
360         }
361
362         /**
363          * In case of empty output [] or {}
364          **/
365         if (output.length() <= 2) {
366             return result;
367         }
368
369         int currentIdx = 0;
370
371         // Process  jsonpath macros
372         List<Object> values = new ArrayList<>();
373         String processedPattern = "";
374         currentIdx = 0;
375         int maxRows = 1; // in normal case, only one row will be there
376         while (currentIdx < line.length()) {
377             int idxS = line.indexOf("$o{", currentIdx); //check for output stream
378             if (idxS == -1) {
379                 idxS = line.indexOf("$e{", currentIdx); //check for error stream
380                 if (idxS == -1) {
381                     processedPattern += line.substring(currentIdx);
382                     break;
383                 }
384             }
385             int idxE = line.indexOf("}", idxS);
386             String jsonPath = line.substring(idxS + 3, idxE);
387             jsonPath = jsonPath.trim();
388             Object value = new Object();
389             try {
390                 // Instead of parsing, just assign the json as it is
391                 if (!jsonPath.equals("$"))
392                     value = JsonPath.read(output, jsonPath);
393                 else
394                     value = output;
395             } catch (PathNotFoundException e1) { // NOSONAR
396                 //set to blank for those entries which are missing from the output json
397                 value = "";
398             } catch (Exception e) {
399                 throw new OnapCommandCmdFailure("Invalid json format in command output");
400             }
401
402             if (value instanceof JSONArray) {
403                 JSONArray arr = (JSONArray) value;
404                 if (arr.size() > maxRows) {
405                     maxRows = arr.size();
406                 }
407             }
408             processedPattern += line.substring(currentIdx, idxS) + "%s";
409             values.add(value);
410             currentIdx = idxE + 1;
411         }
412
413         if (processedPattern.isEmpty()) {
414             result.add(line);
415             return result;
416         } else {
417             for (int i = 0; i < maxRows; i++) {
418                 currentIdx = 0;
419                 String bodyProcessedLine = "";
420                 int positionalIdx = 0; // %s positional idx
421                 while (currentIdx < processedPattern.length()) {
422                     int idxS = processedPattern.indexOf("%s", currentIdx);
423                     if (idxS == -1) {
424                         bodyProcessedLine += processedPattern.substring(currentIdx);
425                         break;
426                     }
427
428                     int idxEnd = idxS + 2; // %s
429
430                     try {
431                         Object val = values.get(positionalIdx);
432                         String valStr = String.valueOf(val);
433
434                         if (val instanceof JSONArray) {
435                             JSONArray aJson = (JSONArray) val;
436
437                             if (!aJson.isEmpty()) {
438                                 valStr = aJson.get(i).toString();
439                             } else {
440                                 throw new OnapCommandResultEmpty();
441                             }
442                         }
443
444                         bodyProcessedLine += processedPattern.substring(currentIdx, idxS) + valStr;
445                         currentIdx = idxEnd;
446                         positionalIdx++;
447                     } catch (OnapCommandResultEmpty e) {
448                         throw e;
449                     } catch (Exception e) {
450                         throw new OnapCommandResultMapProcessingFailed(line, e);
451                     }
452                 }
453                 result.add(bodyProcessedLine);
454             }
455
456             return result;
457         }
458     }
459
460 public String getError() {
461     return error;
462 }
463
464 public void setError(String error) {
465     this.error = error;
466 }
467
468 public List<Integer> getPassCodes() {
469     return passCodes;
470 }
471
472 public void setPassCodes(List<Integer> passCodes) {
473     this.passCodes = passCodes;
474 }
475
476 }