/*******************************************************************************
* Copyright (c) 2012 GigaSpaces Technologies Ltd. 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.
*******************************************************************************/
package org.cloudifysource.shell.commands;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.cloudifysource.domain.ComputeTemplateHolder;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.internal.DSLReader;
import org.cloudifysource.dsl.internal.DSLUtils;
import org.cloudifysource.dsl.internal.packaging.Packager;
import org.cloudifysource.dsl.rest.AddTemplatesException;
import org.cloudifysource.dsl.rest.request.AddTemplatesRequest;
import org.cloudifysource.dsl.rest.response.AddTemplateResponse;
import org.cloudifysource.dsl.rest.response.AddTemplatesResponse;
import org.cloudifysource.dsl.rest.response.AddTemplatesStatus;
import org.cloudifysource.restclient.RestClient;
import org.cloudifysource.shell.ShellUtils;
import org.cloudifysource.shell.exceptions.CLIStatusException;
import org.cloudifysource.shell.installer.CLIEventsDisplayer;
import org.cloudifysource.shell.rest.RestAdminFacade;
import org.cloudifysource.utilitydomain.data.reader.ComputeTemplatesReader;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.fusesource.jansi.Ansi.Color;
/**
* Adds templates to be included in the cloud's templates list. Reads the templates from the (groovy) templates-file.
*
* Required arguments: templates-file-or-folder - Path to a single groovy file (one template to add) or to a folder
* (zipped or not) contains one or more groovy files each groovy file has the form of "*-template.groovy" and declare
* one template to add.
*
* Command syntax: add-templates templates-file-or-folder
*
* @author yael
*
* @since 2.3.0
*
*/
@Command(scope = "cloudify", name = "add-templates", description = "Adds templates to the cloud")
public class AddTemplates extends AdminAwareCommand implements NewRestClientCommand {
@Argument(required = true, name = "templates-file-or-folder",
description = "A template file or a templates folder that can contain several template files.")
private File templatesFileOrDir;
private final CLIEventsDisplayer displayer = new CLIEventsDisplayer();
@Override
protected Object doExecute() throws Exception {
final String templatesPath = templatesFileOrDir.getAbsolutePath();
validateTemplateFile(templatesPath);
final boolean isZipFile = isZipFile(templatesFileOrDir);
final File templatesFolder = getTemplatesFolder(isZipFile);
final List<ComputeTemplateHolder> expectedTemplates =
new ComputeTemplatesReader().readCloudTemplatesFromDirectory(templatesFolder);
File zipFile = templatesFileOrDir;
if (!isZipFile) {
zipFile = Packager.createZipFile("templates", templatesFolder);
}
// add the templates to the cloud
logger.info("Adding " + expectedTemplates.size() + " templates to cloud.");
List<String> addedTemplates;
try {
addedTemplates = adminFacade.addTemplates(zipFile);
} catch (final CLIStatusException e) {
final String reasonCode = e.getReasonCode();
if (reasonCode.equals(CloudifyErrorMessages.FAILED_TO_ADD_TEMPLATES.getName())
|| reasonCode.equals(CloudifyErrorMessages.PARTLY_FAILED_TO_ADD_TEMPLATES.getName())) {
throw new CLIStatusException(reasonCode, convertArgsToIndentJason(e.getArgs()));
} else if (reasonCode.equals("failed_to_add_all_templates")) {
if (e.getArgs().length > 0) {
throw new CLIStatusException(reasonCode, getIndentMap((Map<String, Object>) e.getArgs()[0]));
}
}
throw e;
}
return getFormattedMessage("templates_added_successfully", Color.GREEN)
+ getFormatedAddedTemplateNamesList(addedTemplates);
}
private File getTemplatesFolder(final boolean isZipFile) throws IOException, CLIStatusException {
final String templatesFolderName = templatesFileOrDir.getName();
if (templatesFileOrDir.isFile()) {
if (isZipFile) {
return new ComputeTemplatesReader().unzipCloudTemplatesFolder(templatesFileOrDir);
}
// templatesFileOrDir is a groovy file
if (!templatesFolderName.endsWith(DSLUtils.TEMPLATE_DSL_FILE_NAME_SUFFIX)) {
throw new CLIStatusException("illegal_template_file_name", templatesFolderName);
}
final File parentFile = templatesFileOrDir.getParentFile();
final File[] actualTemplatesDslFiles =
DSLReader.findDefaultDSLFiles(DSLUtils.TEMPLATE_DSL_FILE_NAME_SUFFIX, parentFile);
if (actualTemplatesDslFiles.length > 1) {
throw new CLIStatusException("too_many_template_files", Arrays.toString(actualTemplatesDslFiles));
}
return parentFile;
}
// templatesFileOrDir is a directory
return templatesFileOrDir;
}
private boolean isZipFile(final File templatesFileOrDir) {
final String templatesFolderName = templatesFileOrDir.getName();
return templatesFolderName.endsWith(".zip") || templatesFolderName.endsWith(".jar");
}
private static Object[] convertArgsToIndentJason(final Object[] args) {
final String[] newArgs = new String[args.length];
if (newArgs.length < 2) {
return args;
}
final Map<String, Map<String, String>> failedToAddTemplates = (Map<String, Map<String, String>>) args[0];
final StringBuilder failedToAddTemplatesStr = new StringBuilder();
if (failedToAddTemplates.isEmpty()) {
failedToAddTemplatesStr.append("{ }");
} else {
failedToAddTemplatesStr.append(CloudifyConstants.NEW_LINE)
.append("{")
.append(CloudifyConstants.NEW_LINE);
for (final Entry<String, Map<String, String>> entry : failedToAddTemplates.entrySet()) {
final Map<String, String> failedToAddTemplatesErrDesc = entry.getValue();
failedToAddTemplatesStr.append(CloudifyConstants.TAB_CHAR)
.append(entry.getKey())
.append(":")
.append(CloudifyConstants.NEW_LINE)
.append(CloudifyConstants.TAB_CHAR)
.append("{")
.append(CloudifyConstants.NEW_LINE);
for (final Entry<String, String> templateErrDesc : failedToAddTemplatesErrDesc.entrySet()) {
failedToAddTemplatesStr.append(CloudifyConstants.TAB_CHAR)
.append(CloudifyConstants.TAB_CHAR)
.append(templateErrDesc.getKey())
.append(" - ")
.append(templateErrDesc.getValue())
.append(CloudifyConstants.NEW_LINE);
}
failedToAddTemplatesStr.append(CloudifyConstants.TAB_CHAR)
.append("}")
.append(CloudifyConstants.NEW_LINE);
}
failedToAddTemplatesStr.append("}");
}
newArgs[0] = failedToAddTemplatesStr.toString();
newArgs[1] = getIndentMap((Map<String, Object>) args[1]);
return newArgs;
}
private static String getIndentMap(final Map<String, Object> map) {
final StringBuilder successfullyAddedTemplatesStr = new StringBuilder();
if (map.isEmpty()) {
successfullyAddedTemplatesStr.append("{ }");
} else {
successfullyAddedTemplatesStr.append(CloudifyConstants.NEW_LINE)
.append("{")
.append(CloudifyConstants.NEW_LINE);
for (final Entry<String, Object> entry : map.entrySet()) {
successfullyAddedTemplatesStr.append(CloudifyConstants.TAB_CHAR)
.append(entry.getKey())
.append(": ")
.append(entry.getValue())
.append(CloudifyConstants.NEW_LINE);
}
successfullyAddedTemplatesStr.append("}");
}
return successfullyAddedTemplatesStr.toString();
}
@Override
public Object doExecuteNewRestClient() throws Exception {
final RestClient newRestClient = ((RestAdminFacade) getRestAdminFacade()).getNewRestClient();
final String templatesPath = templatesFileOrDir.getAbsolutePath();
validateTemplateFile(templatesPath);
final boolean isZipFile = isZipFile(templatesFileOrDir);
final File templatesFolder = getTemplatesFolder(isZipFile);
final List<ComputeTemplateHolder> expectedTemplates =
new ComputeTemplatesReader().readCloudTemplatesFromDirectory(templatesFolder);
File zipFile = templatesFileOrDir;
if (!isZipFile) {
zipFile = Packager.createZipFile("templates", templatesFolder);
}
// add the templates to the cloud
logger.info("Adding " + expectedTemplates.size() + " templates to cloud:"
+ printExpectedTemplates(expectedTemplates));
final String uploadKey = ShellUtils.uploadToRepo(newRestClient, zipFile, displayer);
final AddTemplatesRequest request = new AddTemplatesRequest();
request.setUploadKey(uploadKey);
try {
final AddTemplatesResponse response = newRestClient.addTemplates(request);
return getFormattedMessage("templates_added_successfully", Color.GREEN)
+ getSuccessfulMessage(response);
} catch (AddTemplatesException e) {
// failure or partial failure
return getFailureMessage(e.getAddTemplatesResponse());
}
}
private static String getIndentJson(final String body) throws IOException {
if (StringUtils.isBlank(body)) {
return null;
}
StringWriter out = new StringWriter();
JsonParser parser = null;
JsonGenerator gen = null;
try {
JsonFactory fac = new JsonFactory();
parser = fac.createJsonParser(new StringReader(body));
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(parser);
// Create pretty printer:
gen = fac.createJsonGenerator(out);
gen.useDefaultPrettyPrinter();
// Write:
mapper.writeTree(gen, node);
gen.close();
parser.close();
return out.toString();
} finally {
out.close();
if (gen != null) {
gen.close();
}
if (parser != null) {
parser.close();
}
}
}
private static Object getFormatedAddedTemplateNamesList(final List<String> templates) {
int size = templates.size();
final StringBuilder sb = new StringBuilder(CloudifyConstants.NEW_LINE)
.append("The ").append(size).append(" template" + (size == 1 ? "" : "s") + " added:");
for (final String templateName : templates) {
sb.append(CloudifyConstants.NEW_LINE)
.append(CloudifyConstants.TAB_CHAR)
.append(templateName);
}
return sb;
}
private String getFailureMessage(final AddTemplatesResponse addTemplatesResponse)
throws IOException {
List<String> instances = addTemplatesResponse.getInstances();
int size = instances.size();
StringBuilder sb = new StringBuilder("Add templates to "
+ size + " REST instance" + (size == 1 ? " " : "s ") + instances + " resulted with ");
if (AddTemplatesStatus.PARTIAL_FAILURE.equals(addTemplatesResponse.getStatus())) {
sb.append("partial failure (at least one template failed to be added to at least one REST instance):");
} else {
sb.append("failure (all templates failed to be added to all REST instances):");
}
sb.append(CloudifyConstants.NEW_LINE);
Map<String, AddTemplateResponse> templates = addTemplatesResponse.getTemplates();
Map<String, Map<String, String>> resultMap = new HashMap<String, Map<String, String>>();
ObjectMapper objectMapper = new ObjectMapper();
for (Entry<String, AddTemplateResponse> entry : templates.entrySet()) {
AddTemplateResponse addTemplateResponse = entry.getValue();
Map<String, String> templateResultMap = new HashMap<String, String>();
templateResultMap.put("failed to add to",
objectMapper.writeValueAsString(addTemplateResponse.getFailedToAddHosts()));
templateResultMap.put("successfully added to",
objectMapper.writeValueAsString(addTemplateResponse.getSuccessfullyAddedHosts()));
resultMap.put(entry.getKey(), templateResultMap);
}
sb.append(getIndentJson(objectMapper.writeValueAsString(resultMap)));
return sb.toString().replaceAll("\"", "").replaceAll("\\\\", "");
}
private String getSuccessfulMessage(final AddTemplatesResponse response) {
StringBuilder sb = new StringBuilder();
sb.append("Templates were added to all REST instances: ");
sb.append(response.getInstances());
Map<String, AddTemplateResponse> templates = response.getTemplates();
List<String> templateNames = new LinkedList<String>();
for (String templateName : templates.keySet()) {
templateNames.add(templateName);
}
sb.append(getFormatedAddedTemplateNamesList(templateNames));
return sb.toString();
}
private String printExpectedTemplates(final List<ComputeTemplateHolder> expectedTemplates) {
final StringBuilder sb = new StringBuilder();
for (final ComputeTemplateHolder computeTemplateHolder : expectedTemplates) {
sb.append(CloudifyConstants.NEW_LINE);
sb.append(computeTemplateHolder.getName());
}
return sb.toString();
}
private void validateTemplateFile(final String templatesPath)
throws CLIStatusException {
logger.info("Validating template folder and files: " + templatesPath);
if (!templatesFileOrDir.exists()) {
throw new CLIStatusException("templates_file_not_found", templatesPath);
}
}
}