diff --git a/heat/engine/parser.py b/heat/engine/parser.py index 1c2e3323d8..87a8bbdc8e 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -17,13 +17,13 @@ import eventlet import json import logging import sys - from heat.common import exception from heat.engine import resources from heat.engine import instance from heat.engine import volume from heat.engine import eip from heat.engine import security_group +from heat.engine import user from heat.engine import wait_condition from heat.db import api as db_api @@ -98,6 +98,12 @@ class Stack(object): elif type == 'AWS::CloudFormation::WaitCondition': self.resources[r] = wait_condition.WaitCondition(r, self.t['Resources'][r], self) + elif type == 'AWS::IAM::User': + self.resources[r] = user.User(r, + self.t['Resources'][r], self) + elif type == 'AWS::IAM::AccessKey': + self.resources[r] = user.AccessKey(r, + self.t['Resources'][r], self) else: self.resources[r] = resources.GenericResource(r, self.t['Resources'][r], self) diff --git a/heat/engine/security_group.py b/heat/engine/security_group.py index 6d413a6c17..2b3f0f77dd 100644 --- a/heat/engine/security_group.py +++ b/heat/engine/security_group.py @@ -16,7 +16,6 @@ import eventlet import logging import os - from novaclient.exceptions import BadRequest from heat.common import exception from heat.engine.resources import Resource @@ -53,7 +52,6 @@ class SecurityGroup(Resource): self.description) self.instance_id_set(sec.id) - if 'SecurityGroupIngress' in self.t['Properties']: rules_client = self.nova().security_group_rules for i in self.t['Properties']['SecurityGroupIngress']: diff --git a/heat/engine/user.py b/heat/engine/user.py new file mode 100644 index 0000000000..41893b316f --- /dev/null +++ b/heat/engine/user.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import eventlet +import logging +import os +from novaclient.exceptions import BadRequest +from heat.common import exception +from heat.engine.resources import Resource + +logger = logging.getLogger(__file__) + + +class User(Resource): + def __init__(self, name, json_snippet, stack): + super(User, self).__init__(name, json_snippet, stack) + self.instance_id = '' + + def create(self): + self.state_set(self.CREATE_COMPLETE) + + def FnGetAtt(self, key): + res = None + if key == 'Policies': + res = self.t['Properties']['Policies'] + else: + raise exception.InvalidTemplateAttribute(resource=self.name, + key=key) + + logger.info('%s.GetAtt(%s) == %s' % (self.name, key, res)) + return unicode(res) + +class AccessKey(Resource): + def __init__(self, name, json_snippet, stack): + super(AccessKey, self).__init__(name, json_snippet, stack) + + def create(self): + self.state_set(self.CREATE_COMPLETE) + + def FnGetRefId(self): + return unicode(self.name) + + def FnGetAtt(self, key): + res = None + if key == 'UserName': + res = self.t['Properties']['UserName'] + if key == 'SecretAccessKey': + res = 'TODO-Add-Real-SecreateAccessKey' + else: + raise exception.InvalidTemplateAttribute(resource=self.name, + key=key) + + logger.info('%s.GetAtt(%s) == %s' % (self.name, key, res)) + return unicode(res) + diff --git a/templates/PuppetMaster_Single_Instance.template b/templates/PuppetMaster_Single_Instance.template new file mode 100644 index 0000000000..63e3352917 --- /dev/null +++ b/templates/PuppetMaster_Single_Instance.template @@ -0,0 +1,240 @@ +{ + "AWSTemplateFormatVersion" : "2010-09-09", + + "Description": "Sample template to bring up Puppet Master instance that can be used to bootstrap and manage Puppet Clients. The Puppet Master is populated from an embedded template that defines the set of applications to load. **WARNING** This template creates one or more Amazon EC2 instances. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters" : { + "InstanceType" : { + "Description" : "WebServer EC2 instance type", + "Type" : "String", + "Default" : "m1.large", + "AllowedValues" : [ "t1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"], + "ConstraintDescription" : "must be a valid EC2 instance type." + }, + "KeyName" : { + "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the PuppetMaster", + "Type" : "String" + }, + "ContentManifest" : { + "Default" : "/wordpress/: { include wordpress }", + "Description" : "Manifest of roles to add to nodes.pp", + "Type" : "String" + }, + "ContentLocation" : { + "Default" : "https://s3.amazonaws.com/cloudformation-examples/wordpress-puppet-config.tar.gz", + "Description" : "Location of package (Zip, GZIP or Git repository URL) that includes the PuppetMaster content", + "Type" : "String" + }, + "LinuxDistribution": { + "Default": "F16", + "Description" : "Distribution of choice", + "Type": "String", + "AllowedValues" : [ "F16", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3" ] + } + }, + "Mappings" : { + "AWSInstanceType2Arch" : { + "t1.micro" : { "Arch" : "32" }, + "m1.small" : { "Arch" : "32" }, + "m1.large" : { "Arch" : "64" }, + "m1.xlarge" : { "Arch" : "64" }, + "m2.xlarge" : { "Arch" : "64" }, + "m2.2xlarge" : { "Arch" : "64" }, + "m2.4xlarge" : { "Arch" : "64" }, + "c1.medium" : { "Arch" : "32" }, + "c1.xlarge" : { "Arch" : "64" }, + "cc1.4xlarge" : { "Arch" : "64" } + }, + "DistroArch2AMI": { + "F16" : { "32" : "F16-i386-cfntools", "64" : "F16-x86_64-cfntools" }, + "F17" : { "32" : "F17-i386-cfntools", "64" : "F17-x86_64-cfntools" }, + "U10" : { "32" : "U10-i386-cfntools", "64" : "U10-x86_64-cfntools" }, + "RHEL-6.1" : { "32" : "rhel61-i386-cfntools", "64" : "rhel61-x86_64-cfntools" }, + "RHEL-6.2" : { "32" : "rhel62-i386-cfntools", "64" : "rhel62-x86_64-cfntools" }, + "RHEL-6.3" : { "32" : "rhel63-i386-cfntools", "64" : "rhel63-x86_64-cfntools" } + } + }, + "Resources" : { + "CFNInitUser" : { + "Type" : "AWS::IAM::User", + "Properties" : { + "Policies": [{ + "PolicyName": "AccessForCFNInit", + "PolicyDocument" : { + "Statement": [{ + "Effect" : "Allow", + "Action" : "cloudformation:DescribeStackResource", + "Resource" : "*" + }] + } + }] + } + }, + + "CFNKeys" : { + "Type" : "AWS::IAM::AccessKey", + "Properties" : { + "UserName" : { "Ref": "CFNInitUser" } + } + }, + + "PuppetMasterInstance" : { + "Type" : "AWS::EC2::Instance", + "Metadata" : { + "AWS::CloudFormation::Init" : { + "config" : { + "packages" : { + "yum" : { + "puppet" : [], + "puppet-server" : [], + "ruby-devel" : [], + "gcc" : [], + "make" : [], + "rubygems" : [] + }, + "rubygems" : { + "json" : [] + } + }, + "sources" : { + "/etc/puppet" : { "Ref" : "ContentLocation" } + }, + "files" : { + "/etc/yum.repos.d/epel.repo" : { + "source" : "https://s3.amazonaws.com/cloudformation-examples/enable-epel-on-amazon-linux-ami", + "mode" : "000644", + "owner" : "root", + "group" : "root" + }, + "/etc/puppet/autosign.conf" : { + "content" : "*.internal\n", + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + }, + "/etc/puppet/fileserver.conf" : { + "content" : "[modules]\n allow *.internal\n", + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + }, + "/etc/puppet/puppet.conf" : { + "content" : { "Fn::Join" : ["", [ + "[main]\n", + " logdir=/var/log/puppet\n", + " rundir=/var/run/puppet\n", + " ssldir=$vardir/ssl\n", + " pluginsync=true\n", + "[agent]\n", + " classfile=$vardir/classes.txt\n", + " localconfig=$vardir/localconfig\n"]] }, + "mode" : "000644", + "owner" : "root", + "group" : "root" + }, + "/etc/puppet/modules/cfn/manifests/init.pp" : { + "content" : "class cfn {}", + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + }, + "/etc/puppet/modules/cfn/lib/facter/cfn.rb" : { + "source" : "https://s3.amazonaws.com/cloudformation-examples/cfn-facter-plugin.rb", + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + }, + "/etc/puppet/manifests/nodes.pp" : { + "content" : {"Fn::Join" : ["", [ + "node basenode {\n", + " include cfn\n", + "}\n", + "node /^.*internal$/ inherits basenode {\n", + " case $cfn_roles {\n", + " ", { "Ref" : "ContentManifest" }, "\n", + " }\n", + "}\n"]]}, + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + }, + "/etc/puppet/manifests/site.pp" : { + "content" : "import \"nodes\"\n", + "mode" : "100644", + "owner" : "root", + "group" : "wheel" + } + }, + "services" : { + "sysvinit" : { + "puppetmaster" : { + "enabled" : "true", + "ensureRunning" : "true" + } + } + } + } + } + }, + "Properties" : { + "InstanceType" : { "Ref" : "InstanceType" }, + "SecurityGroups" : [ { "Ref" : "PuppetGroup" } ], + "ImageId" : { "Fn::FindInMap" : [ "DistroArch2AMI", { "Ref" : "LinuxDistribution" }, + { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, + "KeyName" : { "Ref" : "KeyName" }, + "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ + "#!/bin/bash\n", + "yum update -y aws-cfn-bootstrap\n", + + "/opt/aws/bin/cfn-init --region ", { "Ref" : "AWS::Region" }, + " -s ", { "Ref" : "AWS::StackName" }, " -r PuppetMasterInstance ", + " --access-key ", { "Ref" : "CFNKeys" }, + " --secret-key ", { "Fn::GetAtt" : ["CFNKeys", "SecretAccessKey"]}, "\n", + "/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "PuppetMasterWaitHandle" }, "'\n"]]}} + } + }, + + "EC2SecurityGroup" : { + "Type" : "AWS::EC2::SecurityGroup", + "Properties" : { + "GroupDescription" : "Group for clients to communicate with Puppet Master" + } + }, + + "PuppetGroup" : { + "Type" : "AWS::EC2::SecurityGroup", + "Properties" : { + "GroupDescription" : "Group for puppet communication", + "SecurityGroupIngress" : [ + { "IpProtocol" : "tcp", "FromPort" : "8140", "ToPort" : "8140", "CidrIp": "0.0.0.0/0"}, + { "IpProtocol" : "tcp", "FromPort": "22", "ToPort": "22", "CidrIp": "0.0.0.0/0" } + ] + } + }, + + "PuppetMasterWaitHandle" : { + "Type" : "AWS::CloudFormation::WaitConditionHandle" + }, + + "PuppetMasterWaitCondition" : { + "Type" : "AWS::CloudFormation::WaitCondition", + "DependsOn" : "PuppetMasterInstance", + "Properties" : { + "Handle" : { "Ref" : "PuppetMasterWaitHandle" }, + "Timeout" : "600" + } + } + }, + + "Outputs" : { + "PuppetMasterDNSName" : { + "Value" : { "Fn::GetAtt" : [ "PuppetMasterInstance", "PrivateDnsName" ] }, + "Description" : "DNS Name of PuppetMaster" + }, + "PuppetClientSecurityGroup" : { + "Value" : { "Ref" : "EC2SecurityGroup" }, + "Description" : "Clients of the Puppet Master should be part of this security group" + } + } +} + diff --git a/templates/WordPress_Single_Instance_puppet.template b/templates/WordPress_Single_Instance_puppet.template new file mode 100644 index 0000000000..a326e5e827 --- /dev/null +++ b/templates/WordPress_Single_Instance_puppet.template @@ -0,0 +1,245 @@ +TemplateFormatVersion" : "2010-09-09", + + "Description": "Sample template to bring up WordPress using the Puppet client to install server roles. A WaitCondition is used to hold up the stack creation until the application is deployed. **WARNING** This template creates one or more Amazon EC2 instances and CloudWatch alarms. You will be billed for the AWS resources used if you create a stack from this template.", + + "Parameters" : { + "KeyName": { + "Type": "String", + "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the web server" + }, + "PuppetClientSecurityGroup": { + "Description" : "The EC2 security group for the instances", + "Type": "String" + }, + "PuppetMasterDNSName": { + "Description" : "The PuppetMaster DNS name", + "Type": "String" + }, + "InstanceType" : { + "Description" : "WebServer EC2 instance type", + "Type" : "String", + "Default" : "m1.small", + "AllowedValues" : [ "t1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"], + "ConstraintDescription" : "must be a valid EC2 instance type." + }, + "DatabaseType": { + "Default": "db.m1.small", + "Description" : "The database instance type", + "Type": "String", + "AllowedValues" : [ "db.m1.small", "db.m1.large", "db.m1.xlarge", "db.m2.xlarge", "db.m2.2xlarge", "db.m2.4xlarge" ], + "ConstraintDescription" : "must contain only alphanumeric characters." + }, + "DatabaseUser": { + "Default" : "admin", + "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." + }, + "DatabasePassword": { + "Default" : "admin", + "NoEcho": "true", + "Type": "String", + "Description" : "Test database admin account password", + "MinLength": "1", + "MaxLength": "41", + "AllowedPattern" : "[a-zA-Z0-9]*", + "ConstraintDescription" : "must contain only alphanumeric characters." + } + }, + + "Mappings" : { + "AWSInstanceType2Arch" : { + "t1.micro" : { "Arch" : "64" }, + "m1.small" : { "Arch" : "64" }, + "m1.medium" : { "Arch" : "64" }, + "m1.large" : { "Arch" : "64" }, + "m1.xlarge" : { "Arch" : "64" }, + "m2.xlarge" : { "Arch" : "64" }, + "m2.2xlarge" : { "Arch" : "64" }, + "m2.4xlarge" : { "Arch" : "64" }, + "c1.medium" : { "Arch" : "64" }, + "c1.xlarge" : { "Arch" : "64" }, + "cc1.4xlarge" : { "Arch" : "64HVM" }, + "cc2.8xlarge" : { "Arch" : "64HVM" }, + "cg1.4xlarge" : { "Arch" : "64HVM" } + }, + + "AWSRegionArch2AMI" : { + "us-east-1" : { "32" : "ami-31814f58", "64" : "ami-1b814f72", "64HVM" : "ami-0da96764" }, + "us-west-2" : { "32" : "ami-38fe7308", "64" : "ami-30fe7300", "64HVM" : "NOT_YET_SUPPORTED" }, + "us-west-1" : { "32" : "ami-11d68a54", "64" : "ami-1bd68a5e", "64HVM" : "NOT_YET_SUPPORTED" }, + "eu-west-1" : { "32" : "ami-973b06e3", "64" : "ami-953b06e1", "64HVM" : "NOT_YET_SUPPORTED" }, + "ap-southeast-1" : { "32" : "ami-b4b0cae6", "64" : "ami-beb0caec", "64HVM" : "NOT_YET_SUPPORTED" }, + "ap-northeast-1" : { "32" : "ami-0644f007", "64" : "ami-0a44f00b", "64HVM" : "NOT_YET_SUPPORTED" }, + "sa-east-1" : { "32" : "ami-3e3be423", "64" : "ami-3c3be421", "64HVM" : "NOT_YET_SUPPORTED" } + } + }, + + "Resources" : { + + "CFNInitUser" : { + "Type" : "AWS::IAM::User", + "Properties" : { + "Policies": [{ + "PolicyName": "AccessForCFNInit", + "PolicyDocument" : { + "Statement": [{ + "Effect" : "Allow", + "Action" : "cloudformation:DescribeStackResource", + "Resource" : "*" + }] + } + }] + } + }, + + "CFNKeys" : { + "Type" : "AWS::IAM::AccessKey", + "Properties" : { + "UserName" : { "Ref": "CFNInitUser" } + } + }, + + "WebServer": { + "Type": "AWS::EC2::Instance", + "Metadata" : { + "AWS::CloudFormation::Init" : { + "config" : { + "packages" : { + "yum" : { + "puppet" : [], + "ruby-devel" : [], + "gcc" : [], + "make" : [], + "rubygems" : [] + }, + "rubygems" : { + "json" : [] + } + }, + "files" : { + "/etc/yum.repos.d/epel.repo" : { + "source" : "https://s3.amazonaws.com/cloudformation-examples/enable-epel-on-amazon-linux-ami", + "mode" : "000644", + "owner" : "root", + "group" : "root" + }, + "/etc/puppet/puppet.conf" : { + "content" : { "Fn::Join" : ["", [ + "[main]\n", + " logdir=/var/log/puppet\n", + " rundir=/var/run/puppet\n", + " ssldir=$vardir/ssl\n", + " pluginsync=true\n", + "[agent]\n", + " classfile=$vardir/classes.txt\n", + " localconfig=$vardir/localconfig\n", + " server=",{ "Ref" : "PuppetMasterDNSName" },"\n" + ]] }, + "mode" : "000644", + "owner" : "root", + "group" : "root" + } + }, + "services" : { + "sysvinit" : { + "puppet" : { + "enabled" : "true", + "ensureRunning" : "true" + } + } + } + } + }, + "Puppet" : { + "roles" : [ "wordpress" ], + "host" : {"Fn::GetAtt" : ["WordPressDatabase", "Endpoint.Address"]}, + "database" : "WordPressDB", + "user" : {"Ref" : "DatabaseUser"}, + "password" : {"Ref" : "DatabasePassword" } + } + }, + "Properties": { + "SecurityGroups": [ { "Ref": "PuppetClientSecurityGroup" }, { "Ref" : "EC2SecurityGroup" } ], + "ImageId": { "Fn::FindInMap": [ "AWSRegionArch2AMI", { "Ref": "AWS::Region" }, { "Fn::FindInMap": [ "AWSInstanceType2Arch", { "Ref": "InstanceType" }, "Arch" ] } ] + }, + "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ + "#!/bin/bash\n", + "yum update -y aws-cfn-bootstrap\n", + + "/opt/aws/bin/cfn-init --region ", { "Ref" : "AWS::Region" }, + " -s ", { "Ref" : "AWS::StackName" }, " -r WebServer ", + " --access-key ", { "Ref" : "CFNKeys" }, + " --secret-key ", { "Fn::GetAtt" : ["CFNKeys", "SecretAccessKey"]}, "\n", + "/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "ApplicationWaitHandle" }, "'\n" + ]]}}, + "KeyName": { "Ref": "KeyName" }, + "InstanceType": { "Ref": "InstanceType" } + } + }, + + + "EC2SecurityGroup" : { + "Type" : "AWS::EC2::SecurityGroup", + "Properties" : { + "GroupDescription" : "Enable HTTP access for Wordpress plus SSH access via port 22", + "SecurityGroupIngress" : [ + {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0" }, + {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0" } + ] + } + }, + + "ApplicationWaitHandle" : { + "Type" : "AWS::CloudFormation::WaitConditionHandle" + }, + + "ApplicationWaitCondition" : { + "Type" : "AWS::CloudFormation::WaitCondition", + "DependsOn" : "WebServer", + "Properties" : { + "Handle" : { "Ref" : "ApplicationWaitHandle" }, + "Timeout" : "600" + } + }, + + "WordPressDatabase" : { + "Type" : "AWS::RDS::DBInstance", + "Properties" : { + "AllocatedStorage" : "5", + "DBName" : "WordPressDB", + "Engine" : "MySQL", + "DBInstanceClass" : { "Ref" : "DatabaseType" }, + "DBSecurityGroups" : [ { "Ref": "DBSecurityGroup" } ], + "MasterUsername" : { "Ref" : "DatabaseUser" }, + "MasterUserPassword" : { "Ref" : "DatabasePassword" } + } + }, + + "DBSecurityGroup": { + "Type": "AWS::RDS::DBSecurityGroup", + "Properties": { + "DBSecurityGroupIngress": { + "EC2SecurityGroupName": { "Ref": "EC2SecurityGroup" } + }, + "GroupDescription": "database access" + } + } + }, + + "Outputs": { + "WebsiteURL": { + "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "WebServer", "PublicDnsName" ] }, "/wordpress" ] ] }, + "Description" : "URL of the WordPress website" + }, + "InstallURL": { + "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "WebServer", "PublicDnsName" ] }, "/wordpress/wp-admin/install.php" ] ] }, + "Description" : "URL to install WordPress" + } + } +} +