From 886de44e19cb44fd77bd927c7c4d55532fbf5a44 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Fri, 26 Jan 2018 20:25:34 -0500 Subject: [PATCH 01/10] Fn::Select functionality --- src/validator.ts | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/validator.ts b/src/validator.ts index 574fbea..b8c38dc 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -526,7 +526,9 @@ function resolveIntrinsicFunction(ref: any, key: string) : string | boolean | st case 'Fn::Not': return doIntrinsicNot(ref, key); case 'Fn::ImportValue': - return doIntrinsicImportValue(ref, key); + return doIntrinsicImportValue(ref, key); + case 'Fn::Select': + return doIntrinsicSelect(ref, key); default: addError("warn", `Unhandled Intrinsic Function ${key}, this needs implementing. Some errors might be missed.`, placeInTemplate, "Functions"); return null; @@ -680,6 +682,46 @@ function doIntrinsicGetAZs(ref: any, key: string){ } +function doIntrinsicSelect(ref: any, key: string){ + let toGet = ref[key]; + if(!Array.isArray(toGet) || toGet.length < 2) { + addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + if (toGet[0] === undefined ) { + addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + + let index = toGet[0]; + if (typeof index !== 'number') { + addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + if (toGet[1] === undefined ) { + addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + + let list = toGet[1] + if (!Array.isArray(list)) { + //we may need to resolve it + list = resolveIntrinsicFunction(list, Object.keys(list)[0]); + if (!Array.isArray(list)) { + addError('crit', "Fn::Select requires the second element to be a list, function call did not resolve to a list", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + } + if (index >= 0 && index < list.length) { + return list[index]; + } else { + addError('warn', "First element of Fn::Select exceeds the length of the list, if list is a function, make sure it returns a longer list", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + + +} + function doIntrinsicSub(ref: any, key: string){ let toGet = ref[key]; let replacementStr = null; From 0a0abd7e8e2d89060ff4ea7c5d6d8346bd35e33b Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Fri, 26 Jan 2018 20:33:22 -0500 Subject: [PATCH 02/10] parse the number --- src/validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validator.ts b/src/validator.ts index b8c38dc..b0aa626 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -693,7 +693,7 @@ function doIntrinsicSelect(ref: any, key: string){ return 'INVALID_SELECT'; } - let index = toGet[0]; + let index = parseInt(toGet[0]); if (typeof index !== 'number') { addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; From 5b055580d08698f89e01130135e0cc1c06ae7918 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Sun, 28 Jan 2018 21:11:59 -0500 Subject: [PATCH 03/10] udating with lots of json tests --- src/test/validatorTest.ts | 63 ++++- src/validator.ts | 17 +- .../json/5_invalid_intrinsic_select_1.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_2.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_3.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_4.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_5.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_6.json | 245 ++++++++++++++++++ .../json/5_invalid_intrinsic_select_7.json | 245 ++++++++++++++++++ .../valid/json/5_valid_intrinsic_select.json | 245 ++++++++++++++++++ 10 files changed, 2033 insertions(+), 7 deletions(-) create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_1.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_2.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_3.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_4.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_5.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_6.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_7.json create mode 100644 testData/valid/json/5_valid_intrinsic_select.json diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index 75d3e9c..b6842a7 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -141,6 +141,67 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', true); expect(result['errors']['warn']).to.have.lengthOf(1); }); + describe('Fn::Select', () => { + it("should pass validation, with flat list and intrinsic list", () => { + const input = require('../../testData/valid/json/5_valid_intrinsic_select.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should warn if index is greater than list size", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_1.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(1); + }); + it("should error if second element is not a list or a function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_2.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not a number or does not parse to a number", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_3.json'); + let result = validator.validateJsonObject(input); + console.log(JSON.stringify(result)); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not defined or is null", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_4.json'); + let result = validator.validateJsonObject(input); + console.log(JSON.stringify(result)); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if only one element as argument list", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_5.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element is null or undefined", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_6.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element does not resolve to a list", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_7.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + }); + }); @@ -836,4 +897,4 @@ describe('validator', () => { }) }); -}); \ No newline at end of file +}); diff --git a/src/validator.ts b/src/validator.ts index b0aa626..3b1e1ca 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -688,25 +688,30 @@ function doIntrinsicSelect(ref: any, key: string){ addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } - if (toGet[0] === undefined ) { - addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + if (toGet[0] === undefined || toGet[0] === null) { + addError('crit', "Fn::Select first element cannot be null or undefined", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } let index = parseInt(toGet[0]); - if (typeof index !== 'number') { + if (typeof index !== 'number' || isNaN(index)) { addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } - if (toGet[1] === undefined ) { - addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + if (toGet[1] === undefined || toGet[1] === null) { + addError('crit', "Fn::Select Second element cannot be null or undefined", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } let list = toGet[1] if (!Array.isArray(list)) { //we may need to resolve it - list = resolveIntrinsicFunction(list, Object.keys(list)[0]); + if (typeof list !== 'string') { + list = resolveIntrinsicFunction(list, Object.keys(list)[0]); + } else { + addError('crit', "Fn::Select requires the second element to resolve to a list, it appears to be a string", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } if (!Array.isArray(list)) { addError('crit', "Fn::Select requires the second element to be a list, function call did not resolve to a list", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; diff --git a/testData/invalid/json/5_invalid_intrinsic_select_1.json b/testData/invalid/json/5_invalid_intrinsic_select_1.json new file mode 100644 index 0000000..e2c0284 --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_1.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "2", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : [0, ["beanstalkdb"]]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_2.json b/testData/invalid/json/5_invalid_intrinsic_select_2.json new file mode 100644 index 0000000..e334307 --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_2.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : [0, "beanstalkdb"]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_3.json b/testData/invalid/json/5_invalid_intrinsic_select_3.json new file mode 100644 index 0000000..7d6a26f --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_3.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : ["asdf", ["beanstalkdb"]]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_4.json b/testData/invalid/json/5_invalid_intrinsic_select_4.json new file mode 100644 index 0000000..6f69df3 --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_4.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : ["0"]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_5.json b/testData/invalid/json/5_invalid_intrinsic_select_5.json new file mode 100644 index 0000000..d1f3f44 --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_5.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : [ null, ["beanstalkdb"]]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_6.json b/testData/invalid/json/5_invalid_intrinsic_select_6.json new file mode 100644 index 0000000..c659f9d --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_6.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : ["asdf", null]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_7.json b/testData/invalid/json/5_invalid_intrinsic_select_7.json new file mode 100644 index 0000000..85cc58f --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_7.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "0", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : ["0", ["beanstalkd"]]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} diff --git a/testData/valid/json/5_valid_intrinsic_select.json b/testData/valid/json/5_valid_intrinsic_select.json new file mode 100644 index 0000000..9dd82d8 --- /dev/null +++ b/testData/valid/json/5_valid_intrinsic_select.json @@ -0,0 +1,245 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters": { + + "DBUser": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account name", + "MinLength": "1", + "MaxLength": "16", + "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", + "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." + }, + + "DBPassword": { + "NoEcho": "true", + "Type": "String", + "Description": "Test database admin account password", + "MinLength": "8", + "MaxLength": "41", + "AllowedPattern": "[a-zA-Z0-9]*", + "ConstraintDescription": "must contain only alphanumeric characters." + }, + + "OperatorEMail": { + "Description": "EMail address to notify if there are any operational issues", + "Type": "String", + "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", + "ConstraintDescription": "must be a valid email address." + } + }, + + "Mappings" : { + "Region2Principal" : { + "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, + "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, + "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } + }, + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + } + }, + + "Conditions" : { + "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, + {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, + "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} + }, + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + } ] + }, + "Path": "/" + } + }, + + "WebServerRolePolicy": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName" : "WebServerRole", + "PolicyDocument" : { + "Statement" : [ { + "Effect" : "Allow", + "NotAction" : "iam:*", + "Resource" : "*" + } ] + }, + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "WebServerInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [ { "Ref": "WebServerRole" } ] + } + }, + + "SampleApplication": { + "Type": "AWS::ElasticBeanstalk::Application", + "Properties": { + "Description": "AWS Elastic Beanstalk Sample Application" + } + }, + + "SampleApplicationVersion" : { + "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", + "Properties" : { + "Description" : "Version 1.0", + "ApplicationName" : { "Ref" : "SampleApplication" }, + "SourceBundle" : { + "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, + "S3Key": "CloudFormationBeanstalkRDSExample.war" + } + } + }, + + "SampleConfigurationTemplate" : { + "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", + "Properties" : { + "ApplicationName" : { "Ref" : "SampleApplication" }, + "Description" : "Default Configuration Version 1.0", + "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", + "OptionSettings" : [{ + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "JDBC_CONNECTION_STRING", + "Value": { + "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM1", + "Value": { "Ref": "DBUser" } + }, { + "Namespace": "aws:elasticbeanstalk:application:environment", + "OptionName": "PARAM2", + "Value": { "Ref": "DBPassword" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "SecurityGroups", + "Value": { "Ref": "InstanceSecurityGroup" } + }, { + "Namespace": "aws:autoscaling:launchconfiguration", + "OptionName": "IamInstanceProfile", + "Value": { "Ref": "WebServerInstanceProfile" } + }] + } + }, + + "SampleEnvironment": { + "Type": "AWS::ElasticBeanstalk::Environment", + "Properties": { + "Description": "AWS Elastic Beanstalk Environment running Sample Application", + "ApplicationName": { "Ref": "SampleApplication" }, + "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, + "VersionLabel": { "Ref" : "SampleApplicationVersion" } + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS allows ingress from EC2 instances in this group.", + "SecurityGroupIngress": [] + } + }, + + "DBEC2SecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Condition" : "Is-EC2-VPC", + "Properties" : { + "GroupDescription": "Open database for access", + "SecurityGroupIngress" : [{ + "IpProtocol" : "tcp", + "FromPort" : "3306", + "ToPort" : "3306", + "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } + }] + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Condition" : "Is-EC2-Classic", + "Properties": { + "DBSecurityGroupIngress": [{ + "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } + }], + "GroupDescription": "database access" + } + }, + + "SampleDB": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "Engine": "MySQL", + "DBName": {"Fn::Select" : [0, ["beanstalkdb"]]}, + "MasterUsername": { "Ref": "DBUser" }, + "DBInstanceClass": "db.t2.small", + "AllocatedStorage": "5", + "MasterUserPassword": { "Ref": "DBPassword" }, + "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, + "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} + } + }, + + "AlarmTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] + } + }, + + "CPUAlarmHigh": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "EvaluationPeriods": "10", + "Statistic": "Average", + "Threshold": "50", + "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", + "Period": "60", + "Namespace": "AWS/RDS", + "MetricName": "CPUUtilization", + "Dimensions": [{ + "Name": "DBInstanceIdentifier", + "Value": { "Ref": "SampleDB" } + }], + "ComparisonOperator": "GreaterThanThreshold", + "AlarmActions": [{ "Ref": "AlarmTopic" }], + "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] + } + } + }, + + "Outputs": { + "URL": { + "Description": "URL of the AWS Elastic Beanstalk Environment", + "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] + } + } + } +} From 0bafc7521204f8688f1efd3195eb1114353786a4 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Mon, 29 Jan 2018 15:42:55 -0500 Subject: [PATCH 04/10] small fix for console.log call --- src/test/validatorTest.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index b6842a7..984fd1e 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -166,7 +166,6 @@ describe('validator', () => { it("should error if first element is not a number or does not parse to a number", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_3.json'); let result = validator.validateJsonObject(input); - console.log(JSON.stringify(result)); expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); @@ -174,7 +173,6 @@ describe('validator', () => { it("should error if first element is not defined or is null", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_4.json'); let result = validator.validateJsonObject(input); - console.log(JSON.stringify(result)); expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); From 93b079895e44e4cfda647b4a2a470a44ffa7ad3f Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Tue, 30 Jan 2018 15:56:50 -0500 Subject: [PATCH 05/10] Checking for more possiblities of valid and invalid usages, more tests, with simplified templates... more to come --- data/aws_intrinsic_functions.json | 4 +- src/test/validatorTest.ts | 57 ++++- src/validator.ts | 104 +++++--- .../json/5_invalid_intrinsic_select_1.json | 224 +---------------- .../json/5_invalid_intrinsic_select_10.json | 31 +++ .../json/5_invalid_intrinsic_select_11.json | 31 +++ .../json/5_invalid_intrinsic_select_12.json | 31 +++ .../json/5_invalid_intrinsic_select_2.json | 227 +---------------- .../json/5_invalid_intrinsic_select_3.json | 227 +---------------- .../json/5_invalid_intrinsic_select_4.json | 228 +----------------- .../json/5_invalid_intrinsic_select_5.json | 227 +---------------- .../json/5_invalid_intrinsic_select_6.json | 227 +---------------- .../json/5_invalid_intrinsic_select_7.json | 227 +---------------- .../json/5_invalid_intrinsic_select_8.json | 31 +++ .../json/5_invalid_intrinsic_select_9.json | 31 +++ .../valid/json/5_valid_intrinsic_select.json | 226 +---------------- 16 files changed, 335 insertions(+), 1798 deletions(-) create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_10.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_11.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_12.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_8.json create mode 100644 testData/invalid/json/5_invalid_intrinsic_select_9.json diff --git a/data/aws_intrinsic_functions.json b/data/aws_intrinsic_functions.json index 07aca57..bb9a262 100644 --- a/data/aws_intrinsic_functions.json +++ b/data/aws_intrinsic_functions.json @@ -11,7 +11,9 @@ "Fn::ImportValue": {"supportedFunctions": ["Fn::Base64", "Fn::FindInMap", "Fn::If", "Fn::Join", "Fn::Select", "Fn::Split", "Fn::Sub", "Fn::Ref"]}, "Fn::Join": {}, "Fn::Select": {}, + "Fn::Select::Index": {"supportedFunctions": ["Ref", "Fn::FindInMap"]}, + "Fn::Select::List": {"supportedFunctions" : ["Fn::FindInMap", "Fn::GetAtt", "Fn::GetAZs", "Fn::If", "Fn::Split", "Ref"] }, "Fn::Split": {}, "Fn::Sub": { "supportedFunctions": ["Fn::Base64", "Fn::FindInMap", "Fn::GetAtt", "Fn::GetAZs", "Fn::If", "Fn::Join", "Fn::Select", "Ref"]}, "Ref": {} -} \ No newline at end of file +} diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index 984fd1e..a759aa7 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -149,12 +149,13 @@ describe('validator', () => { expect(result['errors']['crit']).to.have.lengthOf(0); expect(result['errors']['warn']).to.have.lengthOf(0); }); - it("should warn if index is greater than list size", () => { + it("should error if index is greater than list size", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_1.json'); let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', true); - expect(result['errors']['crit']).to.have.lengthOf(0); - expect(result['errors']['warn']).to.have.lengthOf(1); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if second element is not a list or a function", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_2.json'); @@ -162,6 +163,7 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if first element is not a number or does not parse to a number", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_3.json'); @@ -169,6 +171,7 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if first element is not defined or is null", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_4.json'); @@ -176,6 +179,7 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if only one element as argument list", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_5.json'); @@ -183,6 +187,7 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if second element is null or undefined", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_6.json'); @@ -190,6 +195,7 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); it("should error if second element does not resolve to a list", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_7.json'); @@ -197,7 +203,50 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', false); expect(result['errors']['crit']).to.have.lengthOf(1); expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element does not resolve to a number", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_8.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element attempts an invalid intrinsic function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_9.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element is anything other than non-array object, number or string", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_10.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); + }); + + it("should error if second element attempts an invalid intrinsic function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_11.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); + }); + it("should error if second element contains a list with null values", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_12.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(result['errors']['crit'][0]['message']); }); + }); diff --git a/src/validator.ts b/src/validator.ts index 3b1e1ca..887d487 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -683,46 +683,82 @@ function doIntrinsicGetAZs(ref: any, key: string){ } function doIntrinsicSelect(ref: any, key: string){ - let toGet = ref[key]; - if(!Array.isArray(toGet) || toGet.length < 2) { - addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; - } - if (toGet[0] === undefined || toGet[0] === null) { - addError('crit', "Fn::Select first element cannot be null or undefined", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; - } - - let index = parseInt(toGet[0]); - if (typeof index !== 'number' || isNaN(index)) { - addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; - } - if (toGet[1] === undefined || toGet[1] === null) { - addError('crit', "Fn::Select Second element cannot be null or undefined", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; - } + let toGet = ref[key]; + if(!Array.isArray(toGet) || toGet.length < 2) { + addError('crit', "Fn::Select only supports an array of two elements", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + if (toGet[0] === undefined || toGet[0] === null) { + addError('crit', "Fn::Select first element cannot be null or undefined", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + let index: number; + if (typeof toGet[0] == 'object' && !Array.isArray(toGet[0])) { + let keys = Object.keys(toGet[0]); + if(awsIntrinsicFunctions['Fn::Select::Index']['supportedFunctions'].indexOf(keys[0]) != -1) { + let resolvedIndex = resolveIntrinsicFunction(toGet[0], keys[0]); + if(typeof resolvedIndex === 'string') { + index = parseInt(resolvedIndex); + } else if (typeof resolvedIndex === 'number') { + index = resolvedIndex; + } else { + addError('crit', "Fn::Select's first argument did not resolve to a string for parsing or a numeric value.", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } - let list = toGet[1] - if (!Array.isArray(list)) { - //we may need to resolve it - if (typeof list !== 'string') { - list = resolveIntrinsicFunction(list, Object.keys(list)[0]); + } else { + addError('crit', `Fn::Select does not support the ${keys[0]} function in argument 1`); + return 'INVALID_SELECT'; + } + } else if (typeof toGet[0] === 'string') { + index = parseInt(toGet[0]) + } else if (typeof toGet[0] === 'number'){ + index = toGet[0]; } else { - addError('crit', "Fn::Select requires the second element to resolve to a list, it appears to be a string", placeInTemplate, "Fn::Select"); + addError('crit', `Fn:Select's first arguement must be a number or resolve to a number, it appears to be a ${typeof(toGet[0])}`, placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } + + if (typeof index === undefined || typeof index !== 'number' || isNaN(index)) { + addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + if (toGet[1] === undefined || toGet[1] === null) { + addError('crit', "Fn::Select Second element cannot be null or undefined", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + + let list = toGet[1] if (!Array.isArray(list)) { - addError('crit', "Fn::Select requires the second element to be a list, function call did not resolve to a list", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; + //we may need to resolve it + if (typeof list !== 'object') { + addError('crit', `Fn::Select requires the second element to resolve to a list, it appears to be a ${typeof list}`, placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } else if(typeof list == 'object'){ + let keys = Object.keys(list); + if(awsIntrinsicFunctions['Fn::Select::List']['supportedFunctions'].indexOf(keys[0]) != -1) { + list = resolveIntrinsicFunction(list, keys[0]); + } else { + addError('crit', `Fn::Select does not support the ${keys[0]} function in argument 2`); + return 'INVALID_SELECT'; + } + } + + + if (!Array.isArray(list)) { + addError('crit', "Fn::Select requires the second element to be a list, function call did not resolve to a list", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; + } + } else if (list.indexOf(null) > -1) { + addError('crit', "FnSelect requires that the list be free of null values", placeInTemplate, "Fn::Select"); + + } + if (index >= 0 && index < list.length) { + return list[index]; + } else { + addError('crit', "First element of Fn::Select exceeds the length of the list, if list is a function, make sure it returns a longer list", placeInTemplate, "Fn::Select"); + return 'INVALID_SELECT'; } - } - if (index >= 0 && index < list.length) { - return list[index]; - } else { - addError('warn', "First element of Fn::Select exceeds the length of the list, if list is a function, make sure it returns a longer list", placeInTemplate, "Fn::Select"); - return 'INVALID_SELECT'; - } } diff --git a/testData/invalid/json/5_invalid_intrinsic_select_1.json b/testData/invalid/json/5_invalid_intrinsic_select_1.json index e2c0284..1875611 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_1.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_1.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,13 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, + "Principal": { "Service": "ec2.amazonaws.com"}, "Action" : { "Fn::Select" : [ "2", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : [0, ["beanstalkdb"]]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } diff --git a/testData/invalid/json/5_invalid_intrinsic_select_10.json b/testData/invalid/json/5_invalid_intrinsic_select_10.json new file mode 100644 index 0000000..ef68eee --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_10.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with invalid value", + + + "Mappings" : { + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1", "b": ["asd"] } + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select": [["0"], ["sts:AssumeRole"]]} + } ] + }, + "Path": "/" + } + } + + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_11.json b/testData/invalid/json/5_invalid_intrinsic_select_11.json new file mode 100644 index 0000000..417aebf --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_11.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with invalid value", + + + "Mappings" : { + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1", "b": ["asd"] } + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select": ["0", {"Fn::Select" : ["0", ["InvalidLists", "sts::AssumeRole"]]}]} + } ] + }, + "Path": "/" + } + } + + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_12.json b/testData/invalid/json/5_invalid_intrinsic_select_12.json new file mode 100644 index 0000000..c3bac19 --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_12.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with invalid value", + + + "Mappings" : { + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1", "b": ["asd"] } + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select": ["0", ["sts:AssumeRole", null]]} + } ] + }, + "Path": "/" + } + } + + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_2.json b/testData/invalid/json/5_invalid_intrinsic_select_2.json index e334307..b0b55a7 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_2.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_2.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,14 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ "0", "sts:AssumeRole"]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : [0, "beanstalkdb"]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_3.json b/testData/invalid/json/5_invalid_intrinsic_select_3.json index 7d6a26f..672e808 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_3.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_3.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,14 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ "asdf", ["sts:AssumeRole"]]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : ["asdf", ["beanstalkdb"]]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_4.json b/testData/invalid/json/5_invalid_intrinsic_select_4.json index 6f69df3..f0e56bf 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_4.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_4.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,15 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ null, ["sts:AssumeRole"]]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : ["0"]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_5.json b/testData/invalid/json/5_invalid_intrinsic_select_5.json index d1f3f44..903e0db 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_5.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_5.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,14 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ "abc" ]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : [ null, ["beanstalkdb"]]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_6.json b/testData/invalid/json/5_invalid_intrinsic_select_6.json index c659f9d..c8ba46e 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_6.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_6.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,14 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ "0", null ]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : ["asdf", null]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_7.json b/testData/invalid/json/5_invalid_intrinsic_select_7.json index 85cc58f..2c4be4e 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_7.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_7.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" } + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,14 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "0", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ "0", { "Fn::FindInMap": ["ListsInAMap", "Lists1", "c"]}]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : ["0", ["beanstalkd"]]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } + diff --git a/testData/invalid/json/5_invalid_intrinsic_select_8.json b/testData/invalid/json/5_invalid_intrinsic_select_8.json new file mode 100644 index 0000000..6d8da5e --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_8.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with invalid value", + + + "Mappings" : { + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1", "b": ["asd"] } + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ {"Fn::FindInMap" : ["ListsInAMap", "Lists2", "b"]}, ["sts:AssumeRole"]]} + } ] + }, + "Path": "/" + } + } + + } +} diff --git a/testData/invalid/json/5_invalid_intrinsic_select_9.json b/testData/invalid/json/5_invalid_intrinsic_select_9.json new file mode 100644 index 0000000..1f3fd5b --- /dev/null +++ b/testData/invalid/json/5_invalid_intrinsic_select_9.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with invalid value", + + + "Mappings" : { + "ListsInAMap" : { + "Lists1" : { "a": [], "b": [], "c": "sts:AssumeRole" }, + "Lists2" : { "a": "1", "b": "asd" } + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": { "Service": "ec2.amazonaws.com"}, + "Action" : { "Fn::Select" : [ {"Fn::Select" : ["0", ["0", "1"]]}, ["sts:AssumeRole"]]} + } ] + }, + "Path": "/" + } + } + + } +} diff --git a/testData/valid/json/5_valid_intrinsic_select.json b/testData/valid/json/5_valid_intrinsic_select.json index 9dd82d8..7e45333 100644 --- a/testData/valid/json/5_valid_intrinsic_select.json +++ b/testData/valid/json/5_valid_intrinsic_select.json @@ -1,66 +1,16 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template ElasticBeanstalk_Simple: Configure and launch an AWS Elastic Beanstalk application that connects to an Amazon RDS database instance. Monitoring is setup on the database. **WARNING** This template creates one or more Amazon EC2 instances and an Amazon Relational Database Service database instance. You will be billed for the AWS resources used if you create a stack from this template.", - - "Parameters": { - - "DBUser": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account name", - "MinLength": "1", - "MaxLength": "16", - "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", - "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." - }, - - "DBPassword": { - "NoEcho": "true", - "Type": "String", - "Description": "Test database admin account password", - "MinLength": "8", - "MaxLength": "41", - "AllowedPattern": "[a-zA-Z0-9]*", - "ConstraintDescription": "must contain only alphanumeric characters." - }, - - "OperatorEMail": { - "Description": "EMail address to notify if there are any operational issues", - "Type": "String", - "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)", - "ConstraintDescription": "must be a valid email address." - } - }, + "Description": "Template to deploy a Role using Fn::Select with literal and intrinsic values", + "Mappings" : { - "Region2Principal" : { - "us-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "eu-west-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-northeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-southeast-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ap-south-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "us-east-2" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "ca-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "sa-east-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" }, - "cn-north-1" : { "EC2Principal" : "ec2.amazonaws.com.cn", "OpsWorksPrincipal" : "opsworks.amazonaws.com.cn" }, - "eu-central-1" : { "EC2Principal" : "ec2.amazonaws.com", "OpsWorksPrincipal" : "opsworks.amazonaws.com" } - }, "ListsInAMap" : { - "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] } + "Lists1" : { "a": [], "b": [], "c": ["", "sts:AssumeRole"] }, + "Lists2" : { "a": "1"} } }, - "Conditions" : { - "Is-EC2-VPC" : { "Fn::Or" : [ {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "eu-central-1" ]}, - {"Fn::Equals" : [{"Ref" : "AWS::Region"}, "cn-north-1" ]}]}, - "Is-EC2-Classic" : { "Fn::Not" : [{ "Condition" : "Is-EC2-VPC"}]} - }, "Resources": { "WebServerRole": { @@ -69,177 +19,13 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Service": [{ "Fn::FindInMap" : ["Region2Principal", {"Ref" : "AWS::Region"}, "EC2Principal"]}] }, - "Action" : { "Fn::Select" : [ "1", {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} + "Principal": { "Fn::Select" : [ "0", ["ec2.amazonaws.com"]]}, + "Action" : { "Fn::Select" : [ {"Fn::FindInMap" : ["ListsInAMap", "Lists2", "a"]}, {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} } ] }, "Path": "/" } - }, - - "WebServerRolePolicy": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyName" : "WebServerRole", - "PolicyDocument" : { - "Statement" : [ { - "Effect" : "Allow", - "NotAction" : "iam:*", - "Resource" : "*" - } ] - }, - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "WebServerInstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Path": "/", - "Roles": [ { "Ref": "WebServerRole" } ] - } - }, - - "SampleApplication": { - "Type": "AWS::ElasticBeanstalk::Application", - "Properties": { - "Description": "AWS Elastic Beanstalk Sample Application" - } - }, - - "SampleApplicationVersion" : { - "Type" : "AWS::ElasticBeanstalk::ApplicationVersion", - "Properties" : { - "Description" : "Version 1.0", - "ApplicationName" : { "Ref" : "SampleApplication" }, - "SourceBundle" : { - "S3Bucket": { "Fn::Join" : ["-", ["cloudformation-examples", {"Ref" : "AWS::Region" }]]}, - "S3Key": "CloudFormationBeanstalkRDSExample.war" - } - } - }, - - "SampleConfigurationTemplate" : { - "Type" : "AWS::ElasticBeanstalk::ConfigurationTemplate", - "Properties" : { - "ApplicationName" : { "Ref" : "SampleApplication" }, - "Description" : "Default Configuration Version 1.0", - "SolutionStackName" : "64bit Amazon Linux 2015.03 v2.0.1 running Tomcat 7 Java 7", - "OptionSettings" : [{ - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "JDBC_CONNECTION_STRING", - "Value": { - "Fn::Join": ["", ["jdbc:mysql://", { "Fn::GetAtt": ["SampleDB", "Endpoint.Address"] }, ":", { "Fn::GetAtt": ["SampleDB", "Endpoint.Port"] }, "/beanstalkdb" ]] } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM1", - "Value": { "Ref": "DBUser" } - }, { - "Namespace": "aws:elasticbeanstalk:application:environment", - "OptionName": "PARAM2", - "Value": { "Ref": "DBPassword" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "SecurityGroups", - "Value": { "Ref": "InstanceSecurityGroup" } - }, { - "Namespace": "aws:autoscaling:launchconfiguration", - "OptionName": "IamInstanceProfile", - "Value": { "Ref": "WebServerInstanceProfile" } - }] - } - }, - - "SampleEnvironment": { - "Type": "AWS::ElasticBeanstalk::Environment", - "Properties": { - "Description": "AWS Elastic Beanstalk Environment running Sample Application", - "ApplicationName": { "Ref": "SampleApplication" }, - "TemplateName": { "Ref" : "SampleConfigurationTemplate" }, - "VersionLabel": { "Ref" : "SampleApplicationVersion" } - } - }, - - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "RDS allows ingress from EC2 instances in this group.", - "SecurityGroupIngress": [] - } - }, - - "DBEC2SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Condition" : "Is-EC2-VPC", - "Properties" : { - "GroupDescription": "Open database for access", - "SecurityGroupIngress" : [{ - "IpProtocol" : "tcp", - "FromPort" : "3306", - "ToPort" : "3306", - "SourceSecurityGroupName" : { "Ref" : "InstanceSecurityGroup" } - }] - } - }, - - "DBSecurityGroup": { - "Type": "AWS::RDS::DBSecurityGroup", - "Condition" : "Is-EC2-Classic", - "Properties": { - "DBSecurityGroupIngress": [{ - "EC2SecurityGroupName": { "Ref": "InstanceSecurityGroup" } - }], - "GroupDescription": "database access" - } - }, - - "SampleDB": { - "Type": "AWS::RDS::DBInstance", - "Properties": { - "Engine": "MySQL", - "DBName": {"Fn::Select" : [0, ["beanstalkdb"]]}, - "MasterUsername": { "Ref": "DBUser" }, - "DBInstanceClass": "db.t2.small", - "AllocatedStorage": "5", - "MasterUserPassword": { "Ref": "DBPassword" }, - "VPCSecurityGroups": { "Fn::If" : [ "Is-EC2-VPC", [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], { "Ref" : "AWS::NoValue" }]}, - "DBSecurityGroups": { "Fn::If" : [ "Is-EC2-Classic", [ { "Ref": "DBSecurityGroup" } ], { "Ref" : "AWS::NoValue" }]} - } - }, - - "AlarmTopic": { - "Type": "AWS::SNS::Topic", - "Properties": { - "Subscription": [{ "Endpoint": { "Ref": "OperatorEMail" }, "Protocol": "email" }] - } - }, - - "CPUAlarmHigh": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "EvaluationPeriods": "10", - "Statistic": "Average", - "Threshold": "50", - "AlarmDescription": "Alarm if CPU too high or metric disappears indicating the RDS database instance is having issues", - "Period": "60", - "Namespace": "AWS/RDS", - "MetricName": "CPUUtilization", - "Dimensions": [{ - "Name": "DBInstanceIdentifier", - "Value": { "Ref": "SampleDB" } - }], - "ComparisonOperator": "GreaterThanThreshold", - "AlarmActions": [{ "Ref": "AlarmTopic" }], - "InsufficientDataActions": [{ "Ref": "AlarmTopic" }] - } } - }, - "Outputs": { - "URL": { - "Description": "URL of the AWS Elastic Beanstalk Environment", - "Value": { "Fn::Join": ["", ["http://", { "Fn::GetAtt": ["SampleEnvironment", "EndpointURL"] }]] - } - } } } From a754adc89006f09b72ce60d2227d61a7192de3c1 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Wed, 31 Jan 2018 09:24:00 -0500 Subject: [PATCH 06/10] handling commaDelimiedList default data, turning into array --- src/test/validatorTest.ts | 13 +++++++- src/validator.ts | 22 +++++++++++-- .../json/5_valid_intrinsic_select_2.json | 31 +++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 testData/valid/json/5_valid_intrinsic_select_2.json diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index a759aa7..dcb4b91 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -149,7 +149,18 @@ describe('validator', () => { expect(result['errors']['crit']).to.have.lengthOf(0); expect(result['errors']['warn']).to.have.lengthOf(0); }); - it("should error if index is greater than list size", () => { + it("should pass validation with Parameter Collection", () => { + const input = require('../../testData/valid/json/5_valid_intrinsic_select_2.json'); + let result = validator.validateJsonObject(input); + console.log(JSON.stringify(result, null, 4)); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + // console.log(JSON.stringify(result, null, 3)); + }); + + + it("should error if index is greater than list size", () => { const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_1.json'); let result = validator.validateJsonObject(input); expect(result).to.have.deep.property('templateValid', false); diff --git a/src/validator.ts b/src/validator.ts index 887d487..876b43b 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -190,7 +190,8 @@ function assignParametersOutput(guessParameters?: string[]) { // only mock the allowed ones. const okToGuess = (guessAll) || (guessParametersSet.has(parameterName)); - const parameterValue = inferParameterValue(parameterName, parameter, okToGuess); + let parameterValue = inferParameterValue(parameterName, parameter, okToGuess); + if (parameter.hasOwnProperty('AllowedValues') && parameter['AllowedValues'].indexOf(parameterValue) < 0) { addError('crit', `Parameter value '${parameterValue}' for ${parameterName} is` @@ -198,6 +199,15 @@ function assignParametersOutput(guessParameters?: string[]) { } + if(parameter['Type'] === "CommaDelimitedList" && typeof parameterValue === 'string') { + parameterValue = parameterValue.split(',').map(x => x.trim()); + parameterValue.forEach(val => { + if (val === ""){ + addError('crit', `Parameter ${parameterName} contains a CommaDelimitedList where the number of commas appears to be equal or greater than the list of items.`, ['Parameters', parameterName], "Parameters"); + } + }) + } + // Assign an Attribute Ref regardless of any failures above workingInput['Parameters'][parameterName]['Attributes'] = {}; workingInput['Parameters'][parameterName]['Attributes']['Ref'] = parameterValue; @@ -742,11 +752,19 @@ function doIntrinsicSelect(ref: any, key: string){ addError('crit', `Fn::Select does not support the ${keys[0]} function in argument 2`); return 'INVALID_SELECT'; } + console.log(keys[0]); + if (keys[0] === "Ref" ) { + // check if it was a paramter which might be converted to a list + const parameterName = toGet[1][keys[0]]; + if (workingInput['Parameters'][parameterName] !== undefined ) { + list = workingInput['Parameters'][parameterName]['Attributes']['Ref']; + } + } } if (!Array.isArray(list)) { - addError('crit', "Fn::Select requires the second element to be a list, function call did not resolve to a list", placeInTemplate, "Fn::Select"); + addError('crit', `Fn::Select requires the second element to be a list, function call did not resolve to a list. It contains value ${list}`, placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } } else if (list.indexOf(null) > -1) { diff --git a/testData/valid/json/5_valid_intrinsic_select_2.json b/testData/valid/json/5_valid_intrinsic_select_2.json new file mode 100644 index 0000000..b673abb --- /dev/null +++ b/testData/valid/json/5_valid_intrinsic_select_2.json @@ -0,0 +1,31 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Template to deploy a Role using Fn::Select with literal and intrinsic values", + + "Parameters" : { + "RoleActions": { + "Description": "Comma-delimited list of actions", + "Type": "CommaDelimitedList", + "Default": "sts:AssumeRole,iam:listUsers" + } + }, + + + "Resources": { + "WebServerRole": { + "Type": "AWS::IAM::Role", + "Properties" : { + "AssumeRolePolicyDocument" : { + "Statement" : [{ + "Effect" : "Allow", + "Principal": "ec2.amazonaws.com", + "Action" : { "Fn::Select" : ["0", { "Ref": "RoleActions"} ]} + } ] + }, + "Path": "/" + } + } + + } +} From d9927056105dcfb3031c5f639bb10120dc9459ce Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Mon, 5 Feb 2018 10:03:50 -0500 Subject: [PATCH 07/10] updates for yaml tests, minor corrections to crit messages, and change to invalid json, to make sure it is invalid yaml too, as well as checking for null values in the yamlcleanup function --- src/parser.ts | 6 +- src/test/validatorTest.ts | 126 +++++++++++++++++- src/validator.ts | 5 +- testData/invalid/json/invalid_json.json | 2 +- .../yaml/5_invalid_intrinsic_select_1.yaml | 26 ++++ .../yaml/5_invalid_intrinsic_select_10.yaml | 24 ++++ .../yaml/5_invalid_intrinsic_select_11.yaml | 24 ++++ .../yaml/5_invalid_intrinsic_select_12.yaml | 26 ++++ .../yaml/5_invalid_intrinsic_select_2.yaml | 25 ++++ .../yaml/5_invalid_intrinsic_select_3.yaml | 28 ++++ .../yaml/5_invalid_intrinsic_select_4.yaml | 27 ++++ .../yaml/5_invalid_intrinsic_select_5.yaml | 25 ++++ .../yaml/5_invalid_intrinsic_select_6.yaml | 24 ++++ .../yaml/5_invalid_intrinsic_select_7.yaml | 26 ++++ .../yaml/5_invalid_intrinsic_select_8.yaml | 25 ++++ .../yaml/5_invalid_intrinsic_select_9.yaml | 24 ++++ .../valid/json/5_valid_intrinsic_select.json | 2 +- .../json/5_valid_intrinsic_select_2.json | 2 +- .../valid/yaml/5_valid_intrinsic_select.yaml | 24 ++++ .../yaml/5_valid_intrinsic_select_2.yaml | 22 +++ 20 files changed, 482 insertions(+), 11 deletions(-) create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml create mode 100644 testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml create mode 100644 testData/valid/yaml/5_valid_intrinsic_select.yaml create mode 100644 testData/valid/yaml/5_valid_intrinsic_select_2.yaml diff --git a/src/parser.ts b/src/parser.ts index 5601288..53de5e3 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -63,7 +63,7 @@ function cleanupYaml(ref: any){ let key = Object.keys(ref)[i]; // Resolve the function - if(ref[key].hasOwnProperty('class') && ref[key].hasOwnProperty('data')){ + if( ref[key] !== null && ref[key].hasOwnProperty('class') && ref[key].hasOwnProperty('data')){ // We have a Yaml generated object @@ -96,7 +96,7 @@ function cleanupYaml(ref: any){ ref[key] = {}; ref[key][outputKeyName] = outputData; - }else if(key != 'Attributes' && typeof ref[key] == "object"){ + }else if(key != 'Attributes' && typeof ref[key] == "object" && ref[key] !== null){ lastPlaceInTemplate = ref; lastKeyInTemplate = key; cleanupYaml(ref[key]); @@ -104,4 +104,4 @@ function cleanupYaml(ref: any){ } -} \ No newline at end of file +} diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index dcb4b91..6260368 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -152,11 +152,9 @@ describe('validator', () => { it("should pass validation with Parameter Collection", () => { const input = require('../../testData/valid/json/5_valid_intrinsic_select_2.json'); let result = validator.validateJsonObject(input); - console.log(JSON.stringify(result, null, 4)); expect(result).to.have.deep.property('templateValid', true); expect(result['errors']['crit']).to.have.lengthOf(0); expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(JSON.stringify(result, null, 3)); }); @@ -261,6 +259,130 @@ describe('validator', () => { }); + }); + + describe('Fn::Select', () => { + it('should validate in yaml with literal and intrinic elements in array', () => { + const input = './testData/valid/yaml/5_valid_intrinsic_select.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + + + }); + + it('should validate in yaml with Comma Separated List Param', () => { + const input = './testData/valid/yaml/5_valid_intrinsic_select_2.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + + + }); + + + it("should error if index is greater than list size", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if second element is not a list or a function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element is not a number or does not parse to a number", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element is not defined or is null", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if only one element as argument list", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if second element is null or undefined", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if second element does not resolve to a list", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element does not resolve to a number", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element attempts an invalid intrinsic function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if first element is anything other than non-array object, number or string", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + + it("should error if second element attempts an invalid intrinsic function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + it("should error if second element contains a list with null values", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + console.log(result['errors']['crit'][0]['message']); + }); + + + }); describe('Fn::Sub', () => { diff --git a/src/validator.ts b/src/validator.ts index 876b43b..1cb0add 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -752,7 +752,6 @@ function doIntrinsicSelect(ref: any, key: string){ addError('crit', `Fn::Select does not support the ${keys[0]} function in argument 2`); return 'INVALID_SELECT'; } - console.log(keys[0]); if (keys[0] === "Ref" ) { // check if it was a paramter which might be converted to a list const parameterName = toGet[1][keys[0]]; @@ -768,13 +767,13 @@ function doIntrinsicSelect(ref: any, key: string){ return 'INVALID_SELECT'; } } else if (list.indexOf(null) > -1) { - addError('crit', "FnSelect requires that the list be free of null values", placeInTemplate, "Fn::Select"); + addError('crit', "Fn::Select requires that the list be free of null values", placeInTemplate, "Fn::Select"); } if (index >= 0 && index < list.length) { return list[index]; } else { - addError('crit', "First element of Fn::Select exceeds the length of the list, if list is a function, make sure it returns a longer list", placeInTemplate, "Fn::Select"); + addError('crit', "First element of Fn::Select exceeds the length of the list.", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } diff --git a/testData/invalid/json/invalid_json.json b/testData/invalid/json/invalid_json.json index 03dcc4b..f232818 100644 --- a/testData/invalid/json/invalid_json.json +++ b/testData/invalid/json/invalid_json.json @@ -1 +1 @@ -{'some': 'Invalid', json} \ No newline at end of file +{'some': 'Invalid', 'json' => 0} diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml new file mode 100644 index 0000000..01a85d6 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ '2', !FindInMap ['ListsInAMap', 'Lists1', 'c' ] ] + Path: '/' + + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml new file mode 100644 index 0000000..25a52a3 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 + b: ['asd'] +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [["0"], ["sts:AssumeRole"]] + Path: '/' + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml new file mode 100644 index 0000000..9be0196 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 + b: ['asd'] +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select ["0", !Select ["0", [["InvalidLists"], "sts::AssumeRole"]]] + Path: '/' + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml new file mode 100644 index 0000000..6e84a27 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 + b: ['asd'] +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select ["0", ["sts:AssumeRole", null]] + + Path: '/' + + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml new file mode 100644 index 0000000..a99a473 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml @@ -0,0 +1,25 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ '0', 'sts:AssumeRole'] + Path: '/' + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml new file mode 100644 index 0000000..238f345 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml @@ -0,0 +1,28 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ 'asdf', 'sts:AssumeRole' ] + Path: '/' + + + + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml new file mode 100644 index 0000000..c4e5f3d --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml @@ -0,0 +1,27 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select + - !!null + - ['sts:AssumeRole'] + Path: '/' + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml new file mode 100644 index 0000000..11100e0 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml @@ -0,0 +1,25 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ "abc" ] + Path: '/' + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml new file mode 100644 index 0000000..aa7cdcf --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ "0", null ] + Path: '/' + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml new file mode 100644 index 0000000..436f81d --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ "0", !FindInMap ['ListsInAMap', 'Lists1', 'c']] + Path: '/' + + + + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml new file mode 100644 index 0000000..8751ef0 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml @@ -0,0 +1,25 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 + b: ['asd'] +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + Action: !Select [ !FindInMap ['ListsInAMap', 'Lists2', 'b'], ['sts:AssumeRole']] + Path: '/' + + + diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml new file mode 100644 index 0000000..5646657 --- /dev/null +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: 'sts:AssumeRole' + Lists2: + a: 1 + b: ['asd'] +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: 'ec2.amazonaws.com' + + Action: !Select [ !Select ["0", ["0", "1"]], ["sts:AssumeRole"]] + Path: '/' + diff --git a/testData/valid/json/5_valid_intrinsic_select.json b/testData/valid/json/5_valid_intrinsic_select.json index 7e45333..9c64da6 100644 --- a/testData/valid/json/5_valid_intrinsic_select.json +++ b/testData/valid/json/5_valid_intrinsic_select.json @@ -19,7 +19,7 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": { "Fn::Select" : [ "0", ["ec2.amazonaws.com"]]}, + "Principal": { "Service": {"Fn::Select" : [ "0", ["ec2.amazonaws.com"]]}}, "Action" : { "Fn::Select" : [ {"Fn::FindInMap" : ["ListsInAMap", "Lists2", "a"]}, {"Fn::FindInMap" : ["ListsInAMap", "Lists1", "c" ] }]} } ] }, diff --git a/testData/valid/json/5_valid_intrinsic_select_2.json b/testData/valid/json/5_valid_intrinsic_select_2.json index b673abb..57a03a6 100644 --- a/testData/valid/json/5_valid_intrinsic_select_2.json +++ b/testData/valid/json/5_valid_intrinsic_select_2.json @@ -19,7 +19,7 @@ "AssumeRolePolicyDocument" : { "Statement" : [{ "Effect" : "Allow", - "Principal": "ec2.amazonaws.com", + "Principal": { "Service" : "ec2.amazonaws.com"}, "Action" : { "Fn::Select" : ["0", { "Ref": "RoleActions"} ]} } ] }, diff --git a/testData/valid/yaml/5_valid_intrinsic_select.yaml b/testData/valid/yaml/5_valid_intrinsic_select.yaml new file mode 100644 index 0000000..c4023cf --- /dev/null +++ b/testData/valid/yaml/5_valid_intrinsic_select.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Mappings: + ListsInAMap: + Lists1: + a: [] + b: [] + c: + - '' + - 'sts:AssumeRole' + Lists2: + a: 1 +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: + Service: !Select [ 0, ['ec2.amazonaws.com']] + Action: !Select [ !FindInMap ['ListsInAMap', 'Lists2', 'a'], !FindInMap ['ListsInAMap', 'Lists1', 'c' ] ] + + Path: '/' diff --git a/testData/valid/yaml/5_valid_intrinsic_select_2.yaml b/testData/valid/yaml/5_valid_intrinsic_select_2.yaml new file mode 100644 index 0000000..59ae586 --- /dev/null +++ b/testData/valid/yaml/5_valid_intrinsic_select_2.yaml @@ -0,0 +1,22 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Parameters: + RoleActions: + Description: "Comma-delimited list of actions" + Type: "CommaDelimitedList" + Default: "sts:AssumeRole,iam:listUsers" + +Resources: + WebServerRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: 'Allow' + Principal: "ec2.amazonaws.com" + Action: !Select ["0", !Ref 'RoleActions' ] + + Path: '/' + + + From 67b7d98d5c29637983b5cf4736b8406b589de215 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Mon, 5 Feb 2018 10:28:19 -0500 Subject: [PATCH 08/10] fixing typo and spacing --- src/validator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/validator.ts b/src/validator.ts index 1cb0add..03dbe6f 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -536,9 +536,9 @@ function resolveIntrinsicFunction(ref: any, key: string) : string | boolean | st case 'Fn::Not': return doIntrinsicNot(ref, key); case 'Fn::ImportValue': - return doIntrinsicImportValue(ref, key); + return doIntrinsicImportValue(ref, key); case 'Fn::Select': - return doIntrinsicSelect(ref, key); + return doIntrinsicSelect(ref, key); default: addError("warn", `Unhandled Intrinsic Function ${key}, this needs implementing. Some errors might be missed.`, placeInTemplate, "Functions"); return null; @@ -725,7 +725,7 @@ function doIntrinsicSelect(ref: any, key: string){ } else if (typeof toGet[0] === 'number'){ index = toGet[0]; } else { - addError('crit', `Fn:Select's first arguement must be a number or resolve to a number, it appears to be a ${typeof(toGet[0])}`, placeInTemplate, "Fn::Select"); + addError('crit', `Fn:Select's first argument must be a number or resolve to a number, it appears to be a ${typeof(toGet[0])}`, placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } From a1d8af7d6d7f9c4be094afdf612148457ff3fa91 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Wed, 7 Feb 2018 13:52:51 -0500 Subject: [PATCH 09/10] updated test with better comments, and explicit checking of error messages --- src/test/validatorTest.ts | 429 +++++++++--------- src/validator.ts | 2 +- .../json/5_invalid_intrinsic_select_1.json | 2 +- .../json/5_invalid_intrinsic_select_10.json | 2 +- .../json/5_invalid_intrinsic_select_11.json | 2 +- .../json/5_invalid_intrinsic_select_12.json | 2 +- .../json/5_invalid_intrinsic_select_2.json | 2 +- .../json/5_invalid_intrinsic_select_3.json | 2 +- .../json/5_invalid_intrinsic_select_4.json | 2 +- .../json/5_invalid_intrinsic_select_5.json | 2 +- .../json/5_invalid_intrinsic_select_6.json | 2 +- .../json/5_invalid_intrinsic_select_7.json | 2 +- .../json/5_invalid_intrinsic_select_8.json | 2 +- .../json/5_invalid_intrinsic_select_9.json | 2 +- .../yaml/5_invalid_intrinsic_select_1.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_10.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_11.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_12.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_2.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_3.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_4.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_5.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_6.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_7.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_8.yaml | 2 +- .../yaml/5_invalid_intrinsic_select_9.yaml | 4 +- 26 files changed, 238 insertions(+), 243 deletions(-) diff --git a/src/test/validatorTest.ts b/src/test/validatorTest.ts index 6260368..969c53b 100644 --- a/src/test/validatorTest.ts +++ b/src/test/validatorTest.ts @@ -141,135 +141,132 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', true); expect(result['errors']['warn']).to.have.lengthOf(1); }); - describe('Fn::Select', () => { - it("should pass validation, with flat list and intrinsic list", () => { - const input = require('../../testData/valid/json/5_valid_intrinsic_select.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', true); - expect(result['errors']['crit']).to.have.lengthOf(0); - expect(result['errors']['warn']).to.have.lengthOf(0); - }); - it("should pass validation with Parameter Collection", () => { - const input = require('../../testData/valid/json/5_valid_intrinsic_select_2.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', true); - expect(result['errors']['crit']).to.have.lengthOf(0); - expect(result['errors']['warn']).to.have.lengthOf(0); - }); - - it("should error if index is greater than list size", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_1.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element is not a list or a function", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_2.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is not a number or does not parse to a number", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_3.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is not defined or is null", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_4.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if only one element as argument list", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_5.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element is null or undefined", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_6.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element does not resolve to a list", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_7.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element does not resolve to a number", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_8.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element attempts an invalid intrinsic function", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_9.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is anything other than non-array object, number or string", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_10.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - - it("should error if second element attempts an invalid intrinsic function", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_11.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element contains a list with null values", () => { - const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_12.json'); - let result = validator.validateJsonObject(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - // console.log(result['errors']['crit'][0]['message']); - }); - - }); + }); + describe('Fn::Select JSON', () => { + it("should pass validation, with flat list and intrinsic list", () => { + const input = require('../../testData/valid/json/5_valid_intrinsic_select.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should pass validation with Parameter Collection", () => { + const input = require('../../testData/valid/json/5_valid_intrinsic_select_2.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', true); + expect(result['errors']['crit']).to.have.lengthOf(0); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if index is greater than list size", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_1.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf('First element of Fn::Select exceeds the length of the list.')).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element is not a list or a function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_2.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires the second element to resolve to a list")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not a number or does not parse to a number", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_3.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("First element of Fn::Select must be a number, or it must use an intrinsic fuction that returns a number")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not defined or is null", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_4.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select first element cannot be null or undefined")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if only one element as argument list", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_5.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select only supports an array of two elements")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element is null or undefined", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_6.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select Second element cannot be null or undefined")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element does not resolve to a list", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_7.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires the second element to be a list, function call did not resolve to a list.")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element does not resolve to a number", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_8.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select's first argument did not resolve to a string for parsing or a numeric value.")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element attempts an invalid intrinsic function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_9.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select does not support the Fn::Select function in argument 1")).to.be.greaterThan(-1); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is anything other than non-array object, number or string", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_10.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit'][0]['message'].indexOf("Fn:Select's first argument must be a number or resolve to a number")).to.be.greaterThan(-1); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + + it("should error if second element attempts an invalid intrinsic function", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_11.json'); + let result = validator.validateJsonObject(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit'][0]['message'].indexOf("n::Select does not support the Fn::Select function in argument 2")).to.be.greaterThan(-1); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element contains a list with null values", () => { + const input = require('../../testData/invalid/json/5_invalid_intrinsic_select_12.json'); + let result = validator.validateJsonObject(input); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires that the list be free of null values")).to.be.greaterThan(-1); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + }); - describe('Fn::Select', () => { + describe('Fn::Select YAML', () => { it('should validate in yaml with literal and intrinic elements in array', () => { const input = './testData/valid/yaml/5_valid_intrinsic_select.yaml'; let result = validator.validateFile(input); expect(result).to.have.deep.property('templateValid', true); expect(result['errors']['crit']).to.have.lengthOf(0); expect(result['errors']['warn']).to.have.lengthOf(0); - - }); it('should validate in yaml with Comma Separated List Param', () => { @@ -278,108 +275,106 @@ describe('validator', () => { expect(result).to.have.deep.property('templateValid', true); expect(result['errors']['crit']).to.have.lengthOf(0); expect(result['errors']['warn']).to.have.lengthOf(0); - - }); - it("should error if index is greater than list size", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element is not a list or a function", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is not a number or does not parse to a number", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is not defined or is null", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if only one element as argument list", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element is null or undefined", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element does not resolve to a list", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element does not resolve to a number", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element attempts an invalid intrinsic function", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if first element is anything other than non-array object, number or string", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - - it("should error if second element attempts an invalid intrinsic function", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); - it("should error if second element contains a list with null values", () => { - const input = './testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml'; - let result = validator.validateFile(input); - expect(result).to.have.deep.property('templateValid', false); - expect(result['errors']['crit']).to.have.lengthOf(1); - expect(result['errors']['warn']).to.have.lengthOf(0); - console.log(result['errors']['crit'][0]['message']); - }); + it("should error if index is greater than list size", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf('First element of Fn::Select exceeds the length of the list.')).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element is not a list or a function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires the second element to resolve to a list")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not a number or does not parse to a number", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("First element of Fn::Select must be a number, or it must use an intrinsic fuction that returns a number")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is not defined or is null", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select first element cannot be null or undefined")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if only one element as argument list", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select only supports an array of two elements")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element is null or undefined", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select Second element cannot be null or undefined")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element does not resolve to a list", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires the second element to be a list, function call did not resolve to a list.")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element does not resolve to a number", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select's first argument did not resolve to a string for parsing or a numeric value.")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element attempts an invalid intrinsic function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select does not support the Fn::Select function in argument 1")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if first element is anything other than non-array object, number or string", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn:Select's first argument must be a number or resolve to a number")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + + it("should error if second element attempts an invalid intrinsic function", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("n::Select does not support the Fn::Select function in argument 2")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); + it("should error if second element contains a list with null values", () => { + const input = './testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml'; + let result = validator.validateFile(input); + expect(result).to.have.deep.property('templateValid', false); + expect(result['errors']['crit']).to.have.lengthOf(1); + expect(result['errors']['crit'][0]['message'].indexOf("Fn::Select requires that the list be free of null values")).to.be.greaterThan(-1); + expect(result['errors']['warn']).to.have.lengthOf(0); + }); diff --git a/src/validator.ts b/src/validator.ts index 03dbe6f..536d1f0 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -730,7 +730,7 @@ function doIntrinsicSelect(ref: any, key: string){ } if (typeof index === undefined || typeof index !== 'number' || isNaN(index)) { - addError('crit', "First element of Fn::Select must be a number", placeInTemplate, "Fn::Select"); + addError('crit', "First element of Fn::Select must be a number, or it must use an intrinsic fuction that returns a number", placeInTemplate, "Fn::Select"); return 'INVALID_SELECT'; } if (toGet[1] === undefined || toGet[1] === null) { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_1.json b/testData/invalid/json/5_invalid_intrinsic_select_1.json index 1875611..aa27f60 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_1.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_1.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select with index greater than list size", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_10.json b/testData/invalid/json/5_invalid_intrinsic_select_10.json index ef68eee..89f9e66 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_10.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_10.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select, using array for first param", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_11.json b/testData/invalid/json/5_invalid_intrinsic_select_11.json index 417aebf..c6c2c8f 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_11.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_11.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select with use of invalid intrinsic function for param 2", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_12.json b/testData/invalid/json/5_invalid_intrinsic_select_12.json index c3bac19..fb0c0b1 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_12.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_12.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Template with invalid null element in Fn::Select param 2", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_2.json b/testData/invalid/json/5_invalid_intrinsic_select_2.json index b0b55a7..c0526bf 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_2.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_2.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select where second element is not a list or a function", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_3.json b/testData/invalid/json/5_invalid_intrinsic_select_3.json index 672e808..6d83dfb 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_3.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_3.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select where first elememt is a string that does not parse to a number", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_4.json b/testData/invalid/json/5_invalid_intrinsic_select_4.json index f0e56bf..9bfd83b 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_4.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_4.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select with null index", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_5.json b/testData/invalid/json/5_invalid_intrinsic_select_5.json index 903e0db..864a772 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_5.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_5.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select with only one argument", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_6.json b/testData/invalid/json/5_invalid_intrinsic_select_6.json index c8ba46e..a751ce4 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_6.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_6.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select with second element null", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_7.json b/testData/invalid/json/5_invalid_intrinsic_select_7.json index 2c4be4e..d09e37c 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_7.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_7.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select, second element does not resolve to a list", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_8.json b/testData/invalid/json/5_invalid_intrinsic_select_8.json index 6d8da5e..e153957 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_8.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_8.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select, index's intrinsic does not resolve to a numeric", "Mappings" : { diff --git a/testData/invalid/json/5_invalid_intrinsic_select_9.json b/testData/invalid/json/5_invalid_intrinsic_select_9.json index 1f3fd5b..dbe174e 100644 --- a/testData/invalid/json/5_invalid_intrinsic_select_9.json +++ b/testData/invalid/json/5_invalid_intrinsic_select_9.json @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template to deploy a Role using Fn::Select with invalid value", + "Description": "Malformed Fn::Select, invalid intrinsic function use in index param", "Mappings" : { diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml index 01a85d6..5eadbf2 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_1.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select with index greater than list size' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml index 25a52a3..3e345b1 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_10.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: "Malformed Fn::Select, using array for first param" Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml index 9be0196..2ffabcf 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_11.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'TMalformed Fn::Select with use of invalid intrinsic function for param 2.' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml index 6e84a27..508d594 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_12.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Template with invalid null element in Fn::Select param 2' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml index a99a473..f31da81 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_2.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select where second element is not a list or a function' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml index 238f345..2a10e90 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_3.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select where first elememt is a string that does not parse to a number' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml index c4e5f3d..c73da8f 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_4.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select with null index' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml index 11100e0..1f41e68 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_5.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select with only one argument' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml index aa7cdcf..4be62f5 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_6.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select with second element null' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml index 436f81d..992d0fc 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_7.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select, second element does not resolve to a list' Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml index 8751ef0..cb81b84 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_8.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: "Malformed Fn::Select, index's intrinsic does not resolve to a numeric" Mappings: ListsInAMap: Lists1: diff --git a/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml b/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml index 5646657..5b03403 100644 --- a/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml +++ b/testData/invalid/yaml/5_invalid_intrinsic_select_9.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: '2010-09-09' -Description: 'Template to deploy a Role using Fn::Select with literal and intrinsic values' +Description: 'Malformed Fn::Select, invalid intrinsic function use in index param' Mappings: ListsInAMap: Lists1: @@ -8,7 +8,7 @@ Mappings: c: 'sts:AssumeRole' Lists2: a: 1 - b: ['asd'] + b: 'asd' Resources: WebServerRole: Type: 'AWS::IAM::Role' From 4c3f42045d804cfad7100a295886374b56ed40c5 Mon Sep 17 00:00:00 2001 From: "David P. Smith" Date: Thu, 8 Feb 2018 08:59:21 -0500 Subject: [PATCH 10/10] package.json needs to specify approx 2.6.2, otherwise tyepscript 2.7.x breaks --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82aa529..84d1f3b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/winston": "^2.3.7", "chai": "latest", "mocha": "latest", - "typescript": "^2.6.1", + "typescript": "~2.6.2", "dependency-check": "^2.9.1" }, "scripts": {