/*************************************************************************
* Copyright 2013-2014 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudformation.template;
import com.eucalyptus.cloudformation.CloudFormationException;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.entity.StackEntityHelper;
import com.eucalyptus.cloudformation.entity.VersionedStackEntity;
import com.eucalyptus.cloudformation.resources.ResourceInfo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Map;
public class FunctionEvaluation {
public static final String REF_STR = "Ref";
public static final String CONDITION_STR = "Condition";
public static final String FN_AND = "Fn::And";
public static final String FN_EQUALS = "Fn::Equals";
public static final String FN_IF = "Fn::If";
public static final String FN_NOT = "Fn::Not";
public static final String FN_OR = "Fn::Or";
public static final String FN_BASE64 = "Fn::Base64";
public static final String FN_SELECT = "Fn::Select";
public static final String FN_JOIN = "Fn::Join";
public static final String FN_FIND_IN_MAP = "Fn::FindInMap";
public static final String FN_GET_AZS = "Fn::GetAZs";
public static final String FN_GET_ATT = "Fn::GetAtt";
public static final String FN_SUB = "Fn::Sub";
public static final String AWS_NO_VALUE ="AWS::NoValue" ;
public static boolean mayRepresentStringFunction(JsonNode jsonNode) {
for (IntrinsicFunctions value: IntrinsicFunctions.values()) {
if (value.evaluateMatch(jsonNode).isMatch() && value.mayBeStringFunction()) {
return true;
}
}
return false;
}
public static boolean representsBooleanFunction(JsonNode jsonNode) {
for (IntrinsicFunctions value: IntrinsicFunctions.values()) {
if (value.evaluateMatch(jsonNode).isMatch() && value.isBooleanFunction()) {
return true;
}
}
return false;
}
public static boolean evaluateBoolean(JsonNode jsonNode) throws CloudFormationException {
if (jsonNode == null || !jsonNode.isValueNode() ||
!("true".equalsIgnoreCase(jsonNode.asText()) || "false".equalsIgnoreCase(jsonNode.asText()))) {
throw new ValidationErrorException("Template error: Invalid boolean value " + jsonNode);
}
return "true".equalsIgnoreCase(jsonNode.asText());
}
public static void validateConditionSectionArgTypesWherePossible(JsonNode jsonNode) throws CloudFormationException {
validateArgTypesWherePossible(jsonNode, true);
}
public static void validateNonConditionSectionArgTypesWherePossible(JsonNode jsonNode) throws CloudFormationException {
validateArgTypesWherePossible(jsonNode, false);
}
private static void validateArgTypesWherePossible(JsonNode jsonNode, boolean inConditionsSection)
throws CloudFormationException {
if (jsonNode != null) {
if (jsonNode.isArray()) {
for (int i = 0;i < jsonNode.size(); i++) {
validateConditionSectionArgTypesWherePossible(jsonNode.get(i));
}
} else if (jsonNode.isObject()) {
List<String> fieldNames = Lists.newArrayList(jsonNode.fieldNames());
for (String key: fieldNames) {
validateConditionSectionArgTypesWherePossible(jsonNode.get(key));
}
for (IntrinsicFunction intrinsicFunction: IntrinsicFunctions.values()) {
IntrinsicFunction.MatchResult matchResult = intrinsicFunction.evaluateMatch(jsonNode);
if (matchResult.isMatch()) {
intrinsicFunction.validateArgTypesWherePossible(matchResult);
if (intrinsicFunction == IntrinsicFunctions.CONDITION && !inConditionsSection) {
throw new ValidationErrorException("Template error: Condition token can only be used in Conditions block");
}
if (intrinsicFunction == IntrinsicFunctions.EQUALS && !inConditionsSection) {
// strange error but it is what AWS says
throw new ValidationErrorException("Template error: Fn::Equals cannot be partially collapsed");
}
}
}
}
}
// If not an object or array, nothing to validate
}
public static JsonNode evaluateFunctions(JsonNode jsonNode, Template template, String effectiveUserId) throws CloudFormationException {
if (jsonNode == null) return jsonNode;
if (!jsonNode.isArray() && !jsonNode.isObject()) return jsonNode;
if (jsonNode.isArray()) {
ArrayNode arrayCopy = JsonHelper.createArrayNode();
for (int i = 0;i < jsonNode.size(); i++) {
JsonNode arrayElement = evaluateFunctions(jsonNode.get(i), template, effectiveUserId);
arrayCopy.add(arrayElement);
}
return arrayCopy;
}
// an object node
// Some functions require literal values for arguments, so don't recursively evaluate functions on
// values. (Will do so within functions where appropriate)
for (IntrinsicFunction intrinsicFunction: IntrinsicFunctions.values()) {
IntrinsicFunction.MatchResult matchResult = intrinsicFunction.evaluateMatch(jsonNode);
if (matchResult.isMatch()) {
IntrinsicFunction.ValidateResult validateResult = intrinsicFunction.validateArgTypesWherePossible(matchResult);
return intrinsicFunction.evaluateFunction(validateResult, template, effectiveUserId);
}
}
// Otherwise, not a function, so evaluate functions of values
ObjectNode objectCopy = JsonHelper.createObjectNode();
List<String> fieldNames = Lists.newArrayList(jsonNode.fieldNames());
for (String key: fieldNames) {
JsonNode objectElement = evaluateFunctions(jsonNode.get(key), template, effectiveUserId);
objectCopy.put(key, objectElement);
}
return objectCopy;
}
public static JsonNode evaluateFunctionsPreResourceResolution(JsonNode jsonNode, Template template, String effectiveUserId) throws CloudFormationException {
if (jsonNode == null) return jsonNode;
if (!jsonNode.isArray() && !jsonNode.isObject()) return jsonNode;
if (jsonNode.isArray()) {
ArrayNode arrayCopy = JsonHelper.createArrayNode();
for (int i = 0;i < jsonNode.size(); i++) {
JsonNode arrayElement = evaluateFunctionsPreResourceResolution(jsonNode.get(i), template, effectiveUserId);
arrayCopy.add(arrayElement);
}
return arrayCopy;
}
// an object node
// Some functions require literal values for arguments, so don't recursively evaluate functions on
// values. (Will do so within functions where appropriate)
for (IntrinsicFunction intrinsicFunction: IntrinsicFunctions.values()) {
IntrinsicFunction.MatchResult matchResult = intrinsicFunction.evaluateMatch(jsonNode);
if (matchResult.isMatch()) {
IntrinsicFunction.ValidateResult validateResult = intrinsicFunction.validateArgTypesWherePossible(matchResult);
try {
return intrinsicFunction.evaluateFunction(validateResult, template, effectiveUserId);
} catch (ValidationErrorException ex) {
// for now if we get an error due to a Ref: or Fn::GetAtt call on a resource we can just return the original value
return jsonNode;
}
}
}
// Otherwise, not a function, so evaluate functions of values
ObjectNode objectCopy = JsonHelper.createObjectNode();
List<String> fieldNames = Lists.newArrayList(jsonNode.fieldNames());
for (String key: fieldNames) {
JsonNode objectElement = evaluateFunctionsPreResourceResolution(jsonNode.get(key), template, effectiveUserId);
objectCopy.put(key, objectElement);
}
return objectCopy;
}
public static JsonNode evaluateFunctions(JsonNode jsonNode, VersionedStackEntity stackEntity, Map<String, ResourceInfo> resourceInfoMap, String effectiveUserId) throws CloudFormationException {
Template template = new Template();
template.setResourceInfoMap(resourceInfoMap);
StackEntityHelper.populateTemplateWithStackEntity(template, stackEntity);
JsonNode result = evaluateFunctions(jsonNode, template, effectiveUserId);
// just in case the above function changes the template, put the results back into the stack entity
StackEntityHelper.populateStackEntityWithTemplate(stackEntity, template);
return result;
}
}