diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..959dbc6 --- /dev/null +++ b/.rspec @@ -0,0 +1,4 @@ +--color +--require spec_helper +--format d +--order defined diff --git a/Rakefile b/Rakefile index a9efa1c..cb86143 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' namespace :spec do - RSPEC_OPTS = ["--format", "nested", "--format", "html", "--out", "spec_result.html", "--colour"] + RSPEC_OPTS = ["--format", "documentation", "--format", "html", "--out", "spec_result.html", "--colour"] RSpec::Core::RakeTask.new(:file) do |t| t.rspec_opts = RSPEC_OPTS diff --git a/cf_deployer.gemspec b/cf_deployer.gemspec index 056d142..ab73674 100644 --- a/cf_deployer.gemspec +++ b/cf_deployer.gemspec @@ -8,18 +8,25 @@ Gem::Specification.new do |gem| gem.summary = %q{Support multiple components deployment using CloudFormation templates with multiple blue green strategies.} gem.homepage = "http://github.com/manheim/cf_deployer" gem.license = 'MIT' + gem.required_ruby_version = '>= 3.2.8', '< 3.3' - gem.add_runtime_dependency 'aws-sdk','1.44.0' + gem.add_runtime_dependency 'aws-sdk-autoscaling', '~> 1.86' + gem.add_runtime_dependency 'aws-sdk-cloudformation', '~> 1.76' + gem.add_runtime_dependency 'aws-sdk-core', '~> 3.170' + gem.add_runtime_dependency 'aws-sdk-ec2', '~> 1.365' + gem.add_runtime_dependency 'aws-sdk-elasticloadbalancing', '~> 1.42' + gem.add_runtime_dependency 'aws-sdk-route53', '~> 1.71' + gem.add_runtime_dependency 'diffy' + gem.add_runtime_dependency 'json', '~> 2.5' gem.add_runtime_dependency 'log4r' - gem.add_runtime_dependency 'thor' gem.add_runtime_dependency 'rainbow' - gem.add_runtime_dependency 'diffy' - gem.add_development_dependency 'yard', '~> 0.8.7.6' - gem.add_development_dependency 'pry', '~> 0.10.1' - gem.add_development_dependency 'rspec', '2.14.1' - gem.add_development_dependency 'rake', '~> 10.3.0' - gem.add_development_dependency 'webmock', '~> 2.1.0' - gem.add_development_dependency 'vcr', '~> 2.9.3' + gem.add_runtime_dependency 'thor' + gem.add_development_dependency 'pry', '~> 0.13' + gem.add_development_dependency 'rake', '~> 13.0' + gem.add_development_dependency 'rspec', '3.10' + gem.add_development_dependency 'vcr', '~> 6.0' + gem.add_development_dependency 'webmock', '~> 3.11' + gem.add_development_dependency 'yard', '~> 0.9' gem.files = `git ls-files`.split($\).reject {|f| f =~ /^samples\// } gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } diff --git a/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml b/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml index dccd0cd..4dd2765 100644 --- a/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml +++ b/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml @@ -45,7 +45,7 @@ http_interactions: \ \n \n \n \ \n 12345\n \ \n\n" - http_version: + http_version: recorded_at: Tue, 27 Sep 2016 05:47:40 GMT - request: method: post @@ -65,7 +65,7 @@ http_interactions: \ \n \n \n \ \n 12346\n \ \n\n" - http_version: + http_version: recorded_at: Tue, 27 Sep 2016 05:47:40 GMT - request: method: post diff --git a/lib/cf_deployer.rb b/lib/cf_deployer.rb index 80f9580..f766032 100644 --- a/lib/cf_deployer.rb +++ b/lib/cf_deployer.rb @@ -2,8 +2,14 @@ require 'set' require 'time' require 'json' +require 'yaml' require 'timeout' -require 'aws-sdk' +require 'aws-sdk-autoscaling' +require 'aws-sdk-ec2' +require 'aws-sdk-core' +require 'aws-sdk-cloudformation' +require 'aws-sdk-elasticloadbalancing' +require 'aws-sdk-route53' require 'erb' require 'fileutils' require 'log4r' @@ -38,7 +44,8 @@ module CfDeployer - AWS.config(:max_retries => 20) + aws_config = Seahorse::Client::Configuration.new + aws_config.add_option(:max_retries, 20) def self.config opts config = self.parseconfig opts, false @@ -50,19 +57,16 @@ def self.config opts def self.deploy opts config = self.parseconfig opts - # AWS.config(:logger => Logger.new($stdout)) Application.new(config).deploy end def self.runhook opts config = self.parseconfig opts - # AWS.config(:logger => Logger.new($stdout)) Application.new(config).run_hook opts[:component].first, opts[:hook_name] end def self.destroy opts config = self.parseconfig opts, false - # AWS.config(:logger => Logger.new($stdout)) Application.new(config).destroy end @@ -97,7 +101,7 @@ def self.kill_inactive opts private def self.parseconfig options, validate_inputs = true - AWS.config(:region => options[:region]) if options[:region] + Aws.config.update({region: options[:region]}) if options[:region] options[:cli_overrides] = {:settings => options.delete(:settings), :inputs => options.delete(:inputs)} config = ConfigLoader.new.load options ConfigValidation.new.validate config, validate_inputs diff --git a/lib/cf_deployer/cli.rb b/lib/cf_deployer/cli.rb index 3568d1a..edf0606 100644 --- a/lib/cf_deployer/cli.rb +++ b/lib/cf_deployer/cli.rb @@ -94,7 +94,7 @@ def merged_options def set_log_level if options[:'log-level'] == 'aws-debug' CfDeployer::Log.level = 'debug' - AWS.config :logger => Logger.new($stdout) + Aws.config.update(:logger => Logger.new($stdout)) else CfDeployer::Log.level = options[:'log-level'] end diff --git a/lib/cf_deployer/config_loader.rb b/lib/cf_deployer/config_loader.rb index 1684d71..db2faac 100644 --- a/lib/cf_deployer/config_loader.rb +++ b/lib/cf_deployer/config_loader.rb @@ -3,7 +3,7 @@ class ConfigLoader def self.erb_to_json filename, config json_file = File.join(config[:config_dir], "#{filename}.json") - raise ApplicationError.new("#{json_file} is missing") unless File.exists?(json_file) + raise ApplicationError.new("#{json_file} is missing") unless File.exist?(json_file) CfDeployer::Log.info "ERBing JSON for #{filename}" ERB.new(File.read(json_file)).result(binding) rescue RuntimeError,TypeError,NoMethodError => e @@ -39,7 +39,7 @@ def load(options) def load_yaml(text) YAML.load text - rescue Psych::SyntaxError => e + rescue ::Psych::SyntaxError => e error_document text raise e rescue diff --git a/lib/cf_deployer/config_validation.rb b/lib/cf_deployer/config_validation.rb index afdca72..fddbc3d 100644 --- a/lib/cf_deployer/config_validation.rb +++ b/lib/cf_deployer/config_validation.rb @@ -79,7 +79,7 @@ def check_hook_file(component, hook_name) file_name = component[hook_name][:file] return unless file_name path = File.join(component[:config_dir], file_name) - @errors << "File '#{path}' does not exist, which is required by hook '#{hook_name}'" unless File.exists?(path) + @errors << "File '#{path}' does not exist, which is required by hook '#{hook_name}'" unless File.exist?(path) end def check_environments diff --git a/lib/cf_deployer/driver/auto_scaling_group.rb b/lib/cf_deployer/driver/auto_scaling_group.rb index bca8b26..9c8c2e2 100644 --- a/lib/cf_deployer/driver/auto_scaling_group.rb +++ b/lib/cf_deployer/driver/auto_scaling_group.rb @@ -3,7 +3,7 @@ module Driver class AutoScalingGroup extend Forwardable - def_delegators :aws_group, :auto_scaling_instances, :ec2_instances, :load_balancers, :desired_capacity + def_delegators :aws_group, :instances, :desired_capacity, :load_balancer_names attr_reader :group_name, :group @@ -22,7 +22,7 @@ def warm_up desired Log.info "warming up auto scaling group #{group_name} to #{desired}" CfDeployer::Driver::DryRun.guard "Skipping ASG warmup" do - aws_group.set_desired_capacity desired + aws_group.set_desired_capacity({desired_capacity: desired}) wait_for_desired_capacity end end @@ -38,14 +38,14 @@ def cool_down Log.info "Cooling down #{group_name}" CfDeployer::Driver::DryRun.guard "Skipping ASG cooldown" do aws_group.update :min_size => 0, :max_size => 0 - aws_group.set_desired_capacity 0 + aws_group.set_desired_capacity({desired_capacity: 0}) end end def instance_statuses instance_info = {} - ec2_instances.each do |instance| - instance_info[instance.id] = CfDeployer::Driver::Instance.new(instance).status + instances.each do |instance| + instance_info[instance.id] = CfDeployer::Driver::Instance.new(instance.id).status end instance_info end @@ -63,34 +63,25 @@ def desired_capacity_reached? end def healthy_instance_ids - instances = auto_scaling_instances.select do |instance| + _instances = instances.select do |instance| 'HEALTHY'.casecmp(instance.health_status) == 0 end - instances.map(&:id) + _instances.map(&:id) end def in_service_instance_ids - elbs = load_balancers - return [] if elbs.empty? - - ids = elbs.collect(&:instances) - .collect(&:health) - .to_a - .collect { |elb_healths| - elb_healths.select { |health| health[:state] == 'InService' } - .map { |health| health[:instance].id } - } - - ids.inject(:&) + elb_names = load_balancer_names + + return [] if elb_names.empty? + + elb_driver.in_service_instance_ids elb_names end def healthy_instance_count - AWS.memoize do - instances = healthy_instance_ids - instances &= in_service_instance_ids unless load_balancers.empty? - Log.info "Healthy instance count: #{instances.count}" - instances.count - end + instances = healthy_instance_ids + instances &= in_service_instance_ids unless load_balancer_names.empty? + Log.info "Healthy instance count: #{instances.count}" + instances.count rescue => e Log.error "Unable to determine healthy instance count due to error: #{e.message}" -1 @@ -99,7 +90,11 @@ def healthy_instance_count private def aws_group - @my_group ||= AWS::AutoScaling.new.groups[group_name] + @my_group ||= Aws::AutoScaling::AutoScalingGroup.new(group_name) + end + + def elb_driver + @elb_driver ||= Elb.new end end end diff --git a/lib/cf_deployer/driver/cloud_formation_driver.rb b/lib/cf_deployer/driver/cloud_formation_driver.rb index 7857d08..c46c756 100644 --- a/lib/cf_deployer/driver/cloud_formation_driver.rb +++ b/lib/cf_deployer/driver/cloud_formation_driver.rb @@ -7,40 +7,40 @@ def initialize stack_name end def stack_exists? - aws_stack.exists? + !aws_stack.nil? end def create_stack template, opts CfDeployer::Driver::DryRun.guard "Skipping create_stack" do - cloud_formation.stacks.create @stack_name, template, opts + cloud_formation.create_stack(opts.merge(stack_name: @stack_name, template_body: template)) end end def update_stack template, opts begin CfDeployer::Driver::DryRun.guard "Skipping update_stack" do - aws_stack.update opts.merge(:template => template) + cloud_formation.update_stack(opts.merge(stack_name: @stack_name, template_body: template)) end - rescue AWS::CloudFormation::Errors::ValidationError => e - if e.message =~ /No updates are to be performed/ - Log.info e.message - return false - else - raise - end + rescue Aws::CloudFormation::Errors::ValidationError => e + Log.info e.message + return false + rescue => e + puts '*' * 80 + puts e + raise end return !CfDeployer::Driver::DryRun.enabled? end def stack_status - aws_stack.status.downcase.to_sym + aws_stack&.stack_status&.downcase&.to_sym end def outputs aws_stack.outputs.inject({}) do |memo, o| - memo[o.key] = o.value + memo[o.output_key] = o.output_value memo end end @@ -50,14 +50,14 @@ def parameters end def query_output key - output = aws_stack.outputs.find { |o| o.key == key } - output && output.value + output = aws_stack.outputs.find { |o| o.output_key == key } + output && output.output_value end def delete_stack if stack_exists? CfDeployer::Driver::DryRun.guard "Skipping create_stack" do - aws_stack.delete + cloud_formation.delete_stack(stack_name: @stack_name) end else Log.info "Stack #{@stack_name} does not exist!" @@ -66,7 +66,7 @@ def delete_stack def resource_statuses resources = {} - aws_stack.resource_summaries.each do |rs| + cloud_formation.list_stack_resources(stack_name: @stack_name).stack_resource_summaries.each do |rs| resources[rs[:resource_type]] ||= {} resources[rs[:resource_type]][rs[:physical_resource_id]] = rs[:resource_status] end @@ -80,14 +80,17 @@ def template private def cloud_formation - AWS::CloudFormation.new + Aws::CloudFormation::Client.new end def aws_stack - cloud_formation.stacks[@stack_name] + begin + cloud_formation.describe_stacks({stack_name: @stack_name}).stacks.first + rescue Aws::CloudFormation::Errors::ValidationError => e + return nil + end end end - end end diff --git a/lib/cf_deployer/driver/elb_driver.rb b/lib/cf_deployer/driver/elb_driver.rb index 971de89..c958f2a 100644 --- a/lib/cf_deployer/driver/elb_driver.rb +++ b/lib/cf_deployer/driver/elb_driver.rb @@ -2,16 +2,23 @@ module CfDeployer module Driver class Elb def find_dns_and_zone_id elb_id - elb = elb_driver.load_balancers[elb_id] - { :canonical_hosted_zone_name_id => elb.canonical_hosted_zone_name_id, :dns_name => elb.dns_name } + elb = elb_driver.describe_load_balancers({:load_balancer_names => [elb_id]})&.load_balancer_descriptions&.first + { :canonical_hosted_zone_name_id => elb&.canonical_hosted_zone_name_id, :dns_name => elb&.dns_name } + end + + def in_service_instance_ids elb_ids + elb_ids.collect do |elb_id| + elb_driver.describe_instance_health({:load_balancer_name => elb_id}).instance_states + .collect{|instance| instance.state == 'InService' ? instance.instance_id : nil }.compact + end.inject(:&) end private def elb_driver - AWS::ELB.new + @elb_driver ||= Aws::ElasticLoadBalancing::Client.new end end end -end \ No newline at end of file +end diff --git a/lib/cf_deployer/driver/instance.rb b/lib/cf_deployer/driver/instance.rb index dac559e..1b70bca 100644 --- a/lib/cf_deployer/driver/instance.rb +++ b/lib/cf_deployer/driver/instance.rb @@ -14,15 +14,16 @@ def initialize instance_obj_or_id def status instance_info = { } - [:status, :public_ip_address, :private_ip_address, :image_id].each do |stat| + [:public_ip_address, :private_ip_address, :image_id].each do |stat| instance_info[stat] = aws_instance.send(stat) end + instance_info[:status] = aws_instance.state instance_info[:key_pair] = aws_instance.key_pair.name instance_info end def aws_instance - @instance_obj ||= AWS::EC2.new.instances[@id] + @instance_obj ||= Aws::EC2::Instance.new(@id) end end end diff --git a/lib/cf_deployer/driver/route53_driver.rb b/lib/cf_deployer/driver/route53_driver.rb index 3e21d90..3846b4a 100644 --- a/lib/cf_deployer/driver/route53_driver.rb +++ b/lib/cf_deployer/driver/route53_driver.rb @@ -2,7 +2,7 @@ module CfDeployer module Driver class Route53 def initialize(aws_route53 = nil) - @aws_route53 = aws_route53 || AWS::Route53.new + @aws_route53 = aws_route53 || Aws::Route53::Client.new end def find_alias_target(hosted_zone_name, target_host_name) @@ -17,7 +17,7 @@ def set_alias_target(hosted_zone_name, target_host_name, elb_hosted_zone_id, elb Log.info "set alias target --Hosted Zone: #{hosted_zone_name} --Host Name: #{target_host_name} --ELB DNS Name: #{elb_dnsname} --ELB Zone ID: #{elb_hosted_zone_id}" hosted_zone_name = trailing_dot(hosted_zone_name) target_host_name = trailing_dot(target_host_name) - hosted_zone = @aws_route53.hosted_zones.find { |z| z.name == hosted_zone_name } + hosted_zone = @aws_route53.list_hosted_zones_by_name.hosted_zones.find { |z| z.name == hosted_zone_name } raise ApplicationError.new('Target zone not found!') if hosted_zone.nil? change = { @@ -34,7 +34,7 @@ def set_alias_target(hosted_zone_name, target_host_name, elb_hosted_zone_id, elb } batch = { - hosted_zone_id: hosted_zone.path, + hosted_zone_id: hosted_zone.id, change_batch: { changes: [change] } @@ -53,6 +53,7 @@ def delete_record_set(hosted_zone_name, target_host_name) record_set.delete if record_set end end + private def change_resource_record_sets_with_retry(batch) @@ -60,7 +61,7 @@ def change_resource_record_sets_with_retry(batch) while attempts < 20 begin attempts = attempts + 1 - @aws_route53.client.change_resource_record_sets(batch) + @aws_route53.change_resource_record_sets(batch) return rescue Exception => e Log.info "Failed to update alias target, trying again in 20 seconds." @@ -72,11 +73,13 @@ def change_resource_record_sets_with_retry(batch) end def get_hosted_zone(zone_name) - @aws_route53.hosted_zones.find { |z| z.name == trailing_dot(zone_name.downcase) } + @aws_route53.list_hosted_zones_by_name.hosted_zones.find { |z| z.name == trailing_dot(zone_name.downcase) } end - + def get_record_set(hosted_zone, target_host_name) - hosted_zone.resource_record_sets.find { |r| r.name == trailing_dot(target_host_name.downcase) } + @aws_route53.list_resource_record_sets({:hosted_zone_id => hosted_zone.id}) + .resource_record_sets + .find { |r| r.name == trailing_dot(target_host_name.downcase) } end def trailing_dot(text) diff --git a/lib/cf_deployer/stack.rb b/lib/cf_deployer/stack.rb index 91a752f..7c2d57e 100644 --- a/lib/cf_deployer/stack.rb +++ b/lib/cf_deployer/stack.rb @@ -21,7 +21,12 @@ def deploy capabilities = @context[:capabilities] || [] notify = @context[:notify] || [] tags = @context[:tags] || {} - params = to_str(@context[:inputs].select{|key, value| @context[:defined_parameters].keys.include?(key)}) + params = @context[:inputs].select{|key, value| @context[:defined_parameters].keys.include?(key)} + .map do |key, value| + next nil unless @context[:defined_parameters].keys.include?(key) + { parameter_key: key.to_s, parameter_value: value.to_s } + end.compact + CfDeployer::Driver::DryRun.guard "Skipping deploy" do if exists? override_policy_json = nil @@ -56,7 +61,12 @@ def output key def find_output key begin @cf_driver.query_output(key) - rescue AWS::CloudFormation::Errors::ValidationError => e + rescue Aws::CloudFormation::Errors::OperationStatusCheckFailedException => e + raise ResourceNotInReadyState.new("Resource stack not in ready state yet, perhaps you should provision it first?") + rescue => e + puts '*' * 80 + puts e + puts '*' * 80 raise ResourceNotInReadyState.new("Resource stack not in ready state yet, perhaps you should provision it first?") end end @@ -88,20 +98,18 @@ def status end def resource_statuses - AWS.memoize do - resources = @cf_driver.resource_statuses.merge( { :asg_instances => {}, :instances => {} } ) - if resources['AWS::AutoScaling::AutoScalingGroup'] - resources['AWS::AutoScaling::AutoScalingGroup'].keys.each do |asg_name| - resources[:asg_instances][asg_name] = CfDeployer::Driver::AutoScalingGroup.new(asg_name).instance_statuses - end + resources = @cf_driver.resource_statuses.merge( { :asg_instances => {}, :instances => {} } ) + if resources['AWS::AutoScaling::AutoScalingGroup'] + resources['AWS::AutoScaling::AutoScalingGroup'].keys.each do |asg_name| + resources[:asg_instances][asg_name] = CfDeployer::Driver::AutoScalingGroup.new(asg_name).instance_statuses end - if resources['AWS::EC2::Instance'] - resources['AWS::EC2::Instance'].keys.each do |instance_id| - resources[:instances][instance_id] = CfDeployer::Driver::Instance.new(instance_id).status - end + end + if resources['AWS::EC2::Instance'] + resources['AWS::EC2::Instance'].keys.each do |instance_id| + resources[:instances][instance_id] = CfDeployer::Driver::Instance.new(instance_id).status end - resources end + resources end def name @@ -114,10 +122,6 @@ def template private - def to_str(hash) - hash.each { |k,v| hash[k] = v.to_s } - end - def update_stack(template, params, capabilities, tags, override_policy_json) Log.info "Updating stack #{@stack_name}..." args = { @@ -136,7 +140,7 @@ def create_stack(template, params, capabilities, tags, notify, create_policy_jso args = { :disable_rollback => true, :capabilities => capabilities, - :notify => notify, + :notification_arns => notify, :tags => reformat_tags(tags), :parameters => params } @@ -148,7 +152,7 @@ def create_stack(template, params, capabilities, tags, notify, create_policy_jso end def stack_status - @cf_driver.stack_status + @cf_driver.stack_status || :does_not_exist end def wait_for_stack_op_terminate @@ -167,19 +171,19 @@ def wait_for_stack_to_delete begin Log.info "current status: #{stack_status}" sleep 15 - rescue AWS::CloudFormation::Errors::ValidationError => e - if e.message =~ /does not exist/ - break # This is what we wanted anyways - else - raise e - end + rescue Aws::CloudFormation::Errors::StackSetNotFoundException => e + break # This is what we wanted anyway + rescue => e + puts '*' * 80 + puts e + raise e end end } end def reformat_tags tags_hash - tags_hash.keys.map { |key| { 'Key' => key.to_s, 'Value' => tags_hash[key].to_s } } + tags_hash.keys.map { |key| { :key => key.to_s, :value => tags_hash[key].to_s } } end end end diff --git a/spec/aws_call_count_spec_helper.rb b/spec/aws_call_count_spec_helper.rb index 73b1570..a2f5f32 100644 --- a/spec/aws_call_count_spec_helper.rb +++ b/spec/aws_call_count_spec_helper.rb @@ -11,8 +11,20 @@ def override_aws_environment options = {} options[:AWS_REGION] ||= 'us-east-1' options[:AWS_ACCESS_KEY_ID] ||= 'someId' options[:AWS_SECRET_ACCESS_KEY] ||= 'secretKey' - - override_environment_variables(options) { yield } + options[:AWS_EC2_METADATA_DISABLED] ||= 'true' + options[:AWS_SDK_CONFIG_OPT_OUT] ||= '1' + + override_environment_variables(options) do + previous_aws_config = Aws.config.dup + Aws.config.update( + region: options[:AWS_REGION], + credentials: Aws::Credentials.new(options[:AWS_ACCESS_KEY_ID], options[:AWS_SECRET_ACCESS_KEY]) + ) + yield + ensure + Aws.config.clear + Aws.config.update(previous_aws_config) + end end def override_environment_variables options = {} diff --git a/spec/fakes/instance.rb b/spec/fakes/instance.rb index 5f9fb97..d89ed24 100644 --- a/spec/fakes/instance.rb +++ b/spec/fakes/instance.rb @@ -1,3 +1,5 @@ +require 'ostruct' + module Fakes class Instance @@ -20,7 +22,7 @@ def initialize(options) instance_variable_set "@#{attrib}", (options[attrib] || defaults[attrib]) end - @key_pair = ::AWS::EC2::KeyPair.new (options[:key_pair] || defaults[:key_pair] ) + @key_pair = OpenStruct.new(name: (options[:key_pair] || defaults[:key_pair])) end def inspect @@ -29,4 +31,4 @@ def inspect alias_method :to_s, :inspect end -end \ No newline at end of file +end diff --git a/spec/fakes/route53_client.rb b/spec/fakes/route53_client.rb index 797b0ee..4ddb48f 100644 --- a/spec/fakes/route53_client.rb +++ b/spec/fakes/route53_client.rb @@ -1,20 +1,19 @@ +require 'ostruct' + module Fakes class AWSRoute53 attr_reader :fail_counter, :hosted_zones, :client def initialize(opts = {}) - @client = AWSRoute53Client.new(opts) @hosted_zones = opts[:hosted_zones] - @fail_counter = 0 - end - end - class AWSRoute53Client - attr_reader :fail_counter - def initialize(opts = {}) @times_to_fail = opts[:times_to_fail] @fail_counter = 0 end + def list_hosted_zones_by_name + OpenStruct.new(hosted_zones: hosted_zones) + end + def change_resource_record_sets(*args) @fail_counter = @fail_counter + 1 raise 'Error' if @fail_counter <= @times_to_fail diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb8c348..aa47a43 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,7 @@ if ENV['DEBUG'] RSPEC_LOG.level = Logger::DEBUG - AWS.config :logger => RSPEC_LOG + # AWS.config :logger => RSPEC_LOG end def puts *args @@ -20,3 +20,12 @@ def ignore_errors rescue => e RSPEC_LOG.debug "Intentionally ignoring error: #{e.message}" end + +RSpec.configure do |config| + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true +end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 7fcd716..3606d2b 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -28,11 +28,11 @@ end it "application should get all components" do - @app.components.length.should eq(4) - @base.should_not be_nil - @db.should_not be_nil - @queue.should_not be_nil - @web.should_not be_nil + expect(@app.components.length).to eq(4) + expect(@base).not_to be_nil + expect(@db).not_to be_nil + expect(@queue).not_to be_nil + expect(@web).not_to be_nil end context "order components by dependencies" do @@ -154,7 +154,7 @@ context "deploy all components" do it "should deploy all components if no component specified" do @app.deploy - @log.should eq("base db queue web ") + expect(@log).to eq("base db queue web ") end end @@ -162,7 +162,7 @@ it "should deploy specified components" do @context[:targets] = ['web', 'db'] @app.deploy - @log.should eq("db web ") + expect(@log).to eq("db web ") end end end @@ -179,13 +179,13 @@ it 'should get json templates for all components' do @app.json - @log.should eq("base db queue web ") + expect(@log).to eq("base db queue web ") end it 'should get json templates for components specified' do @context[:targets] = ['web', 'db'] @app.json - @log.should eq('db web ') + expect(@log).to eq('db web ') end end end diff --git a/spec/unit/component_spec.rb b/spec/unit/component_spec.rb index b148983..92f3504 100644 --- a/spec/unit/component_spec.rb +++ b/spec/unit/component_spec.rb @@ -98,7 +98,7 @@ it "should ask strategy if component exists" do expect(@strategy).to receive(:exists?){ true } - @web.exists?.should eq(true) + expect(@web.exists?).to eq(true) end it "should find direct dependencies" do @@ -106,7 +106,7 @@ base = CfDeployer::Component.new('myApp', 'uat', 'base', {}) web.dependencies << base - web.depends_on?(base).should eq(true) + expect(web.depends_on?(base)).to eq(true) end it "should find transitive dependencies" do @@ -117,7 +117,7 @@ haproxy.dependencies << base web.dependencies << haproxy - web.depends_on?(base).should eq(true) + expect(web.depends_on?(base)).to eq(true) end it "should find cyclic dependency" do diff --git a/spec/unit/config_loader_spec.rb b/spec/unit/config_loader_spec.rb index 69fdebf..1cb9e25 100644 --- a/spec/unit/config_loader_spec.rb +++ b/spec/unit/config_loader_spec.rb @@ -2,7 +2,7 @@ describe "load config settings" do before :each do - Dir.mkdir 'tmp' unless Dir.exists?('tmp') + Dir.mkdir 'tmp' unless Dir.exist?('tmp') @config_file = File.expand_path("../../../tmp/test_config.yml", __FILE__) @base_json = File.expand_path("../../../tmp/base.json", __FILE__) @api_json = File.expand_path("../../../tmp/api.json", __FILE__) @@ -146,143 +146,143 @@ it "all the keys should be symbols in config" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:base][:'deployment-strategy'].should eq('create-or-update') - config['components'].should be_nil + expect(config[:components][:base][:'deployment-strategy']).to eq('create-or-update') + expect(config['components']).to be_nil end it "should copy application, environment, component to component settings" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:api][:settings][:application].should eq("myApp") - config[:components][:api][:settings][:component].should eq("api") - config[:components][:api][:settings][:environment].should eq("uat") - config[:components][:base][:settings][:application].should eq("myApp") - config[:components][:base][:settings][:component].should eq("base") - config[:components][:base][:settings][:environment].should eq("uat") + expect(config[:components][:api][:settings][:application]).to eq("myApp") + expect(config[:components][:api][:settings][:component]).to eq("api") + expect(config[:components][:api][:settings][:environment]).to eq("uat") + expect(config[:components][:base][:settings][:application]).to eq("myApp") + expect(config[:components][:base][:settings][:component]).to eq("base") + expect(config[:components][:base][:settings][:environment]).to eq("uat") end it "should copy region to coponent settings" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat', :region => 'us-west-1'}) - config[:components][:api][:settings][:region].should eq("us-west-1") - config[:components][:base][:settings][:region].should eq("us-west-1") + expect(config[:components][:api][:settings][:region]).to eq("us-west-1") + expect(config[:components][:base][:settings][:region]).to eq("us-west-1") end it "should copy application, environment, component to component inputs" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:api][:inputs][:application].should eq("myApp") - config[:components][:api][:inputs][:component].should eq("api") - config[:components][:api][:inputs][:environment].should eq("uat") - config[:components][:base][:inputs][:application].should eq("myApp") - config[:components][:base][:inputs][:component].should eq("base") - config[:components][:base][:inputs][:environment].should eq("uat") + expect(config[:components][:api][:inputs][:application]).to eq("myApp") + expect(config[:components][:api][:inputs][:component]).to eq("api") + expect(config[:components][:api][:inputs][:environment]).to eq("uat") + expect(config[:components][:base][:inputs][:application]).to eq("myApp") + expect(config[:components][:base][:inputs][:component]).to eq("base") + expect(config[:components][:base][:inputs][:environment]).to eq("uat") end it "should copy region to coponent inputs" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat', :region => 'us-west-1'}) - config[:components][:api][:inputs][:region].should eq("us-west-1") - config[:components][:base][:inputs][:region].should eq("us-west-1") + expect(config[:components][:api][:inputs][:region]).to eq("us-west-1") + expect(config[:components][:base][:inputs][:region]).to eq("us-west-1") end it "config_dir option should be copied to component context" do config_dir = File.dirname(@config_file) config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:api][:config_dir].should eq(config_dir) - config[:components][:base][:config_dir].should eq(config_dir) - config[:components][:'front-end'][:config_dir].should eq(config_dir) + expect(config[:components][:api][:config_dir]).to eq(config_dir) + expect(config[:components][:base][:config_dir]).to eq(config_dir) + expect(config[:components][:'front-end'][:config_dir]).to eq(config_dir) end it "notify option should be merged to component context" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'}) - config[:components][:base][:notify].should eq(['arn:root', 'arn:base', 'arn:dev']) - config[:components][:api][:notify].should eq(['arn:root', 'arn:api', 'arn:dev']) - config[:components][:'front-end'][:notify].should eq(['arn:root', 'arn:base', 'arn:dev']) + expect(config[:components][:base][:notify]).to eq(['arn:root', 'arn:base', 'arn:dev']) + expect(config[:components][:api][:notify]).to eq(['arn:root', 'arn:api', 'arn:dev']) + expect(config[:components][:'front-end'][:notify]).to eq(['arn:root', 'arn:base', 'arn:dev']) end it "notify option should be merged to environment context" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:base][:notify].should eq(['arn:root', 'arn:base']) - config[:components][:api][:notify].should eq(['arn:root', 'arn:api']) - config[:components][:'front-end'][:notify].should eq(['arn:root', 'arn:base']) + expect(config[:components][:base][:notify]).to eq(['arn:root', 'arn:base']) + expect(config[:components][:api][:notify]).to eq(['arn:root', 'arn:api']) + expect(config[:components][:'front-end'][:notify]).to eq(['arn:root', 'arn:base']) end it "tags option should be copied to component context" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:api][:tags].should eq({:version => 'v1.1'}) - config[:components][:base][:tags].should eq({:version => 'v1.1', :component => 'base'}) - config[:components][:'front-end'][:tags].should eq({:version => 'v1.1'}) + expect(config[:components][:api][:tags]).to eq({:version => 'v1.1'}) + expect(config[:components][:base][:tags]).to eq({:version => 'v1.1', :component => 'base'}) + expect(config[:components][:'front-end'][:tags]).to eq({:version => 'v1.1'}) end it "component's settings should be merged to common settings" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'}) - config[:components][:api][:inputs][:'timeout'].should eq(90) - config[:components][:api][:inputs][:'require-basic-auth'].should eq(true) - config[:components][:api][:inputs][:'mail-server'].should eq('http://api.abc.com') + expect(config[:components][:api][:inputs][:'timeout']).to eq(90) + expect(config[:components][:api][:inputs][:'require-basic-auth']).to eq(true) + expect(config[:components][:api][:inputs][:'mail-server']).to eq('http://api.abc.com') end it "environment's settings should be merged to component settings" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'}) - config[:components][:api][:inputs][:'timeout'].should eq(60) - config[:components][:api][:inputs][:'require-basic-auth'].should eq(true) - config[:components][:api][:inputs][:'mail-server'].should eq('http://dev.abc.com') + expect(config[:components][:api][:inputs][:'timeout']).to eq(60) + expect(config[:components][:api][:inputs][:'require-basic-auth']).to eq(true) + expect(config[:components][:api][:inputs][:'mail-server']).to eq('http://dev.abc.com') end it "should merge environment's components to component settings" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'production'}) - config[:components][:'front-end'][:inputs][:'cname'].should eq('prod-front-end.myserver.com') - config[:components][:api][:inputs][:'cname'].should eq('myserver.com') + expect(config[:components][:'front-end'][:inputs][:'cname']).to eq('prod-front-end.myserver.com') + expect(config[:components][:api][:inputs][:'cname']).to eq('myserver.com') end it "environment variables without prefix 'cfdeploy_settings_' should not be merged to components settings" do ENV['timeout'] = "180" config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'}) - config[:components][:api][:inputs][:'timeout'].should eq(60) + expect(config[:components][:api][:inputs][:'timeout']).to eq(60) end it "should merge environment variables should be merged to components settings" do ENV['cfdeploy_inputs_timeout'] = "180" config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'}) - config[:components][:api][:inputs][:'timeout'].should eq("180") + expect(config[:components][:api][:inputs][:'timeout']).to eq("180") end it "cli settings should be merged to components settings" do ENV['cfdeploy_settings_timeout'] = "180" config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev',:cli_overrides => {:settings => {:timeout => 45}}}) - config[:components][:api][:settings][:'timeout'].should eq(45) + expect(config[:components][:api][:settings][:'timeout']).to eq(45) end it "should set cloudFormation parameter names into each component" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:base][:defined_parameters].should eq({}) - config[:components][:api][:defined_parameters].should eq({:'require-basic-auth' => {:Default => "true"}}) - config[:components][:'front-end'][:defined_parameters].should eq({:'require-basic-auth' => {}, :'mail-server' => {}}) + expect(config[:components][:base][:defined_parameters]).to eq({}) + expect(config[:components][:api][:defined_parameters]).to eq({:'require-basic-auth' => {:Default => "true"}}) + expect(config[:components][:'front-end'][:defined_parameters]).to eq({:'require-basic-auth' => {}, :'mail-server' => {}}) end it "should set cloudFormation output names into each component" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:base][:defined_outputs].should eq({:'vpc-id' => {}, :'AutoScalingGroupName'=>{}, :'public-subnet-id'=>{}}) - config[:components][:api][:defined_outputs].should eq({}) - config[:components][:'front-end'][:defined_outputs].should eq({:'AutoScalingGroupName'=>{}, :'elb-cname' => {}}) + expect(config[:components][:base][:defined_outputs]).to eq({:'vpc-id' => {}, :'AutoScalingGroupName'=>{}, :'public-subnet-id'=>{}}) + expect(config[:components][:api][:defined_outputs]).to eq({}) + expect(config[:components][:'front-end'][:defined_outputs]).to eq({:'AutoScalingGroupName'=>{}, :'elb-cname' => {}}) end it "should remove common settings in order not to confuse us" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:settings].should be_nil + expect(config[:settings]).to be_nil end it "should set default elb-name-output for cname-swap strategy" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'front-end'][:settings][:'elb-name-output'].should eq('ELBName') + expect(config[:components][:'front-end'][:settings][:'elb-name-output']).to eq('ELBName') end it "should set default auto-scaling-group-name-output for cname-swap strategy" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:api][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ]) + expect(config[:components][:api][:settings][:'auto-scaling-group-name-output']).to eq([ CfDeployer::Defaults::AutoScalingGroupName ]) end it "should set auto-scaling-group-name-output to default if auto-scaling-group-name exists in output for create-or-update strategy" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:base][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ]) + expect(config[:components][:base][:settings][:'auto-scaling-group-name-output']).to eq([ CfDeployer::Defaults::AutoScalingGroupName ]) end it "should not set auto-scaling-group-name-output to default if auto-scaling-group-name does not exists in output for create-or-update strategy" do @@ -295,17 +295,17 @@ File.open(@base_json, 'w') {|f| f.write(base_json) } config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'base'][:settings][:'auto-scaling-group-name-output'].should be_nil + expect(config[:components][:'base'][:settings][:'auto-scaling-group-name-output']).to be_nil end it "should set auto-scaling-group-name-output to default if auto-scaling-group-name exists in output for cname-swap strategy" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ]) + expect(config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output']).to eq([ CfDeployer::Defaults::AutoScalingGroupName ]) end it "should set raise-error-for-unused-inputs to default" do config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'front-end'][:settings][:'raise-error-for-unused-inputs'].should eq(CfDeployer::Defaults::RaiseErrorForUnusedInputs) + expect(config[:components][:'front-end'][:settings][:'raise-error-for-unused-inputs']).to eq(CfDeployer::Defaults::RaiseErrorForUnusedInputs) end it "should not set auto-scaling-group-name-output to default if auto-scaling-group-name does not exists in output for cname-swap strategy" do @@ -319,23 +319,23 @@ File.open(@front_end_json, 'w') {|f| f.write(front_end_json) } config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output'].should be_nil + expect(config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output']).to be_nil end it "should ERB the config file and provide the environment in the binding" do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :environment => 'DrWho') - config[:components][:base][:inputs][:'foobar'].should eq('DrWho.IsGreat') + expect(config[:components][:base][:inputs][:'foobar']).to eq('DrWho.IsGreat') end it "should ERB the component JSON and make the parsed template available" do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :environment => 'DrWho') - CfDeployer::ConfigLoader.erb_to_json('json-with-erb', config[:components][:'json-with-erb']).should include('DrWho') + expect(CfDeployer::ConfigLoader.erb_to_json('json-with-erb', config[:components][:'json-with-erb'])).to include('DrWho') end it 'should use error_document to show the broken document when parsing broken ERB' do config = { :config_dir => File.dirname(@config_file) } - CfDeployer::ConfigLoader.any_instance.should_receive(:error_document) - expect { CfDeployer::ConfigLoader.erb_to_json('broken_erb', config) }.to raise_error + allow_any_instance_of(CfDeployer::ConfigLoader).to receive(:error_document) + expect { CfDeployer::ConfigLoader.erb_to_json('broken_erb', config) }.to raise_error(TypeError) end it 'should use error_document to show the broken document when parsing broken json' do @@ -347,35 +347,36 @@ :defined_parameters => {}, :defined_outputs => {} } - CfDeployer::ConfigLoader.any_instance.should_receive(:error_document) - expect { loader.send(:cf_template, 'broken_json') }.to raise_error + allow_any_instance_of(CfDeployer::ConfigLoader).to receive(:error_document) + + expect { loader.send(:cf_template, 'broken_json') }.to raise_error(RuntimeError) end it 'should use error_document to show the broken document when parsing broken yaml' do - CfDeployer::ConfigLoader.any_instance.should_receive(:error_document) - expect { CfDeployer::ConfigLoader.new.send(:load_yaml, @broken_yaml) }.to raise_error + allow_any_instance_of(CfDeployer::ConfigLoader).to receive(:error_document) + expect { CfDeployer::ConfigLoader.new.send(:load_yaml, @broken_yaml) }.to raise_error(Psych::SyntaxError) end it 'should set default keep-previous-stack to true' do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file) - config[:components][:api][:settings][:'keep-previous-stack'].should eq(CfDeployer::Defaults::KeepPreviousStack) + expect(config[:components][:api][:settings][:'keep-previous-stack']).to eq(CfDeployer::Defaults::KeepPreviousStack) end it 'should keep keep-previous-stack setting' do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file) - config[:components][:'front-end'][:settings][:'keep-previous-stack'].should be_false + expect(config[:components][:'front-end'][:settings][:'keep-previous-stack']).to be_falsey end context 'targets' do it 'should use all components as targets if no targets are specified' do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file) - config[:targets].should eq(['base', 'api', 'front-end', 'very-simple', 'json-with-erb']) + expect(config[:targets]).to eq(['base', 'api', 'front-end', 'very-simple', 'json-with-erb']) end it 'should keep targets if targets are specified' do config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :component => ['api', 'web']) - config[:targets].should eq(['api', 'web']) + expect(config[:targets]).to eq(['api', 'web']) end end @@ -418,7 +419,7 @@ File.open(@config_file, 'w') {|f| f.write(config) } config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file}) - config[:components][:'api'][:defined_parameters].should eq({ db:'base::elb-cname', url:'http://abc.com'}) + expect(config[:components][:'api'][:defined_parameters]).to eq({ db:'base::elb-cname', url:'http://abc.com'}) end end diff --git a/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb b/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb index 4ee2533..0cffa4c 100644 --- a/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +++ b/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb @@ -35,18 +35,18 @@ it 'no if no G and B stacks exist' do blue_stack.die! green_stack.die! - CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?.should be_false + expect(CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?).to be_falsey end it 'yes if B stacks exist' do blue_stack.live! green_stack.die! - CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?.should be_true + expect(CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?).to be_truthy end it 'yes if G stacks exist' do blue_stack.die! green_stack.live! - CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?.should be_true + expect(CfDeployer::DeploymentStrategy.create(app, env, component, context).exists?).to be_truthy end end @@ -123,7 +123,7 @@ @log += "#{arg[:parameters][:name]} deleted." end CfDeployer::DeploymentStrategy.create(app, env, component, context).destroy - @log.should eq('green deleted.blue deleted.') + expect(@log).to eq('green deleted.blue deleted.') end end @@ -601,7 +601,7 @@ allow(green_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}} allow(blue_asg_driver).to receive(:describe) {{desired: 3, min: 1, max: 5}} asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context) - asg_swap.output_value("AutoScalingGroupID").should eq("blueASG") + expect(asg_swap.output_value("AutoScalingGroupID")).to eq("blueASG") end it 'should get the information where the value comes from if the active stack does not exist' do @@ -612,7 +612,7 @@ allow(green_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}} allow(blue_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}} asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context) - asg_swap.output_value(:a_key).should eq("The value will be referenced from the output a_key of undeployed component worker") + expect(asg_swap.output_value(:a_key)).to eq("The value will be referenced from the output a_key of undeployed component worker") end end @@ -642,7 +642,7 @@ :status => 'green deployed' } } - asg_swap.status.should eq(expected_result) + expect(asg_swap.status).to eq(expected_result) end it 'should get status for both green and blue stacks including resources info' do @@ -667,7 +667,7 @@ } } } - asg_swap.status(true).should eq(expected_result) + expect(asg_swap.status(true)).to eq(expected_result) end end @@ -686,7 +686,7 @@ allow(blue_stack).to receive(:resource_statuses) { asg_ids('foo', 'bar') } asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context) - asg_swap.send(:get_active_asgs, blue_stack).should eq(['foo']) + expect(asg_swap.send(:get_active_asgs, blue_stack)).to eq(['foo']) end end @@ -700,7 +700,7 @@ allow(green_asg_driver).to receive(:describe) { {min: 0, desired: 0, max: 0} } asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context) - asg_swap.send(:stack_active?, blue_stack).should be(true) + expect(asg_swap.send(:stack_active?, blue_stack)).to be(true) end end diff --git a/spec/unit/deployment_strategy/base_spec.rb b/spec/unit/deployment_strategy/base_spec.rb index 8c572b7..c2e0aa4 100644 --- a/spec/unit/deployment_strategy/base_spec.rb +++ b/spec/unit/deployment_strategy/base_spec.rb @@ -57,12 +57,11 @@ it "should return nil if there is no active stack" do the_stack = double() - the_stack.should_receive(:exists?).and_return(false) - the_stack.should_not_receive(:template) + expect(the_stack).to receive(:exists?).and_return(false) + expect(the_stack).not_to receive(:template) strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) - strategy.should_receive(:active_stack).and_return(the_stack) - # strategy.should_not_receive(:get_parameters_outputs) + expect(strategy).to receive(:active_stack).and_return(the_stack) expect( strategy.active_template ).to eq(nil) end @@ -71,11 +70,11 @@ the_template = double the_stack = double - the_stack.should_receive(:exists?).and_return(true) - the_stack.should_receive(:template).and_return(the_template) + expect(the_stack).to receive(:exists?).and_return(true) + expect(the_stack).to receive(:template).and_return(the_template) strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) - strategy.should_receive(:active_stack).and_return(the_stack) + expect(strategy).to receive(:active_stack).and_return(the_stack) expect( strategy.active_template ).to eq(the_template) end @@ -96,8 +95,8 @@ it "should run the specified hook" do hook = double() - CfDeployer::Hook.should_receive(:new).and_return(hook) - hook.should_receive(:run) + expect(CfDeployer::Hook).to receive(:new).and_return(hook) + expect(hook).to receive(:run) strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) strategy.instance_variable_set('@params_and_outputs_resolved', true) @@ -107,30 +106,30 @@ it "should not try to resolve parameters and outputs they're already initialized" do strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) strategy.instance_variable_set('@params_and_outputs_resolved', true) - strategy.should_not_receive(:get_parameters_outputs) + expect(strategy).not_to receive(:get_parameters_outputs) strategy.run_hook(:some_hook) end it "should not try to resolve parameters and outputs if there's no running stack" do the_stack = double() - the_stack.should_receive(:exists?).and_return(false) - the_stack.should_receive(:name).and_return("thestack") + expect(the_stack).to receive(:exists?).and_return(false) + expect(the_stack).to receive(:name).and_return("thestack") strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) - strategy.should_receive(:active_stack).and_return(the_stack) - strategy.should_not_receive(:get_parameters_outputs) + expect(strategy).to receive(:active_stack).and_return(the_stack) + expect(strategy).not_to receive(:get_parameters_outputs) strategy.run_hook(:some_hook) end it "should not try to run a hook if there's no running stack" do - CfDeployer::Hook.should_not_receive(:new) + expect(CfDeployer::Hook).not_to receive(:new) the_stack = double() - the_stack.should_receive(:exists?).and_return(false) - the_stack.should_receive(:name).and_return("thestack") + expect(the_stack).to receive(:exists?).and_return(false) + expect(the_stack).to receive(:name).and_return("thestack") strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web]) - strategy.should_receive(:active_stack).and_return(the_stack) + expect(strategy).to receive(:active_stack).and_return(the_stack) strategy.run_hook(:some_hook) end end diff --git a/spec/unit/deployment_strategy/cname_swap_spec.rb b/spec/unit/deployment_strategy/cname_swap_spec.rb index a7b8ab4..8f5a720 100644 --- a/spec/unit/deployment_strategy/cname_swap_spec.rb +++ b/spec/unit/deployment_strategy/cname_swap_spec.rb @@ -66,7 +66,7 @@ end expect(dns_driver).to receive(:delete_record_set).with('foobar.com', 'test.foobar.com') cname_swap.destroy - @log.should eq('green deleted.blue deleted.') + expect(@log).to eq('green deleted.blue deleted.') end end @@ -188,19 +188,19 @@ blue_stack.die! green_stack.die! cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) - cname_swap.exists?.should be_false + expect(cname_swap.exists?).to be_falsey end it 'yes, if green stack exists and blue stack does not' do blue_stack.die! cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) - cname_swap.exists?.should be_true + expect(cname_swap.exists?).to be_truthy end it 'yes, if blue stack exists and green stack does not' do green_stack.die! cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) - cname_swap.exists?.should be_true + expect(cname_swap.exists?).to be_truthy end end @@ -220,7 +220,7 @@ my_context.delete :dns_driver my_context[:settings][:'dns-driver'] = 'CfDeployer::Driver::Verisign' cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', my_context) - cname_swap.send(:dns_driver).class.to_s.should eq(my_context[:settings][:'dns-driver']) + expect(cname_swap.send(:dns_driver).class.to_s).to eq(my_context[:settings][:'dns-driver']) end end @@ -283,14 +283,14 @@ it 'should get stack output if active stack exists' do allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' } - cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) - cname_swap.output_value("AutoScalingGroupID").should eq("blueASG") + cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) + expect(cname_swap.output_value("AutoScalingGroupID")).to eq("blueASG") end it 'should get the information where the value comes from if the active stack does not exist' do allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ '' } - cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) - cname_swap.output_value(:a_key).should eq("The value will be referenced from the output a_key of undeployed component web") + cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) + expect(cname_swap.output_value(:a_key)).to eq("The value will be referenced from the output a_key of undeployed component web") end end end diff --git a/spec/unit/deployment_strategy/create_or_update_spec.rb b/spec/unit/deployment_strategy/create_or_update_spec.rb index f922513..da0f415 100644 --- a/spec/unit/deployment_strategy/create_or_update_spec.rb +++ b/spec/unit/deployment_strategy/create_or_update_spec.rb @@ -33,7 +33,7 @@ expect(@after_create_hook).to receive(:run) do |given_context| hook_context = given_context end - @stack.should_receive(:exists?).and_return(false) + expect(@stack).to receive(:exists?).and_return(false) expect(@stack).to receive(:deploy) @create_or_update.deploy expect(hook_context[:parameters]).to eq( {'vpc' => 'myvpc'} ) @@ -45,7 +45,7 @@ expect(@after_update_hook).to receive(:run) do |given_context| hook_context = given_context end - @stack.should_receive(:exists?).and_return(true) + expect(@stack).to receive(:exists?).and_return(true) expect(@stack).to receive(:deploy) @create_or_update.deploy expect(hook_context[:parameters]).to eq( {'vpc' => 'myvpc'} ) @@ -60,7 +60,7 @@ context = @context[:components][:base] context[:settings] = {} context[:settings][:'auto-scaling-group-name-output'] = ['AutoScalingGroupID'] - @stack.should_receive(:exists?).and_return(false) + expect(@stack).to receive(:exists?).and_return(false) allow(@stack).to receive(:find_output).with('AutoScalingGroupID') { 'asg_name' } allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('asg_name') { asg_driver } allow(asg_driver).to receive(:describe) { {desired:2, min:1, max:3} } @@ -74,19 +74,19 @@ it 'should tell if stack exists' do expect(@stack).to receive(:exists?){true} - @create_or_update.exists?.should eq(true) + expect(@create_or_update.exists?).to eq(true) end it 'should get stack output' do allow(@stack).to receive(:exists?){true} expect(@stack).to receive(:output).with(:a_key){ "output_value" } - @create_or_update.output_value(:a_key).should eq("output_value") + expect(@create_or_update.output_value(:a_key)).to eq("output_value") end it 'should get the information where the value comes from if the stack does not exist' do allow(@stack).to receive(:exists?){false} expect(@stack).not_to receive(:output).with(anything) - @create_or_update.output_value(:a_key).should eq("The value will be referenced from the output a_key of undeployed component base") + expect(@create_or_update.output_value(:a_key)).to eq("The value will be referenced from the output a_key of undeployed component base") end context '#destroy' do @@ -119,11 +119,11 @@ allow(@stack).to receive(:exists?) { true } end it 'should get status from stack' do - @create_or_update.status.should eq({ 'base-uat' => {status: 'deployed'}}) + expect(@create_or_update.status).to eq({ 'base-uat' => {status: 'deployed'}}) end it 'should get status from stack including resource info' do allow(@stack).to receive(:resource_statuses) { 'resource1' } - @create_or_update.status(true).should eq({ 'base-uat' => {status: 'deployed', resources: 'resource1'}}) + expect(@create_or_update.status(true)).to eq({ 'base-uat' => {status: 'deployed', resources: 'resource1'}}) end end end diff --git a/spec/unit/driver/auto_scaling_group_spec.rb b/spec/unit/driver/auto_scaling_group_spec.rb index f770111..c8ccd25 100644 --- a/spec/unit/driver/auto_scaling_group_spec.rb +++ b/spec/unit/driver/auto_scaling_group_spec.rb @@ -2,38 +2,38 @@ describe 'Autoscaling group driver' do let(:group) { double('group', :desired_capacity => 2, :min_size => 1, :max_size => 4)} - let(:scaling) { double('scaling', :groups => { 'myAsg' => group}) } let(:ec2_instance1) { double('ec2_instance1') } let(:ec2_instance2) { double('ec2_instance2') } let(:ec2_instance3) { double('ec2_instance3') } let(:ec2_instance4) { double('ec2_instance4') } - let(:instance1) { double('instance1', :health_status => 'HEALTHY', :ec2_instance => ec2_instance1)} - let(:instance2) { double('instance2', :health_status => 'HEALTHY', :ec2_instance => ec2_instance2)} - let(:instance3) { double('instance3', :health_status => 'HEALTHY', :ec2_instance => ec2_instance3)} - let(:instance4) { double('instance4', :health_status => 'HEALTHY', :ec2_instance => ec2_instance4)} + let(:instance1) { double('instance1', :id => 'instance1', :health_status => 'HEALTHY', :ec2_instance => ec2_instance1)} + let(:instance2) { double('instance2', :id => 'instance2', :health_status => 'HEALTHY', :ec2_instance => ec2_instance2)} + let(:instance3) { double('instance3', :id => 'instance3', :health_status => 'HEALTHY', :ec2_instance => ec2_instance3)} + let(:instance4) { double('instance4', :id => 'instance4', :health_status => 'HEALTHY', :ec2_instance => ec2_instance4)} let(:load_balancer) { double('load_balancer', :instances => [instance1, instance2, instance3, instance4]) } + let(:elb_driver) { double(Aws::ElasticLoadBalancing::Client) } before :each do - allow(AWS::AutoScaling).to receive(:new) { scaling } - allow(group).to receive(:load_balancers) { [] } - allow(group).to receive(:auto_scaling_instances) { [] } - allow(group).to receive(:ec2_instances) { [] } + allow(Aws::ElasticLoadBalancing::Client).to receive(:new) { elb_driver } + allow(group).to receive(:load_balancer_names) { [] } + allow(group).to receive(:instances) { [] } + allow(Aws::AutoScaling::AutoScalingGroup).to receive(:new).with('myAsg') { group } @driver = CfDeployer::Driver::AutoScalingGroup.new('myAsg', 1) end it 'should describe group' do - @driver.describe.should eq({ min: 1, max: 4, desired: 2}) + expect(@driver.describe).to eq({ min: 1, max: 4, desired: 2}) end describe '#warm_up' do it 'should warm up the group to the desired size' do - expect(group).to receive(:set_desired_capacity).with(2) + expect(group).to receive(:set_desired_capacity).with({desired_capacity: 2}) expect(@driver).to receive(:wait_for_desired_capacity) @driver.warm_up 2 end it 'should wait for the warm up of the group even if desired is the same as the minimum' do - expect(group).to receive(:set_desired_capacity).with(1) + expect(group).to receive(:set_desired_capacity).with({desired_capacity: 1}) expect(@driver).to receive(:wait_for_desired_capacity) @driver.warm_up 1 end @@ -45,7 +45,7 @@ end it 'should warm up to maximum if desired number is greater than maximum size of group' do - expect(group).to receive(:set_desired_capacity).with(4) + expect(group).to receive(:set_desired_capacity).with({desired_capacity: 4}) expect(@driver).to receive(:wait_for_desired_capacity) @driver.warm_up 5 end @@ -57,14 +57,14 @@ instance2 = double('instance2', :health_status => 'HEALTHY', id: 'instance2') instance3 = double('instance3', :health_status => 'UNHEALTHY', id: 'instance3') instance4 = double('instance4', :health_status => 'HEALTHY', id: 'instance4') - allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3, instance4]} + allow(group).to receive(:instances){[instance1, instance2, instance3, instance4]} expect(@driver.healthy_instance_ids).to eql ['instance1', 'instance2', 'instance4'] end it 'returns the ids of all instances that are healthy (case insensitive)' do instance1 = double('instance1', :health_status => 'HealThy', id: 'instance1') - allow(group).to receive(:auto_scaling_instances){[instance1]} + allow(group).to receive(:instances){[instance1]} expect(@driver.healthy_instance_ids).to eql ['instance1'] end @@ -73,69 +73,80 @@ describe '#in_service_instance_ids' do context 'when there are no load balancers' do it 'returns no ids' do - allow(group).to receive(:load_balancers).and_return([]) + allow(group).to receive(:load_balancer_names).and_return([]) expect(@driver.in_service_instance_ids).to eq [] end end context 'when there is only 1 elb' do + let(:elb1_instance_health) { double('elb1_instance_health') } + + before(:each) do + allow(group).to receive(:load_balancer_names).and_return(['elb1']) + allow(elb_driver).to receive(:describe_instance_health) + .with(hash_including(load_balancer_name: 'elb1')) { elb1_instance_health } + end + it 'returns the ids of all instances that are in service' do - health1 = { state: 'InService', instance: double('i1', id: 'instance1') } - health2 = { state: 'OutOfService', instance: double('i2', id: 'instance2') } - health3 = { state: 'InService', instance: double('i3', id: 'instance3') } - health4 = { state: 'InService', instance: double('i4', id: 'instance4') } + health1 = double(:state => 'InService', instance_id: 'instance1') + health2 = double(:state => 'OutOfService', instance_id: 'instance2') + health3 = double(:state => 'InService', instance_id: 'instance3') + health4 = double(:state => 'InService', instance_id: 'instance4') - instance_collection = double('instance_collection', health: [health1, health2, health3, health4]) - elb = double('elb', instances: instance_collection) - allow(group).to receive(:load_balancers).and_return([ elb ]) + allow(elb1_instance_health).to receive(:instance_states) { [health1, health2, health3, health4] } + allow(group).to receive(:load_balancer_names).and_return([ 'elb1' ]) expect(@driver.in_service_instance_ids).to eql ['instance1', 'instance3', 'instance4'] end end context 'when there are multiple elbs' do + let(:elb1_instance_health) { double('elb1_instance_health') } + let(:elb2_instance_health) { double('elb2_instance_health') } + let(:elb3_instance_health) { double('elb3_instance_health') } + + before(:each) do + allow(group).to receive(:load_balancer_names).and_return(['elb1', 'elb2', 'elb3']) + allow(elb_driver).to receive(:describe_instance_health) + .with(hash_including(load_balancer_name: 'elb1')) { elb1_instance_health } + allow(elb_driver).to receive(:describe_instance_health) + .with(hash_including(load_balancer_name: 'elb2')) { elb2_instance_health } + allow(elb_driver).to receive(:describe_instance_health) + .with(hash_including(load_balancer_name: 'elb3')) { elb3_instance_health } + end + it 'returns only the ids of instances that are in all ELBs' do - health1 = { state: 'InService', instance: double('i1', id: 'instance1') } - health2 = { state: 'InService', instance: double('i2', id: 'instance2') } - health3 = { state: 'InService', instance: double('i3', id: 'instance3') } - health4 = { state: 'InService', instance: double('i4', id: 'instance4') } - health5 = { state: 'InService', instance: double('i5', id: 'instance5') } - - instance_collection1 = double('instance_collection1', health: [health1, health2, health3]) - instance_collection2 = double('instance_collection2', health: [health2, health3, health4]) - instance_collection3 = double('instance_collection3', health: [health2, health3, health5]) - elb1 = double('elb1', instances: instance_collection1) - elb2 = double('elb2', instances: instance_collection2) - elb3 = double('elb3', instances: instance_collection3) - allow(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ]) + health1 = double(state: 'InService', instance_id: 'instance1') + health2 = double(state: 'InService', instance_id: 'instance2') + health3 = double(state: 'InService', instance_id: 'instance3') + health4 = double(state: 'InService', instance_id: 'instance4') + health5 = double(state: 'InService', instance_id: 'instance5') + + allow(elb1_instance_health).to receive(:instance_states) { [health1, health2, health3] } + allow(elb2_instance_health).to receive(:instance_states) { [health2, health3, health4] } + allow(elb3_instance_health).to receive(:instance_states) { [health2, health3, health5] } # Only instance 2 and 3 are associated with all ELB's expect(@driver.in_service_instance_ids).to eql ['instance2', 'instance3'] end it 'returns only the ids instances that are InService in all ELBs' do - health11 = { state: 'OutOfService', instance: double('i1', id: 'instance1') } - health12 = { state: 'InService', instance: double('i2', id: 'instance2') } - health13 = { state: 'InService', instance: double('i3', id: 'instance3') } - - health21 = { state: 'InService', instance: double('i1', id: 'instance1') } - health22 = { state: 'InService', instance: double('i2', id: 'instance2') } - health23 = { state: 'OutOfService', instance: double('i3', id: 'instance3') } - - health31 = { state: 'InService', instance: double('i1', id: 'instance1') } - health32 = { state: 'InService', instance: double('i2', id: 'instance2') } - health33 = { state: 'InService', instance: double('i3', id: 'instance3') } + health11 = double(state: 'OutOfService', instance_id: 'instance1') + health12 = double(state: 'InService', instance_id: 'instance2') + health13 = double(state: 'InService', instance_id: 'instance3') - instance_collection1 = double('instance_collection1', health: [health11, health12, health13]) - instance_collection2 = double('instance_collection2', health: [health21, health22, health23]) - instance_collection3 = double('instance_collection3', health: [health31, health32, health33]) + health21 = double(state: 'InService', instance_id: 'instance1') + health22 = double(state: 'InService', instance_id: 'instance2') + health23 = double(state: 'OutOfService', instance_id: 'instance3') - elb1 = double('elb1', instances: instance_collection1) - elb2 = double('elb2', instances: instance_collection2) - elb3 = double('elb3', instances: instance_collection3) + health31 = double(state: 'InService', instance_id: 'instance1') + health32 = double(state: 'InService', instance_id: 'instance2') + health33 = double(state: 'InService', instance_id: 'instance3') - allow(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ]) + allow(elb1_instance_health).to receive(:instance_states) { [health11, health12, health13] } + allow(elb2_instance_health).to receive(:instance_states) { [health21, health22, health23] } + allow(elb3_instance_health).to receive(:instance_states) { [health31, health32, health33] } # Only instance 2 is InService across all ELB's expect(@driver.in_service_instance_ids).to eql ['instance2'] @@ -158,7 +169,7 @@ it 'should return the number of instances that are both healthy, and in service' do healthy_instance_ids = ['1', '3', '4', '5'] in_service_instance_ids = ['3', '4'] - allow(@driver).to receive(:load_balancers).and_return(double('elb', empty?: false)) + allow(@driver).to receive(:load_balancer_names).and_return(['elb1']) expect(@driver).to receive(:healthy_instance_ids).and_return(healthy_instance_ids) expect(@driver).to receive(:in_service_instance_ids).and_return(in_service_instance_ids) @@ -176,7 +187,7 @@ describe '#cool_down' do it 'should cool down group' do expect(group).to receive(:update).with({min_size: 0, max_size: 0}) - expect(group).to receive(:set_desired_capacity).with(0) + expect(group).to receive(:set_desired_capacity).with({desired_capacity: 0}) @driver.cool_down end end @@ -184,7 +195,7 @@ describe '#warm_up_cooled_group' do it 'should set min, max, and desired from a hash' do hash = {:max => 5, :min => 2, :desired => 3} - allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3]} + allow(group).to receive(:instances){[instance1, instance2, instance3]} expect(group).to receive(:update).with({:min_size => 2, :max_size => 5}) expect(@driver).to receive(:warm_up).with(3) @driver.warm_up_cooled_group hash @@ -193,15 +204,13 @@ describe '#instance_statuses' do it 'should get the status for any EC2 instances' do - aws_instance = double AWS::EC2::Instance - expect(aws_instance).to receive(:id) { 'i-abcd1234' } - allow(@driver).to receive(:ec2_instances) { [ aws_instance ] } + allow(@driver).to receive(:instances) { [ instance1 ] } returned_status = { :status => :some_status } cfd_instance = double CfDeployer::Driver::Instance - expect(CfDeployer::Driver::Instance).to receive(:new).with(aws_instance) { cfd_instance } + expect(CfDeployer::Driver::Instance).to receive(:new).with(instance1.id) { cfd_instance } expect(cfd_instance).to receive(:status) { returned_status } - expect(@driver.instance_statuses).to eq( { 'i-abcd1234' => returned_status } ) + expect(@driver.instance_statuses).to eq( { 'instance1' => returned_status } ) end end @@ -226,7 +235,7 @@ expect(group).to receive(:desired_capacity).and_return(expected_number) expect(@driver).to receive(:healthy_instance_count).and_return(expected_number) - expect(@driver.desired_capacity_reached?).to be_true + expect(@driver.desired_capacity_reached?).to be_truthy end it 'returns false if healthy instance count is less than desired capacity' do @@ -235,7 +244,7 @@ expect(group).to receive(:desired_capacity).and_return(expected_number) expect(@driver).to receive(:healthy_instance_count).and_return(expected_number - 1) - expect(@driver.desired_capacity_reached?).to be_false + expect(@driver.desired_capacity_reached?).to be_falsey end it 'returns true if healthy instance count is more than desired capacity' do @@ -244,7 +253,7 @@ expect(group).to receive(:desired_capacity).and_return(expected_number) expect(@driver).to receive(:healthy_instance_count).and_return(expected_number + 1) - expect(@driver.desired_capacity_reached?).to be_true + expect(@driver.desired_capacity_reached?).to be_truthy end end end diff --git a/spec/unit/driver/cloud_formation_spec.rb b/spec/unit/driver/cloud_formation_spec.rb index dbd516a..4f594fd 100644 --- a/spec/unit/driver/cloud_formation_spec.rb +++ b/spec/unit/driver/cloud_formation_spec.rb @@ -2,8 +2,8 @@ describe 'CloudFormation' do let(:outputs) { [output1, output2] } - let(:output1) { double('output1', :key => 'key1', :value => 'value1')} - let(:output2) { double('output2', :key => 'key2', :value => 'value2')} + let(:output1) { double('output1', :output_key => 'key1', :output_value => 'value1')} + let(:output2) { double('output2', :output_key => 'key2', :output_value => 'value2')} let(:parameters) { double('parameters')} let(:resource_summaries) { [ { @@ -22,24 +22,27 @@ :resource_status => 'STATUS_2' } ] } - let(:stack) { double('stack', :outputs => outputs, :parameters => parameters, :resource_summaries => resource_summaries) } + let(:stack) { double('stack', :stack_name => 'testStack', :outputs => outputs, :parameters => parameters) } + let(:cloudFormationStacks) { [stack] } let(:cloudFormation) { double('cloudFormation', - :stacks => - {'testStack' => stack - }) + :describe_stacks => double(:stacks => cloudFormationStacks), + :list_stack_resources => double(:stack_resource_summaries => resource_summaries) + ) } before(:each) do - allow(AWS::CloudFormation).to receive(:new) { cloudFormation } + allow(Aws::CloudFormation::Client).to receive(:new) { cloudFormation } + allow(cloudFormation).to receive(:create_stack) + allow(cloudFormation).to receive(:update_stack) end it 'should get outputs of stack' do - CfDeployer::Driver::CloudFormation.new('testStack').outputs.should eq({'key1' => 'value1', 'key2' => 'value2'}) + expect(CfDeployer::Driver::CloudFormation.new('testStack').outputs).to eq({'key1' => 'value1', 'key2' => 'value2'}) end it 'should get parameters of stack' do - CfDeployer::Driver::CloudFormation.new('testStack').parameters.should eq(parameters) + expect(CfDeployer::Driver::CloudFormation.new('testStack').parameters).to eq(parameters) end context 'update_stack' do @@ -60,32 +63,31 @@ result = cloud_formation.update_stack :template, {} end - expect(result).to be_false + expect(result).to be_falsey end it 'returns false if no updates were performed (because no difference in template)' do cloud_formation = CfDeployer::Driver::CloudFormation.new 'my_stack' - expect(cloud_formation).to receive(:aws_stack).and_raise(AWS::CloudFormation::Errors::ValidationError.new('No updates are to be performed')) + expect(cloudFormation).to receive(:update_stack).and_raise(Aws::CloudFormation::Errors::ValidationError.new(nil, 'No updates are to be performed')) result = nil CfDeployer::Driver::DryRun.disable_for do result = cloud_formation.update_stack :template, {} end - expect(result).to be_false + expect(result).to be_falsey end it 'returns true when updates are performed' do cloud_formation = CfDeployer::Driver::CloudFormation.new 'my_stack' - aws_stack = double(:update => :did_something) - expect(cloud_formation).to receive(:aws_stack).and_return aws_stack result = nil CfDeployer::Driver::DryRun.disable_for do result = cloud_formation.update_stack :template, {} end - expect(result).to be_true + expect(cloudFormation).to have_received(:update_stack) + expect(result).to be_truthy end end @@ -102,7 +104,7 @@ } } - CfDeployer::Driver::CloudFormation.new('testStack').resource_statuses.should eq(expected) + expect(CfDeployer::Driver::CloudFormation.new('testStack').resource_statuses).to eq(expected) end end end diff --git a/spec/unit/driver/dry_run_spec.rb b/spec/unit/driver/dry_run_spec.rb index 9c0fb93..8b48eb4 100644 --- a/spec/unit/driver/dry_run_spec.rb +++ b/spec/unit/driver/dry_run_spec.rb @@ -11,8 +11,8 @@ enabled_in_block = CfDeployer::Driver::DryRun.enabled? end - expect(enabled_in_block).to be_true - expect(CfDeployer::Driver::DryRun.enabled?).to be_false + expect(enabled_in_block).to be_truthy + expect(CfDeployer::Driver::DryRun.enabled?).to be_falsey end end @@ -27,8 +27,8 @@ end end.to raise_error('boom') - expect(enabled_in_block).to be_true - expect(CfDeployer::Driver::DryRun.enabled?).to be_false + expect(enabled_in_block).to be_truthy + expect(CfDeployer::Driver::DryRun.enabled?).to be_falsey end end end @@ -42,8 +42,8 @@ enabled_in_block = CfDeployer::Driver::DryRun.enabled? end - expect(enabled_in_block).to be_false - expect(CfDeployer::Driver::DryRun.enabled?).to be_true + expect(enabled_in_block).to be_falsey + expect(CfDeployer::Driver::DryRun.enabled?).to be_truthy end end @@ -58,8 +58,8 @@ end end.to raise_error('boom') - expect(enabled_in_block).to be_false - expect(CfDeployer::Driver::DryRun.enabled?).to be_true + expect(enabled_in_block).to be_falsey + expect(CfDeployer::Driver::DryRun.enabled?).to be_truthy end end end diff --git a/spec/unit/driver/elb_spec.rb b/spec/unit/driver/elb_spec.rb index 3e2db14..84b7cab 100644 --- a/spec/unit/driver/elb_spec.rb +++ b/spec/unit/driver/elb_spec.rb @@ -2,10 +2,16 @@ describe CfDeployer::Driver::Elb do it 'should get dns name and hosted zone id' do - elb = double('elb', :dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id') - aws = double('aws', :load_balancers => {'myelb' => elb}) + elb = double('elasticloadbalancing', :dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id') + + aws = double('aws') + elb_name = 'myelb' - expect(AWS::ELB).to receive(:new){aws} - CfDeployer::Driver::Elb.new.find_dns_and_zone_id(elb_name).should eq({:dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id'}) + load_balancer_descriptions = double('elb', :load_balancer_descriptions => [elb]) + + expect(Aws::ElasticLoadBalancing::Client).to receive(:new){aws} + expect(aws).to receive(:describe_load_balancers).with(hash_including(load_balancer_names: [elb_name])) { load_balancer_descriptions } + + expect(CfDeployer::Driver::Elb.new.find_dns_and_zone_id(elb_name)).to eq({:dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id'}) end end diff --git a/spec/unit/driver/instance_spec.rb b/spec/unit/driver/instance_spec.rb index 5210fa4..f64b2d0 100644 --- a/spec/unit/driver/instance_spec.rb +++ b/spec/unit/driver/instance_spec.rb @@ -11,14 +11,9 @@ } - aws = double AWS::EC2 - expect(AWS::EC2).to receive(:new) { aws } - - instance_collection = double AWS::EC2::InstanceCollection - expect(aws).to receive(:instances) { instance_collection } - instance = Fakes::Instance.new expected.merge( { :id => 'i-wxyz1234' } ) - expect(instance_collection).to receive(:[]).with(instance.id) { instance } + allow(instance).to receive(:state) { :pending } + expect(Aws::EC2::Instance).to receive(:new).with('i-wxyz1234') { instance } instance_status = CfDeployer::Driver::Instance.new('i-wxyz1234').status diff --git a/spec/unit/driver/route53_spec.rb b/spec/unit/driver/route53_spec.rb index 8f4fe45..4b10367 100644 --- a/spec/unit/driver/route53_spec.rb +++ b/spec/unit/driver/route53_spec.rb @@ -6,50 +6,59 @@ describe ".find_alias_target" do it "should raise an error when the target zone cannot be found" do route53 = double('route53') - allow(AWS::Route53).to receive(:new) { route53 } + allow(Aws::Route53::Client).to receive(:new) { route53 } - allow(route53).to receive(:hosted_zones) { [] } + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [] } expect { subject.find_alias_target('abc.com', 'foo') }.to raise_error('Target zone not found!') end it "should get empty alias target when the target host cannot be found" do - zone = double('zone') - allow(zone).to receive(:name) { 'target.com.' } - allow(zone).to receive(:resource_record_sets) { [] } + resource_record_sets = double('resource_record_sets', :resource_record_sets => []) + zone = double('zone', :id => '/hostedzone/hostedzoneid', :name => 'target.com.') route53 = double('route53') - allow(AWS::Route53).to receive(:new) { route53 } - allow(route53).to receive(:hosted_zones) { [zone] } + allow(Aws::Route53::Client).to receive(:new) { route53 } + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [zone] } + allow(route53).to receive(:list_resource_record_sets).with(hash_including(hosted_zone_id: '/hostedzone/hostedzoneid')) { resource_record_sets } - subject.find_alias_target('target.com', 'foo').should be_nil + expect(subject.find_alias_target('target.com', 'foo')).to be_nil end it "should get alias target" do host = double('host', :name => 'foo.target.com.', :alias_target => { :dns_name => 'abc.com.'}) - zone = double('zone', :name => 'target.com.', :resource_record_sets => [host]) - route53 = double('route53', :hosted_zones => [zone]) - allow(AWS::Route53).to receive(:new) { route53 } + resource_record_sets = double('resource_record_sets', :resource_record_sets => [host]) + zone = double('zone', :id => '/hostedzone/hostedzoneid', :name => 'target.com.') + route53 = double('route53') + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [zone] } + allow(route53).to receive(:list_resource_record_sets).with(hash_including(hosted_zone_id: '/hostedzone/hostedzoneid')) { resource_record_sets } + allow(Aws::Route53::Client).to receive(:new) { route53 } - subject.find_alias_target('Target.com', 'Foo.target.com').should eq('abc.com') + expect(subject.find_alias_target('Target.com', 'Foo.target.com')).to eq('abc.com') end it "should get a nil alias target when the record exists but has no alias target" do host = double('host', :name => 'foo.target.com.', :alias_target => nil) - zone = double('zone', :name => 'target.com.', :resource_record_sets => [host]) - route53 = double('route53', :hosted_zones => [zone]) - allow(AWS::Route53).to receive(:new) { route53 } + resource_record_sets = double('resource_record_sets', :resource_record_sets => [host]) + zone = double('zone', :id => '/hostedzone/hostedzoneid', :name => 'target.com.') + route53 = double('route53') + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [zone] } + allow(route53).to receive(:list_resource_record_sets).with(hash_including(hosted_zone_id: '/hostedzone/hostedzoneid')) { resource_record_sets } + allow(Aws::Route53::Client).to receive(:new) { route53 } - subject.find_alias_target('target.com', 'foo.target.com').should be_nil + expect(subject.find_alias_target('target.com', 'foo.target.com')).to be_nil end it "should get alias target when zone and host name having trailing dot" do host = double('host', :name => 'foo.target.com.', :alias_target => { :dns_name => 'abc.com.'}) - zone = double('zone', :name => 'target.com.', :resource_record_sets => [host]) - route53 = double('route53', :hosted_zones => [zone]) - allow(AWS::Route53).to receive(:new) { route53 } + resource_record_sets = double('resource_record_sets', :resource_record_sets => [host]) + zone = double('zone', :id => '/hostedzone/hostedzoneid', :name => 'target.com.') + route53 = double('route53') + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [zone] } + allow(route53).to receive(:list_resource_record_sets).with(hash_including(hosted_zone_id: '/hostedzone/hostedzoneid')) { resource_record_sets } + allow(Aws::Route53::Client).to receive(:new) { route53 } - subject.find_alias_target('target.com.', 'foo.target.com.').should eq('abc.com') + expect(subject.find_alias_target('target.com.', 'foo.target.com.')).to eq('abc.com') end end @@ -57,25 +66,25 @@ describe ".set_alias_target" do it "should raise an error when the target-zone cannot be found" do route53 = double('route53') - allow(AWS::Route53).to receive(:new) { route53 } + allow(Aws::Route53::Client).to receive(:new) { route53 } - allow(route53).to receive(:hosted_zones) { [] } + allow(route53).to receive_message_chain(:list_hosted_zones_by_name, :hosted_zones) { [] } expect { subject.set_alias_target('abc.com', 'foo', 'abc', 'def') }.to raise_error('Target zone not found!') end it "should attempt multiple times" do - failing_route53 = Fakes::AWSRoute53.new(times_to_fail: 5, hosted_zones: [double(name: 'abc.com.', path: '')]) + failing_route53 = Fakes::AWSRoute53.new(times_to_fail: 5, hosted_zones: [double(id: '/hostedzone/hostedzoneid', name: 'abc.com.', path: '')]) route53_driver = CfDeployer::Driver::Route53.new(failing_route53) allow(route53_driver).to receive(:sleep) route53_driver.set_alias_target('abc.com', 'foo', 'abc', 'def') - expect(failing_route53.client.fail_counter).to eq(6) + expect(failing_route53.fail_counter).to eq(6) end it "should raise an exception when failing more than 20 times" do - failing_route53 = Fakes::AWSRoute53.new(times_to_fail: 21, hosted_zones: [double(name: 'abc.com.', path: '')]) + failing_route53 = Fakes::AWSRoute53.new(times_to_fail: 21, hosted_zones: [double(id: '/hostedzone/hostedzoneid', name: 'abc.com.', path: '')]) route53_driver = CfDeployer::Driver::Route53.new(failing_route53) allow(route53_driver).to receive(:sleep) diff --git a/spec/unit/hook_spec.rb b/spec/unit/hook_spec.rb index ef44bd2..d874497 100644 --- a/spec/unit/hook_spec.rb +++ b/spec/unit/hook_spec.rb @@ -3,7 +3,7 @@ describe CfDeployer::Hook do before :all do - Dir.mkdir 'tmp' unless Dir.exists?('tmp') + Dir.mkdir 'tmp' unless Dir.exist?('tmp') @file = File.expand_path("../../../tmp/test_code.rb", __FILE__) end it 'should eval string as hook' do @@ -67,8 +67,8 @@ it 'should catch SyntaxError during eval and show nicer output' do context = { app: 'myApp' } the_hook = CfDeployer::Hook.new('MyHook', "puts 'hello") - the_hook.should_receive :error_document - expect { the_hook.run(context) }.to raise_error + expect(the_hook).to receive(:error_document) + expect { the_hook.run(context) }.to raise_error(SyntaxError) end it 'should catch NoMethodError during eval and show nicer output' do diff --git a/spec/unit/stack_spec.rb b/spec/unit/stack_spec.rb index dccf3d3..57de9e6 100644 --- a/spec/unit/stack_spec.rb +++ b/spec/unit/stack_spec.rb @@ -26,10 +26,10 @@ expected_opt = { :disable_rollback => true, :capabilities => [], - :notify => ['topic1_arn', 'topic2_arn'], - :tags => [{'Key' => 'app', 'Value' => 'app1'}, - {'Key' => 'env', 'Value' => 'dev'}], - :parameters => {:foo => 'bar'} + :notification_arns => ['topic1_arn', 'topic2_arn'], + :tags => [{:key => 'app', :value => 'app1'}, + {:key => 'env', :value => 'dev'}], + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}] } expect(@cf_driver).to receive(:create_stack).with(template, expected_opt) stack = CfDeployer::Stack.new('test','web', @config) @@ -47,10 +47,10 @@ expected_opt = { :disable_rollback => true, :capabilities => [], - :notify => ['topic1_arn', 'topic2_arn'], - :tags => [{'Key' => 'app', 'Value' => 'app1'}, - {'Key' => 'env', 'Value' => 'dev'}], - :parameters => {:foo => 'bar'}, + :notification_arns => ['topic1_arn', 'topic2_arn'], + :tags => [{:key => 'app', :value => 'app1'}, + {:key => 'env', :value => 'dev'}], + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}], :stack_policy_body => create_policy } expect(@cf_driver).to receive(:create_stack).with(template, expected_opt) @@ -65,7 +65,7 @@ allow(@cf_driver).to receive(:stack_status) { :create_complete } expected_opt = { :capabilities => [], - :parameters => {:foo => 'bar'} + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}] } expect(@cf_driver).to receive(:update_stack).with(template, expected_opt) stack = CfDeployer::Stack.new('test','web', @config) @@ -79,7 +79,7 @@ allow(@cf_driver).to receive(:stack_status) { :create_complete } expected_opt = { :capabilities => [], - :parameters => {:foo => 'bar'} + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}] } expect(@cf_driver).to receive(:update_stack).with(template, expected_opt).and_return(true) stack = CfDeployer::Stack.new('test','web', @config) @@ -94,7 +94,7 @@ allow(@cf_driver).to receive(:stack_status) { :create_complete } expected_opt = { :capabilities => [], - :parameters => {:foo => 'bar'} + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}] } expect(@cf_driver).to receive(:update_stack).with(template, expected_opt).and_return(false) stack = CfDeployer::Stack.new('test','web', @config) @@ -109,7 +109,7 @@ allow(@cf_driver).to receive(:stack_status) { :update_rollback_complete } expected_opt = { :capabilities => [], - :parameters => {:foo => 'bar'} + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}] } expect(@cf_driver).to receive(:update_stack).with(template, expected_opt).and_return(false) stack = CfDeployer::Stack.new('test','web', @config) @@ -126,7 +126,7 @@ allow(@cf_driver).to receive(:stack_status) { :create_complete } expected_opt = { :capabilities => [], - :parameters => {:foo => 'bar'}, + :parameters => [{:parameter_key => 'foo', :parameter_value => 'bar'}], :stack_policy_during_update_body => override_policy } expect(@cf_driver).to receive(:update_stack).with(template, expected_opt) @@ -168,7 +168,7 @@ context '#output' do it 'should get output value' do expect(@cf_driver).to receive(:query_output).with('mykey'){ 'myvalue'} - @stack.output('mykey').should eq('myvalue') + expect(@stack.output('mykey')).to eq('myvalue') end it 'should get error if output is empty' do @@ -180,12 +180,12 @@ context '#find_output' do it 'should get output value' do expect(@cf_driver).to receive(:query_output).with('mykey'){ 'myvalue'} - @stack.find_output('mykey').should eq('myvalue') + expect(@stack.find_output('mykey')).to eq('myvalue') end it 'should return nil for non-existent value' do expect(@cf_driver).to receive(:query_output).with('mykey'){ nil } - @stack.find_output('mykey').should be(nil) + expect(@stack.find_output('mykey')).to be(nil) end end @@ -218,16 +218,16 @@ it 'should be fine to get not exist error after deletion' do allow(@stack).to receive(:exists?).and_return(true, true) - allow(@stack).to receive(:stack_status).and_raise(AWS::CloudFormation::Errors::ValidationError.new('the stack does not exist')) + allow(@stack).to receive(:stack_status).and_raise(Aws::CloudFormation::Errors::StackSetNotFoundException.new(nil, 'the stack does not exist')) expect(@cf_driver).to receive(:delete_stack) expect {@stack.delete}.not_to raise_error end it 'should raise an error if a validation error is thrown not about stack does not exist' do allow(@stack).to receive(:exists?).and_return(true, true) - allow(@stack).to receive(:stack_status).and_raise(AWS::CloudFormation::Errors::ValidationError.new('I am an error')) + allow(@stack).to receive(:stack_status).and_raise(Aws::CloudFormation::Errors::InvalidOperationException.new(nil, 'I am an error')) expect(@cf_driver).to receive(:delete_stack) - expect {@stack.delete}.to raise_error + expect {@stack.delete}.to raise_error(Aws::CloudFormation::Errors::InvalidOperationException) end end