├── .gitignore ├── Dockerfile ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── cloudformer.gemspec ├── docker-compose.yml ├── lib ├── cloudformer.rb └── cloudformer │ ├── stack.rb │ ├── tasks.rb │ └── version.rb ├── samples ├── Rakefile └── basic_template.json └── spec └── stack_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.1.2 2 | 3 | ADD . /usr/local/app 4 | WORKDIR /usr/local/app 5 | RUN bundle install 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Arvind Kunday 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudFormer 2 | 3 | Cloudformer attempts to simplify AWS Cloudformation stack creation process in ruby projects by providing reusable rake tasks to perform common operations such as apply(create/update), delete, recreate on stack along with validations on templates. Task executions which enforce a stack change will wait until ROLLBACK/COMPLETE or DELETE is signalled on the stack (useful in continuous deployment environments to wait until deployment is successful). Refer [examples section](#example) for more information. 4 | 5 | ### Note: 6 | This gem requires aws-sdk version 1.0 series, 2.0 series has some problems which is holding the upgrade to ruby 2.0. 7 | 8 | The list of rake tasks provided are: 9 | 10 | ``` 11 | 12 | rake apply # Apply Stack with Cloudformation script and parameters(And wait till complete - supports updates) 13 | rake delete # Delete stack from CloudFormation(And wait till stack is complete) 14 | rake events # Get the recent events from the stack 15 | rake outputs # Get the outputs of stack 16 | rake recreate # Recreate stack(runs delete & apply) 17 | rake status # Get the deployed app status 18 | 19 | ``` 20 | 21 | 22 | ## Installation 23 | 24 | Add this line to your application's Gemfile: 25 | 26 | gem 'cloudformer' 27 | 28 | And then execute: 29 | 30 | $ bundle 31 | 32 | Or install it yourself as: 33 | 34 | $ gem install cloudformer 35 | 36 | ## AWS Environment configuration 37 | 38 | Cloudformer depends on the aws-sdk gem to query AWS API. You will need to export AWS configuration to environment variables to your .bashrc/.bash_profile or your build server: 39 | 40 | export AWS_ACCESS_KEY=your access key 41 | export AWS_REGION=ap-southeast-2 42 | export AWS_SECRET_ACCESS_KEY=your secret access key 43 | 44 | 45 | ## Configuration 46 | 47 | You can add cloudformer tasks to your project by adding the following to your rake file: 48 | 49 | require 'cloudformer' 50 | Cloudformer::Tasks.new("earmark") do |t| 51 | t.template = "cloudformation/cloudformation.json" 52 | t.parameters = parameters 53 | end 54 | 55 | where `cloudformation/cloudformation.json` is the stack json file and parameters is a hash of parameters used in the template. 56 | For a template which takes the following parameters: 57 | 58 | "Parameters": { 59 | "PackageUrl": { 60 | "Type": "String" 61 | }, 62 | "PackageVersion": { 63 | "Type": "String" 64 | } 65 | } 66 | 67 | the parameter hash(Ruby Object) would look like: 68 | 69 | { 70 | "PackageUrl" => "http://localhost/app.rpm", 71 | "PackageVersion" => "123" 72 | } 73 | 74 | If you have a template with no parameters, pass an empty hash `{}` instead. 75 | 76 | ## Example 77 | 78 | Here is a simple Cloudformation Stack(Code available in the samples directory) with a Single EC2 Server: 79 | 80 | { 81 | "AWSTemplateFormatVersion": "2010-09-09", 82 | "Description": "Cloudformer - Demo App", 83 | "Parameters": { 84 | "AmiId": { 85 | "Type": "String" 86 | } 87 | }, 88 | "Resources": { 89 | "ApplicationServer": { 90 | "Type": "AWS::EC2::Instance", 91 | "Properties": { 92 | "ImageId": { 93 | "Ref": "AmiId" 94 | }, 95 | "InstanceType": "t1.micro", 96 | "Monitoring": "false" 97 | } 98 | } 99 | }, 100 | "Outputs": { 101 | "Server": { 102 | "Value": { 103 | "Fn::GetAtt": [ 104 | "ApplicationServer", 105 | "PrivateIp" 106 | ] 107 | } 108 | } 109 | } 110 | } 111 | 112 | Then, in your Rakefile add, 113 | 114 | require 'cloudformer/tasks' 115 | 116 | Cloudformer::Tasks.new("app") do |t| 117 | t.template = "basic_template.json" 118 | t.tags = [{'Key' => 'Name', 'Value' => 'BASIC-TMPLT'}, 119 | {'Key' => 'Owner', 'Value' => 'APPOWNER'}] 120 | 121 | #AMI Works in Sydney region only, ensure you supply the right AMI. 122 | t.parameters = {"AmiId" => "ami-8da439b7"} 123 | end 124 | 125 | You should then see following commands: 126 | 127 | rake apply # Apply Stack with Cloudformation script and parameters 128 | rake delete # Delete stack from CloudFormation 129 | rake events # Get the recent events from the stack 130 | rake outputs # Get the outputs of stack 131 | rake recreate # Recreate stack 132 | rake status # Get the deployed app status 133 | 134 | Running `rake status` gives us: 135 | 136 | =============================================== 137 | app - Not Deployed 138 | ================================================ 139 | 140 | Running `rake apply` will create an environment or update existing depending on the nature of action requested in parameters: 141 | 142 | Initializing stack creation... 143 | ================================================================================================== 144 | 2013-10-24 07:55:24 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated 145 | 2013-10-24 07:55:36 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - 146 | 2013-10-24 07:55:37 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated 147 | 2013-10-24 07:56:25 UTC - AWS::EC2::Instance - CREATE_COMPLETE - 148 | 2013-10-24 07:56:26 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE - 149 | ================================================================================================== 150 | 151 | Running `rake apply` again gives us: 152 | 153 | No updates are to be performed. 154 | 155 | To remove the stack `rake delete` gives us: 156 | 157 | ============================================================================================== 158 | Attempting to delete stack - app 159 | ============================================================================================== 160 | 2013-10-24 07:55:24 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated 161 | 2013-10-24 07:55:36 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - 162 | 2013-10-24 07:55:37 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated 163 | 2013-10-24 07:56:25 UTC - AWS::EC2::Instance - CREATE_COMPLETE - 164 | 2013-10-24 07:56:26 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE - 165 | 2013-10-24 07:58:54 UTC - AWS::CloudFormation::Stack - DELETE_IN_PROGRESS - User Initiated 166 | 2013-10-24 07:58:56 UTC - AWS::EC2::Instance - DELETE_IN_PROGRESS - 167 | 168 | Attempts to delete a non-existing stack will result in: 169 | 170 | ============================================== 171 | Attempting to delete stack - app 172 | ============================================== 173 | Stack not up. 174 | ============================================== 175 | 176 | To recreate the stack use `rake recreate`: 177 | 178 | ================================================================================================= 179 | Attempting to delete stack - app 180 | ================================================================================================= 181 | 2013-10-24 08:04:11 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated 182 | 2013-10-24 08:04:22 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - 183 | 2013-10-24 08:04:23 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated 184 | 2013-10-24 08:05:12 UTC - AWS::EC2::Instance - CREATE_COMPLETE - 185 | 2013-10-24 08:05:13 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE - 186 | 2013-10-24 08:05:52 UTC - AWS::CloudFormation::Stack - DELETE_IN_PROGRESS - User Initiated 187 | 2013-10-24 08:06:02 UTC - AWS::EC2::Instance - DELETE_IN_PROGRESS - 188 | Stack deleted successfully. 189 | Initializing stack creation... 190 | ================================================================================================= 191 | 2013-10-24 08:06:31 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated 192 | 2013-10-24 08:06:52 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - 193 | 2013-10-24 08:06:54 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated 194 | 2013-10-24 08:07:41 UTC - AWS::EC2::Instance - CREATE_COMPLETE - 195 | ================================================================================================= 196 | ================================================================================================= 197 | Server - - 172.31.3.52 198 | ================================================================================================= 199 | 200 | To see the stack outputs `rake outputs`: 201 | 202 | ============================== 203 | Server - - 172.31.3.52 204 | ============================== 205 | 206 | To see recent events on the stack `rake events`: 207 | 208 | ================================================================================================== 209 | 2013-10-24 08:06:31 UTC - AWS::CloudFormation::Stack - CREATE_IN_PROGRESS - User Initiated 210 | 2013-10-24 08:06:52 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - 211 | 2013-10-24 08:06:54 UTC - AWS::EC2::Instance - CREATE_IN_PROGRESS - Resource creation Initiated 212 | 2013-10-24 08:07:41 UTC - AWS::EC2::Instance - CREATE_COMPLETE - 213 | 2013-10-24 08:07:43 UTC - AWS::CloudFormation::Stack - CREATE_COMPLETE - 214 | ================================================================================================== 215 | 216 | ## Contributing 217 | 218 | 1. Fork it 219 | 2. Create your feature branch (`git checkout -b my-new-feature`) 220 | 3. Commit your changes (`git commit -am 'Add some feature'`) 221 | 4. Push to the branch (`git push origin my-new-feature`) 222 | 5. Create new Pull Request 223 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec' 3 | 4 | begin 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) 8 | 9 | task :default => :spec 10 | rescue LoadError 11 | # no rspec available 12 | end 13 | -------------------------------------------------------------------------------- /cloudformer.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'cloudformer/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "cloudformer" 8 | spec.version = Cloudformer::VERSION 9 | spec.authors = ["Arvind Kunday"] 10 | spec.email = ["hi@kunday.com"] 11 | spec.description = %q{Cloudformation tasks for apply(create/update), delete, recreate on stack along with validations on templates} 12 | spec.summary = %q{Cloudformer attempts to simplify AWS Cloudformation stack creation process in ruby projects by providing reusable rake tasks to perform common operations such as apply(create/update), delete, recreate on stack along with validations on templates.} 13 | spec.homepage = "https://github.com/kunday/cloudformer" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.3" 22 | spec.add_development_dependency "rspec" 23 | spec.add_dependency "rake" 24 | spec.add_dependency "aws-sdk", '~> 1.0' 25 | spec.add_dependency "httparty" 26 | end 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | app: 2 | build: . 3 | -------------------------------------------------------------------------------- /lib/cloudformer.rb: -------------------------------------------------------------------------------- 1 | require "cloudformer/version" 2 | require "cloudformer/stack" 3 | require "cloudformer/tasks" 4 | 5 | module Cloudformer 6 | end 7 | -------------------------------------------------------------------------------- /lib/cloudformer/stack.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk' 2 | require 'httparty' 3 | 4 | class Stack 5 | attr_accessor :stack, :name, :deployed 6 | 7 | SUCESS_STATES = ["CREATE_COMPLETE", "UPDATE_COMPLETE"] 8 | FAILURE_STATES = ["CREATE_FAILED", "DELETE_FAILED", "UPDATE_ROLLBACK_FAILED", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE","ROLLBACK_FAILED","UPDATE_ROLLBACK_COMPLETE","UPDATE_ROLLBACK_FAILED"] 9 | END_STATES = SUCESS_STATES + FAILURE_STATES 10 | 11 | # WAITING_STATES = ["CREATE_IN_PROGRESS","DELETE_IN_PROGRESS","ROLLBACK_IN_PROGRESS","UPDATE_COMPLETE_CLEANUP_IN_PROGRESS","UPDATE_IN_PROGRESS","UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS","UPDATE_ROLLBACK_IN_PROGRESS"] 12 | def initialize(stack_name) 13 | @name = stack_name 14 | @cf = AWS::CloudFormation.new 15 | @stack = @cf.stacks[name] 16 | @ec2 = AWS::EC2.new 17 | end 18 | 19 | def deployed 20 | return stack.exists? 21 | end 22 | 23 | def apply(template_file, parameters, disable_rollback=false, capabilities=[], notify=[], tags=[]) 24 | if ( template_file =~ /^https:\/\/s3\S+\.amazonaws\.com\/(.*)/ ) 25 | template = template_file 26 | elsif ( template_file =~ /^http.*(.json)$/ ) 27 | begin 28 | response = HTTParty.get(template_file) 29 | template = response.body 30 | rescue => e 31 | puts "Unable to retieve json file for template from #{template_file} - #{e.class}, #{e}" 32 | return :Failed 33 | end 34 | else 35 | template = File.read(template_file) 36 | end 37 | validation = validate(template) 38 | unless validation["valid"] 39 | puts "Unable to update - #{validation["response"][:code]} - #{validation["response"][:message]}" 40 | return :Failed 41 | end 42 | pending_operations = false 43 | begin 44 | if deployed 45 | pending_operations = update(template, parameters, capabilities) 46 | else 47 | pending_operations = create(template, parameters, disable_rollback, capabilities, notify, tags) 48 | end 49 | rescue ::AWS::CloudFormation::Errors::ValidationError => e 50 | puts e.message 51 | return (if e.message == "No updates are to be performed." then :NoUpdates else :Failed end) 52 | end 53 | wait_until_end if pending_operations 54 | return (if deploy_succeded? then :Succeeded else :Failed end) 55 | end 56 | 57 | def deploy_succeded? 58 | return true unless FAILURE_STATES.include?(stack.status) 59 | puts "Unable to deploy template. Check log for more information." 60 | false 61 | end 62 | 63 | def stop_instances 64 | update_instances("stop") 65 | end 66 | 67 | def start_instances 68 | update_instances("start") 69 | end 70 | 71 | def delete 72 | with_highlight do 73 | puts "Attempting to delete stack - #{name}" 74 | stack.delete 75 | wait_until_end 76 | return deploy_succeded? 77 | end 78 | end 79 | 80 | def status 81 | with_highlight do 82 | if deployed 83 | puts "#{stack.name} - #{stack.status} - #{stack.status_reason}" 84 | else 85 | puts "#{name} - Not Deployed" 86 | end 87 | end 88 | end 89 | 90 | def events(options = {}) 91 | with_highlight do 92 | if !deployed 93 | puts "Stack not up." 94 | return 95 | end 96 | stack.events.sort_by {|a| a.timestamp}.each do |event| 97 | puts "#{event.timestamp} - #{event.physical_resource_id.to_s} - #{event.logical_resource_id} - #{event.resource_type} - #{event.resource_status} - #{event.resource_status_reason.to_s}" 98 | end 99 | end 100 | end 101 | 102 | def outputs 103 | with_highlight do 104 | if !deployed 105 | puts "Stack not up." 106 | return 1 107 | end 108 | stack.outputs.each do |output| 109 | puts "#{output.key} - #{output.description} - #{output.value}" 110 | end 111 | end 112 | return 0 113 | end 114 | 115 | def validate(template) 116 | response = @cf.validate_template(template) 117 | return { 118 | "valid" => response[:code].nil?, 119 | "response" => response 120 | } 121 | end 122 | 123 | private 124 | def wait_until_end 125 | printed = [] 126 | current_time = Time.now 127 | with_highlight do 128 | if !deployed 129 | puts "Stack not up." 130 | return 131 | end 132 | loop do 133 | printable_events = stack.events.reject{|a| (a.timestamp < current_time)}.sort_by {|a| a.timestamp}.reject {|a| a if printed.include?(a.event_id)} 134 | printable_events.each { |event| puts "#{event.timestamp} - #{event.physical_resource_id.to_s} - #{event.resource_type} - #{event.resource_status} - #{event.resource_status_reason.to_s}" } 135 | printed.concat(printable_events.map(&:event_id)) 136 | break if END_STATES.include?(stack.status) 137 | sleep(30) 138 | end 139 | end 140 | end 141 | 142 | def with_highlight &block 143 | cols = `tput cols`.chomp!.to_i 144 | puts "="*cols 145 | yield 146 | puts "="*cols 147 | end 148 | 149 | def update(template, parameters, capabilities) 150 | stack.update({ 151 | :template => template, 152 | :parameters => parameters, 153 | :capabilities => capabilities 154 | }) 155 | return true 156 | end 157 | 158 | def create(template, parameters, disable_rollback, capabilities, notify, tags) 159 | puts "Initializing stack creation..." 160 | @cf.stacks.create(name, template, :parameters => parameters, :disable_rollback => disable_rollback, :capabilities => capabilities, :notify => notify, :tags => tags) 161 | sleep 10 162 | return true 163 | end 164 | 165 | def update_instances(action) 166 | with_highlight do 167 | puts "Attempting to #{action} all ec2 instances in the stack #{stack.name}" 168 | return "Stack not up" if !deployed 169 | stack.resources.each do |resource| 170 | begin 171 | next if resource.resource_type != "AWS::EC2::Instance" 172 | physical_resource_id = resource.physical_resource_id 173 | puts "Attempting to #{action} Instance with physical_resource_id: #{physical_resource_id}" 174 | @ec2.instances[physical_resource_id].send(action) 175 | rescue 176 | puts "Some resources are not up." 177 | end 178 | end 179 | end 180 | end 181 | end 182 | 183 | -------------------------------------------------------------------------------- /lib/cloudformer/tasks.rb: -------------------------------------------------------------------------------- 1 | require 'cloudformer/version' 2 | require 'cloudformer/stack' 3 | require 'rake/tasklib' 4 | 5 | module Cloudformer 6 | class Tasks < Rake::TaskLib 7 | def initialize(stack_name) 8 | @stack_name = stack_name 9 | @stack =Stack.new(stack_name) 10 | if block_given? 11 | yield self 12 | define_tasks 13 | end 14 | end 15 | 16 | attr_accessor :template, :parameters, :disable_rollback, :retry_delete, :capabilities, :notify, :tags 17 | 18 | private 19 | def define_tasks 20 | define_create_task 21 | define_validate_task 22 | define_delete_task 23 | define_delete_with_stop_task 24 | define_events_task 25 | define_status_task 26 | define_outputs_task 27 | define_recreate_task 28 | define_stop_task 29 | define_start_task 30 | end 31 | 32 | def define_create_task 33 | desc "Apply Stack with Cloudformation script and parameters." 34 | task :apply do 35 | if retry_delete 36 | @stack.delete 37 | end 38 | result = @stack.apply(template, parameters, disable_rollback, capabilities, notify, tags) 39 | if result == :Failed then exit 1 end 40 | if result == :NoUpdates then exit 0 end 41 | end 42 | end 43 | 44 | def define_delete_task 45 | desc "Delete stack from CloudFormation" 46 | task :delete do 47 | begin 48 | exit 1 unless @stack.delete 49 | rescue 50 | puts "Stack deleted successfully." 51 | end 52 | end 53 | end 54 | 55 | def define_delete_with_stop_task 56 | desc "Delete stack after stopping all instances" 57 | task :force_delete do 58 | begin 59 | @stack.stop_instances 60 | exit 1 unless @stack.delete 61 | rescue => e 62 | puts "Stack delete message - #{e.message}" 63 | end 64 | end 65 | end 66 | 67 | def define_status_task 68 | desc "Get the deployed app status." 69 | task :status do 70 | @stack.status 71 | end 72 | end 73 | 74 | def define_events_task 75 | desc "Get the recent events from the stack." 76 | task :events do 77 | @stack.events 78 | end 79 | end 80 | 81 | def define_outputs_task 82 | desc "Get the outputs of stack." 83 | task :outputs do 84 | @stack.outputs 85 | end 86 | end 87 | def define_recreate_task 88 | desc "Recreate stack." 89 | task :recreate => [:delete, :apply, :outputs] 90 | end 91 | 92 | def define_stop_task 93 | desc "Stop EC2 instances in stack." 94 | task :stop do 95 | @stack.stop_instances 96 | end 97 | end 98 | 99 | def define_start_task 100 | desc "Start EC2 instances in stack." 101 | task :start do 102 | @stack.start_instances 103 | end 104 | end 105 | 106 | def define_validate_task 107 | desc "Validate the Stack." 108 | task :validate do 109 | @stack.validate(template) 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/cloudformer/version.rb: -------------------------------------------------------------------------------- 1 | module Cloudformer 2 | VERSION = "0.0.18" 3 | end 4 | -------------------------------------------------------------------------------- /samples/Rakefile: -------------------------------------------------------------------------------- 1 | require 'cloudformer' 2 | 3 | Cloudformer::Tasks.new("arvind-test") do |t| 4 | t.template = "basic_template.json" 5 | #AMI Works in Sydney region only, ensure you supply the right AMI. 6 | t.parameters = {"AmiId" => "ami-8da439b7"} 7 | t.disable_rollback = true 8 | t.capabilities=[] 9 | end -------------------------------------------------------------------------------- /samples/basic_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Cloudformer - Demo App", 4 | "Parameters": { 5 | "AmiId": { 6 | "Type": "String" 7 | } 8 | }, 9 | "Resources": { 10 | "ApplicationServer": { 11 | "Type": "AWS::EC2::Instance", 12 | "Properties": { 13 | "ImageId": { 14 | "Ref": "AmiId" 15 | }, 16 | "InstanceType": "t1.micro", 17 | "Monitoring": "false" 18 | } 19 | } 20 | }, 21 | "Outputs": { 22 | "Server": { 23 | "Value": { 24 | "Fn::GetAtt": [ 25 | "ApplicationServer", 26 | "PrivateIp" 27 | ] 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /spec/stack_spec.rb: -------------------------------------------------------------------------------- 1 | require 'cloudformer/stack' 2 | 3 | describe Stack do 4 | before :each do 5 | @cf = double(AWS::CloudFormation) 6 | @cf_stack = double(AWS::CloudFormation::Stack) 7 | @collection = double(AWS::CloudFormation::StackCollection) 8 | AWS::CloudFormation.should_receive(:new).and_return(@cf) 9 | @collection.should_receive(:[]).and_return(@cf_stack) 10 | @cf.should_receive(:stacks).and_return(@collection) 11 | end 12 | describe "when deployed" do 13 | before :each do 14 | @stack = Stack.new("stack") 15 | end 16 | 17 | it "should report as the stack being deployed" do 18 | @cf_stack.should_receive(:exists?).and_return(true) 19 | @stack.deployed.should be 20 | end 21 | 22 | describe "#delete" do 23 | it "should return a true if delete fails" do 24 | pending 25 | @cf_stack.should_receive(:exists?).and_return(false) 26 | @cf_stack.should_receive(:status) 27 | @stack.delete.should be 28 | end 29 | end 30 | end 31 | 32 | describe "when stack is not deployed" do 33 | before :each do 34 | @stack = Stack.new("stack") 35 | end 36 | 37 | it "should report as the stack not being deployed" do 38 | @cf_stack.should_receive(:exists?).and_return(false) 39 | @stack.deployed.should_not be 40 | end 41 | end 42 | 43 | describe "when stack operation throws ValidationError" do 44 | before :each do 45 | @stack = Stack.new("stack") 46 | @cf_stack.should_receive(:exists?).and_return(true) 47 | File.should_receive(:read).and_return("template") 48 | @cf.should_receive(:validate_template).and_return({"valid" => true}) 49 | @cf_stack.should_receive(:update).and_raise(AWS::CloudFormation::Errors::ValidationError) 50 | end 51 | 52 | it "apply should return Failed to signal the error" do 53 | @stack.apply(nil, nil).should be(:Failed) 54 | end 55 | end 56 | 57 | describe "when stack operation throws ValidationError because no updates are to be performed" do 58 | before :each do 59 | @stack = Stack.new("stack") 60 | @cf_stack.should_receive(:exists?).and_return(true) 61 | File.should_receive(:read).and_return("template") 62 | @cf.should_receive(:validate_template).and_return({"valid" => true}) 63 | @cf_stack.should_receive(:update).and_raise(AWS::CloudFormation::Errors::ValidationError.new("No updates are to be performed.")) 64 | end 65 | 66 | it "apply should return NoUpdate to signal the error" do 67 | @stack.apply(nil, nil).should be(:NoUpdates) 68 | end 69 | end 70 | 71 | describe "when stack update succeeds" do 72 | before :each do 73 | @stack = Stack.new("stack") 74 | @cf_stack.should_receive(:exists?).at_least(:once).and_return(true) 75 | File.should_receive(:read).and_return("template") 76 | @cf.should_receive(:validate_template).and_return({"valid" => true}) 77 | @cf_stack.should_receive(:update).and_return(false) 78 | @cf_stack.should_receive(:events).and_return([]) 79 | @cf_stack.should_receive(:status).at_least(:once).and_return("UPDATE_COMPLETE") 80 | end 81 | 82 | it "apply should return Succeeded" do 83 | @stack.apply(nil, nil).should be(:Succeeded) 84 | end 85 | end 86 | end 87 | --------------------------------------------------------------------------------