├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.md ├── README.md ├── Rakefile ├── lib ├── resources │ ├── autoscaling │ │ ├── group.rb │ │ └── launch_configuration.rb │ ├── cloudformation │ │ └── stack.rb │ ├── cloudwatch │ │ └── alarm.rb │ ├── common.rb │ ├── dynamodb │ │ └── table.rb │ ├── ec2 │ │ ├── image.rb │ │ ├── instance.rb │ │ ├── internet_gateway.rb │ │ ├── network_interface.rb │ │ ├── route_table.rb │ │ ├── security_group.rb │ │ ├── subnet.rb │ │ ├── subnets.rb │ │ └── vpc.rb │ ├── elasticloadbalancing │ │ └── load_balancer.rb │ ├── rds │ │ └── db_instance.rb │ └── redshift │ │ └── cluster.rb └── serverspec-aws.rb └── spec ├── autoscaling ├── group_spec.rb └── launch_configuration_spec.rb ├── cloudformation └── stack_spec.rb ├── cloudwatch └── alarm_spec.rb ├── dynamodb └── table_spec.rb ├── ec2 ├── image_spec.rb ├── instance_spec.rb ├── internet_gateway_spec.rb ├── network_interface_spec.rb ├── route_table_spec.rb ├── security_group_spec.rb ├── subnet_spec.rb ├── vpc_spec.rb └── vpc_subnets_spec.rb ├── elasticloadbalancing └── load_balancer_spec.rb ├── rds └── db_instance_spec.rb ├── redshift └── cluster_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .yardoc 3 | doc/ 4 | Gemfile.lock 5 | serverspec-aws.gemspec 6 | .bundle/ 7 | /pkg/ 8 | /.project 9 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'serverspec-aws.gemspec' 4 | 5 | Style/Encoding: 6 | Enabled: true 7 | 8 | Style/UnneededPercentQ: 9 | Enabled: true 10 | 11 | Style/StringLiterals: 12 | Enabled: true 13 | 14 | Style/PercentLiteralDelimiters: 15 | Enabled: true 16 | 17 | Style/MultilineIfThen: 18 | Enabled: true 19 | 20 | Style/FileName: 21 | Enabled: true 22 | Exclude: ['lib/serverspec-aws.rb'] 23 | 24 | Style/SymbolArray: 25 | EnforcedStyle: brackets 26 | 27 | Layout/TrailingBlankLines: 28 | Enabled: true 29 | 30 | # Configuration parameters: AllowURI, URISchemes. 31 | Metrics/LineLength: 32 | Max: 80 33 | 34 | Metrics/BlockLength: 35 | Max: 50 36 | ExcludedMethods: RSpec.describe 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.6 # still supplied with ChefDK 4 | - 2.1.8 5 | - 2.2.4 6 | - 2.3.0 7 | 8 | script: 9 | - bundle install --with development 10 | - bundle exec rake test 11 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --output-dir ../docs - README.md LICENSE.md 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.1.2 2 | * Feature/windows instance #5 3 | * Add Image resource #6 4 | * Add method accessible_from to test security group rules #9 5 | 6 | ## v0.1.1 7 | * Iterable EC2::Subnets (PR [#4](https://github.com/SaltwaterC/serverspec-aws/pull/4)). 8 | 9 | ## v0.1.0 10 | * Initial release. 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'aws-sdk', '~>2' 4 | gem 'netaddr', '~>1.5' 5 | gem 'require_all', '~>1' 6 | gem 'serverspec', '~>2' 7 | 8 | group :development, optional: true do 9 | gem 'guard-rspec', '~>4' 10 | gem 'guard-rubocop', '~>1' 11 | gem 'jeweler', '~>2' 12 | gem 'rake', '~>10.4' 13 | gem 'rspec', '~>3' 14 | gem 'rubocop', '~>0.52' 15 | gem 'yard', '~>0.9' 16 | end 17 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | group :red_green_refactor, halt_on_fail: true do 2 | guard :rubocop 3 | 4 | guard :rspec, cmd: 'rspec' do 5 | watch('spec/spec_helper.rb') { 'spec' } 6 | watch(%r{^lib/resources/(.+)/(.+)\.rb$}) do |m| 7 | "spec/#{m[1]}/#{m[2]}_spec.rb" 8 | end 9 | watch(%r{^spec/.+/.+\.rb$}) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # serverspec-aws license 2 | 3 | Copyright (c) 2015, Ștefan Rusu All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * Neither the name of Ștefan Rusu nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ȘTEFAN RUSU BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About [![build status](https://secure.travis-ci.org/SaltwaterC/serverspec-aws.png?branch=master)](https://travis-ci.org/SaltwaterC/serverspec-aws) 2 | 3 | Serverspec resources for AWS using AWS SDK for Ruby v2. 4 | 5 | ## Documentation 6 | 7 | The documentation is available as [GitHub page](http://saltwaterc.github.io/serverspec-aws/). 8 | 9 | ## Usage 10 | 11 | There aren't examples per se, but you can take a peek at the spec directory. The integration tests against itself also shows the usage mode. However, in real world use cases, the stubbing of the AWS SDK is (obviously) not necesary, hence you don't need to pass the instance argument for the resource classes which was implemented as a testing feature. Also, the spec_helper doesn't need to enable the stub_responses for real world use cases. 12 | 13 | ## Contributors 14 | 15 | * [Tim Myerscough](https://github.com/temyers) 16 | 17 | ## Development 18 | 19 | The following docker container will give you a command shell into an environment for development 20 | ``` 21 | docker run -v $PWD:/root -it ruby:2.4 /bin/bash 22 | ``` 23 | 24 | ## Running Tests 25 | 26 | ``` 27 | bundle install 28 | rake spec 29 | ``` 30 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Jeweler stuff 2 | begin 3 | require 'jeweler' 4 | require_relative 'lib/serverspec-aws' 5 | Jeweler::Tasks.new do |gem| 6 | gem.name = 'serverspec-aws' 7 | gem.version = Serverspec::Type::AWS::VERSION 8 | gem.summary = %(Serverspec for AWS) 9 | gem.description = %(Serverspec resources for testing the AWS infrastructure) 10 | gem.author = 'Ștefan Rusu' 11 | gem.email = 'saltwaterc@gmail.com' 12 | gem.files = Dir['lib/*.rb'] + Dir['lib/resources/*.rb'] 13 | gem.license = 'BSD-3-Clause' 14 | gem.homepage = 'https://github.com/SaltwaterC/serverspec-aws' 15 | end 16 | rescue LoadError 17 | STDERR.puts 'Jeweler, or one of its dependencies, is not available.' 18 | end 19 | 20 | begin 21 | # Rubocop stuff 22 | require 'rubocop/rake_task' 23 | RuboCop::RakeTask.new 24 | rescue LoadError 25 | STDERR.puts 'Rubocop, or one of its dependencies, is not available.' 26 | end 27 | 28 | # Rspec stuff 29 | begin 30 | require 'rspec/core/rake_task' 31 | RSpec::Core::RakeTask.new(:spec) 32 | rescue LoadError 33 | STDERR.puts 'RSpec, or one of its dependencies, is not available.' 34 | end 35 | 36 | # Guard stuff 37 | desc 'Executes guard' 38 | task :guard do 39 | # outdated documentation is outdated 40 | # require 'guard' 41 | # Guard.run_all 42 | system 'guard' 43 | end 44 | 45 | begin 46 | # YARD stuff 47 | require 'yard' 48 | YARD::Rake::YardocTask.new 49 | rescue LoadError 50 | STDERR.puts 'YARD, or one of its dependencies, is not available.' 51 | end 52 | 53 | desc 'Runs the cleanup task, then the YARD server' 54 | task yardserver: [:cleanup] do 55 | system 'yard server --reload' 56 | end 57 | 58 | # Cleanup stuff 59 | desc 'Delete all temporary files' 60 | task :cleanup do 61 | rm_rf '.yardoc' 62 | rm_rf 'doc' 63 | rm_rf 'pkg' 64 | rm_f 'serverspec-aws.gemspec' 65 | rm_f 'Gemfile.lock' 66 | end 67 | 68 | desc 'Runs the rubocop and the spec tasks' 69 | task test: [:spec, :rubocop] 70 | 71 | # Testing stuff 72 | task default: [:test] 73 | -------------------------------------------------------------------------------- /lib/resources/autoscaling/group.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The AutoScaling module contains the AutoScaling API resources 5 | module AutoScaling 6 | # The Group class exposes the AutoScaling::Group resources 7 | class Group < Base 8 | # AWS SDK for Ruby v2 Aws::AutoScaling::Client wrapper for 9 | # initializing a Group resource 10 | # @param group_name [String] The name of the Group 11 | # @param instance [Class] Aws::AutoScaling::Client instance 12 | # @raise [RuntimeError] if group_name.nil? 13 | # @raise [RuntimeError] if groups.length == 0 14 | # @raise [RuntimeError] if groups.length > 1 15 | def initialize(group_name, instance = nil) 16 | check_init_arg 'group_name', 'AutoScaling::Group', group_name 17 | @group_name = group_name 18 | @aws = instance.nil? ? Aws::AutoScaling::Client.new : instance 19 | get_group group_name 20 | end 21 | 22 | # Returns the String representation of AutoScaling::Group 23 | def to_s 24 | "AutoScaling Group: #{@group_name}" 25 | end 26 | 27 | # The name of the associated LaunchConfiguration 28 | # @return [String] 29 | def launch_configuration 30 | @group.launch_configuration_name 31 | end 32 | 33 | # The minimum size of the Group 34 | # @return [Integer] 35 | def min_size 36 | @group.min_size 37 | end 38 | 39 | # The maximum size of the Group 40 | # @return [Integer] 41 | def max_size 42 | @group.max_size 43 | end 44 | 45 | # The size of the Group 46 | # @return [Integer] 47 | def desired_capacity 48 | @group.desired_capacity 49 | end 50 | 51 | # The number of seconds after a scaling activity completes before any 52 | # further scaling activities can start 53 | # @return [Integer] 54 | def default_cooldown 55 | @group.default_cooldown 56 | end 57 | 58 | # One or more availability zones for the Group 59 | # @return [Array(String)] 60 | def availability_zones 61 | @group.availability_zones 62 | end 63 | 64 | # One or more LoadBalancers associated with the Group 65 | # @return [Array(String)] 66 | def load_balancer_names 67 | @group.load_balancer_names 68 | end 69 | 70 | # The service of interest for the health status check, which can be 71 | # either EC2 or ELB 72 | # @return [String] 73 | def health_check_type 74 | @group.health_check_type 75 | end 76 | 77 | # The amount of time that AutoScaling waits before checking an 78 | # instance's health status after being in service 79 | # @return [Integer] 80 | def health_check_grace_period 81 | @group.health_check_grace_period 82 | end 83 | 84 | # The EC2 Instances associated with the Group 85 | # @return [Array(Hash)] 86 | # @example group.instances #=> 87 | # [ 88 | # { 89 | # instance_id: 'The ID of the instance', 90 | # availability_zone: 'The AZ associated with this instance', 91 | # lifecycle_state: 'A description of current lifecycle state', 92 | # health_status: 'The health status of the instance', 93 | # launch_configuration_name: 'The associated launch config' 94 | # } 95 | # ] 96 | def instances 97 | @group.instances 98 | end 99 | 100 | # The number of EC2 Instances associated with the Group 101 | # @return [Integer] 102 | def instance_count 103 | @group.instances.length 104 | end 105 | 106 | # The suspended processes associated with the Group 107 | # @return [Array(Hash)] 108 | # @example group.suspended_processes #=> 109 | # [ 110 | # { 111 | # process_name: 'The name of the suspended process', 112 | # suspension_reason: 'The reason that the process was suspended' 113 | # } 114 | # ] 115 | def suspended_processes 116 | @group.suspended_processes 117 | end 118 | 119 | # The name of the placement group into which you'll launch your 120 | # Instances 121 | # @return [String] 122 | def placement_group 123 | @group.placement_group 124 | end 125 | 126 | # One or more Subnet IDs, if applicable 127 | # @return [Array(String)] 128 | def vpc_subnets 129 | @group.vpc_zone_identifier.split(',').map(&:strip) 130 | end 131 | 132 | # The metrics enabled for this Group 133 | # @return [Array(Hash)] 134 | def enabled_metrics 135 | @group.enabled_metrics 136 | end 137 | 138 | # The current state of the Group when a DeleteAutoScalingGroup action 139 | # is in progress 140 | # @return [String] 141 | def status 142 | @group.status 143 | end 144 | 145 | # The tags for the Group 146 | # @return [Array(Hash)] 147 | def tags 148 | @group.tags 149 | end 150 | 151 | # The termination policies for this Group 152 | # @return [Array(String)] 153 | def termination_policies 154 | @group.termination_policies 155 | end 156 | 157 | # The policies for the Group 158 | # @return [Array(Hash)] 159 | def scaling_policies 160 | @aws.describe_policies( 161 | auto_scaling_group_name: @group_name 162 | ).scaling_policies 163 | end 164 | 165 | private 166 | 167 | # @private 168 | def get_group(name) 169 | groups = @aws.describe_auto_scaling_groups( 170 | auto_scaling_group_names: [name] 171 | ).auto_scaling_groups 172 | check_length 'autoscaling groups', groups 173 | @group = groups[0] 174 | end 175 | end 176 | end 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /lib/resources/autoscaling/launch_configuration.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The AutoScaling module contains the AutoScaling API resources 5 | module AutoScaling 6 | # The LaunchConfiguration class exposes the 7 | # AutoScaling::LaunchConfiguration resources 8 | class LaunchConfiguration < Base 9 | # AWS SDK for Ruby v2 Aws::AutoScaling::Client wrapper for 10 | # initializing a LaunchConfiguration resource 11 | # @param config_name [String] The name of the LaunchConfiguration 12 | # @param instance [Class] Aws::AutoScaling::Client instance 13 | # @raise [RuntimeError] if config_name.nil? 14 | # @raise [RuntimeError] if configs.length == 0 15 | # @raise [RuntimeError] if configs.length > 1 16 | def initialize(config_name, instance = nil) 17 | check_init_arg( 18 | 'config_name', 19 | 'AutoScaling::LaunchConfiguration', 20 | config_name 21 | ) 22 | @config_name = config_name 23 | @aws = instance.nil? ? Aws::AutoScaling::Client.new : instance 24 | get_config config_name 25 | end 26 | 27 | # Returns the String representation of 28 | # AutoScaling::LaunchConfiguration 29 | def to_s 30 | "AutoScaling LaunchConfiguration: #{@config_name}" 31 | end 32 | 33 | # Check if the Instance monitoring is enabled 34 | def instance_monitored? 35 | @config.instance_monitoring.enabled 36 | end 37 | 38 | # Check if the Instance is optimized for EBS I/O 39 | def ebs_optimized? 40 | @config.ebs_optimized 41 | end 42 | 43 | # Check whether the EC2 Instances are associated with a public IP 44 | # address 45 | def with_public_ip_address? 46 | @config.associate_public_ip_address 47 | end 48 | 49 | # Check if the Instance tenancy is default 50 | def default_tenancy? 51 | @config.placement_tenancy == 'default' 52 | end 53 | 54 | # The ID of the AMI 55 | # @return [String] 56 | def image_id 57 | @config.image_id 58 | end 59 | 60 | # The name of the key pair 61 | # @return [String] 62 | def key_name 63 | @config.key_name 64 | end 65 | 66 | # The SecurityGroups to associate with the EC2 Instances 67 | # @return [Array(String)] 68 | def security_groups 69 | @config.security_groups 70 | end 71 | 72 | # The ID of a ClassicLink-enabled VPC to link your EC2-Classic 73 | # Instances to 74 | # @return [String] 75 | def classic_link_vpc_id 76 | @config.classic_link_vpc_id 77 | end 78 | 79 | # The IDs of one or more SecurityGroups for the VPC specified in 80 | # ClassicLinkVPCId 81 | # @return [Array(String)] 82 | def classic_link_vpc_security_groups 83 | @config.classic_link_vpc_security_groups 84 | end 85 | 86 | # The user data available to the EC2 Instances 87 | # @return [String] 88 | def user_data 89 | @config.user_data 90 | end 91 | 92 | # The Instance type for the EC2 Instances 93 | # @return [String] 94 | def instance_type 95 | @config.instance_type 96 | end 97 | 98 | # The ID of the kernel associated with the AMI 99 | # @return [String] 100 | def kernel_id 101 | @config.kernel_id 102 | end 103 | 104 | # The ID of the RAM disk associated with the AMI 105 | # @return [String] 106 | def ramdisk_id 107 | @config.ramdisk_id 108 | end 109 | 110 | # A block device mapping that specifies how block devices are exposed 111 | # to the Instance. See 112 | # {http://amzn.to/1Q6ohM1 AutoScaling#describe_launch_configurations} 113 | # @return [Array(Hash)] 114 | def block_device_mappings 115 | @config.block_device_mappings 116 | end 117 | 118 | # The name or ARN of the Instance profile associated with the IAM role 119 | # for the Instance 120 | # @return [String] 121 | def iam_instance_profile 122 | @config.iam_instance_profile 123 | end 124 | 125 | private 126 | 127 | # @private 128 | def get_config(name) 129 | cfgs = @aws.describe_launch_configurations( 130 | launch_configuration_names: [name] 131 | ).launch_configurations 132 | check_length 'launch configurations', cfgs 133 | @config = cfgs[0] 134 | end 135 | end 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /lib/resources/cloudformation/stack.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The CloudFormation module contains the AutoScaling API resources 5 | module CloudFormation 6 | # The Stack class exposes the CloudFormation::Stack resources 7 | class Stack < Base 8 | # AWS SDK for Ruby v2 Aws::CloudFormation::Client wrapper for 9 | # initializing a Stack resource 10 | # @param stack_name [String] The name of the Stack 11 | # @param instance [Class] Aws::CloudFormation::Client instance 12 | # @raise [RuntimeError] if stack_name.nil? 13 | # @raise [RuntimeError] if stacks.length == 0 14 | # @raise [RuntimeError] if stacks.length > 1 15 | def initialize(stack_name, instance = nil) 16 | check_init_arg 'stack_name', 'CloudFormation::Stack', stack_name 17 | @stack_name = stack_name 18 | @aws = instance.nil? ? Aws::CloudFormation::Client.new : instance 19 | get_stack stack_name 20 | end 21 | 22 | # Returns the String representation of CloudFormation::Stack 23 | # @return [String] 24 | def to_s 25 | "CloudFormation Stack: #{@stack_name}" 26 | end 27 | 28 | # Check whether the rollback on Stack creation failures is disabled 29 | def rollback_disabled? 30 | @stack.disable_rollback 31 | end 32 | 33 | # Check if the Stack is in CREATE_COMPLETE or UPDATE_COMPLETE status 34 | def ok? 35 | @stack.stack_status == 'CREATE_COMPLETE' || 36 | @stack.stack_status == 'UPDATE_COMPLETE' 37 | end 38 | 39 | # User defined description associated with the Stack 40 | # @return [String] 41 | def description 42 | @stack.description 43 | end 44 | 45 | # A list of Parameter structures 46 | # @return [Array(Hash)] 47 | def parameters 48 | @stack.parameters 49 | end 50 | 51 | # Current status of the Stack 52 | # @return [String] 53 | def stack_status 54 | @stack.stack_status 55 | end 56 | 57 | # SNS topic ARNs to which Stack related events are published 58 | # @return [Array(String)] 59 | def notification_arns 60 | @stack.notification_arns 61 | end 62 | 63 | # The amount of time within which Stack creation should complete 64 | # @return [Integer] 65 | def timeout_in_minutes 66 | @stack.timeout_in_minutes 67 | end 68 | 69 | # The capabilities allowed in the Stack 70 | # @return [Array(String)] 71 | def capabilities 72 | @stack.capabilities 73 | end 74 | 75 | # A list of output structures 76 | # @return [Array(Hash)] 77 | def outputs 78 | @stack.outputs 79 | end 80 | 81 | # A list of tags that specify cost allocation information for the 82 | # Stack 83 | # @return [Array(Hash)] 84 | def tags 85 | @stack.tags 86 | end 87 | 88 | private 89 | 90 | # @private 91 | def get_stack(name) 92 | stacks = @aws.describe_stacks(stack_name: name).stacks 93 | check_length 'stacks', stacks 94 | @stack = stacks[0] 95 | end 96 | end 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/resources/cloudwatch/alarm.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The CloudWatch module contains the CloudWatch API resources 5 | module CloudWatch 6 | # The Alarm class exposes the CloudWatch::Alarm resources 7 | class Alarm < Base 8 | # AWS SDK for Ruby v2 Aws::CloudWatch::Client wrapper for initializing 9 | # an Alarm resource 10 | # @param alarm_name [String] The name of the Alarm 11 | # @param instance [Class] Aws::CloudWatch::Client instance 12 | # @raise [RuntimeError] if alarm_name.nil? 13 | # @raise [RuntimeError] if alarms.length == 0 14 | # @raise [RuntimeError] if alarms.length > 1 15 | def initialize(alarm_name, instance = nil) 16 | check_init_arg 'alarm_name', 'CloudWatch::Alarm', alarm_name 17 | @alarm_name = alarm_name 18 | @aws = instance.nil? ? Aws::CloudWatch::Client.new : instance 19 | get_alarm alarm_name 20 | end 21 | 22 | # Returns the String representation of CloudWatch::Alarmm 23 | # @return [String] 24 | def to_s 25 | "CloudWatch Alarm: #{@alarm_name}" 26 | end 27 | 28 | # Checks if the Alarm state is OK 29 | def ok? 30 | @alarm.state_value == 'OK' 31 | end 32 | 33 | # Indicates whether actions should be executed during any changes to 34 | # the Alarm's state 35 | def actions_enabled? 36 | @alarm.actions_enabled 37 | end 38 | 39 | # The description for the Alarm 40 | # @return [String] 41 | def alarm_description 42 | @alarm.alarm_description 43 | end 44 | 45 | # The list of actions to execute when this Alarm transitions into an 46 | # OK state from any other state 47 | # @return [Array(String)] 48 | def ok_actions 49 | @alarm.ok_actions 50 | end 51 | 52 | # The list of actions to execute when this Alarm transitions into an 53 | # ALARM state from any other state 54 | # @return [Array(String)] 55 | def alarm_actions 56 | @alarm.alarm_actions 57 | end 58 | 59 | # The list of actions to execute when this Alarm transitions into an 60 | # INSUFFICIENT_DATA state from any other state 61 | # @return [Array(String)] 62 | def insufficient_data_actions 63 | @alarm.insufficient_data_actions 64 | end 65 | 66 | # The name of the Alarm's metric 67 | # @return [String] 68 | def metric_name 69 | @alarm.metric_name 70 | end 71 | 72 | # The namespace of Alarm's associated metric 73 | # @return [String] 74 | def namespace 75 | @alarm.namespace 76 | end 77 | 78 | # The statistic to apply to the Alarm's associated metric 79 | # @return [String] 80 | def statistic 81 | @alarm.statistic 82 | end 83 | 84 | # The list of dimensions associated with the Alarm's associated metric 85 | # @return [Array(Hash{Symbol => String})] 86 | # @example alarm.dimensions #=> 87 | # [ 88 | # { 89 | # name: 'The name of the dimension', 90 | # value: 'The value representing the dimension measurement' 91 | # } 92 | # ] 93 | def dimensions 94 | @alarm.dimensions 95 | end 96 | 97 | # The period in seconds over which the statistic is applied 98 | # @return [Integer] 99 | def period 100 | @alarm.period 101 | end 102 | 103 | # The number of periods over which data is compared to the specified 104 | # threshold 105 | # @return [Integer] 106 | def evaluation_periods 107 | @alarm.evaluation_periods 108 | end 109 | 110 | # The value against which the specified statistic is compared 111 | # @return [Number] 112 | def threshold 113 | @alarm.threshold 114 | end 115 | 116 | # The arithmetic operation to use when comparing the specified 117 | # Statistic and Threshold 118 | # @return [String] 119 | def comparison_operator 120 | @alarm.comparison_operator 121 | end 122 | 123 | private 124 | 125 | # @private 126 | def get_alarm(name) 127 | als = @aws.describe_alarms(alarm_names: [name]).metric_alarms 128 | check_length 'alarms', als 129 | @alarm = als[0] 130 | end 131 | end 132 | end 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/resources/common.rb: -------------------------------------------------------------------------------- 1 | # The Serverspec module contains all the Serverspec resources 2 | module Serverspec 3 | # The Type module contains the Serverspec types 4 | module Type 5 | # The AWS module contains all the AWS API resources 6 | module AWS 7 | # The Serverspec::Type::AWS::VERSION constant actually sets this library 8 | # version in the format: major.minor.patch.build 9 | VERSION = '0.1.2'.freeze 10 | 11 | # Check if the initialization argument of an AWS resource class is present 12 | # @param arg_name [String] - The name of the init argument 13 | # @param class_name [String] - The name of the AWS resource class 14 | # @param arg [String] - The arg passed to the class constructor 15 | # @raise [RuntimeError] if arg.nil? 16 | def check_init_arg(arg_name, class_name, arg) 17 | raise "Must specify #{arg_name} for #{class_name}" if arg.nil? 18 | end 19 | 20 | # Check the length for operations that should return only one resource 21 | # @param item_name [String] - The name of the item to check 22 | # @param item [Array] - The actual item for checking the length 23 | # @raise [RuntimeError] if item.length == 0 24 | # @raise [RuntimeError] if item.length > 1 25 | def check_length(item_name, item) 26 | return if item.length == 1 27 | 28 | if item.empty? 29 | raise "No #{item_name} with the specified name were"\ 30 | 'returned' 31 | else 32 | raise "Multiple #{item_name} with the same name "\ 33 | 'were returned' 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/resources/dynamodb/table.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The DynamoDB module contains the DynamoDB API resources 5 | module DynamoDB 6 | # The Table class exposes the DynamoDB::Table resources 7 | class Table < Base 8 | # AWS SDK for Ruby v2 Aws::DynamoDB::Client wrapper for initializing 9 | # a Table resource 10 | # @param tbl_name [String] The name of the Table 11 | # @param instance [Class] Aws::DynamoDB::Client instance 12 | # @raise [RuntimeError] if tbl_name.nil? 13 | # @raise [ResourceNotFoundException] if table.nil? 14 | def initialize(tbl_name, instance = nil) 15 | check_init_arg 'tbl_name', 'DynamoDB::Table', tbl_name 16 | @tbl_name = tbl_name 17 | @aws = instance.nil? ? Aws::DynamoDB::Client.new : instance 18 | get_table tbl_name 19 | 20 | @type_map = { 21 | 'S' => :string, 22 | 'N' => :number, 23 | 'B' => :binary 24 | } 25 | end 26 | 27 | # Returns the String representation of DynamoDB::Table 28 | # @return [String] 29 | def to_s 30 | "DynamoDB Table: #{@tbl_name}" 31 | end 32 | 33 | # Returns true if the Table is active 34 | def valid? 35 | @table.table_status == 'ACTIVE' 36 | end 37 | 38 | # Returns true if the Table has a hash key 39 | def with_hash_key? 40 | key_type?('hash') 41 | end 42 | 43 | # Returns true if the Table has a range key 44 | def with_range_key? 45 | key_type?('range') 46 | end 47 | 48 | # Returns true if there is a local secondary index 49 | def local_secondary_indexed? 50 | @table.local_secondary_indexes.is_a?(Array) 51 | end 52 | 53 | # Returns true if there is a global secondary index 54 | def global_secondary_indexed? 55 | @table.global_secondary_indexes.is_a?(Array) 56 | end 57 | 58 | # An array of AttributeDefinition objects. Each of these objects 59 | # describes one attribute in the Table and index key schema 60 | # @return [Array(Hash)] 61 | def attribute_definitions 62 | @table.attribute_definitions 63 | end 64 | 65 | # The primary key structure for the Table 66 | # @return [Array(Hash)] 67 | def key_schema 68 | @table.key_schema 69 | end 70 | 71 | # The maximum number of strongly consistent reads consumed per second 72 | # before DynamoDB returns a ThrottlingException 73 | # @return [Integer] 74 | def read_capacity 75 | @table.provisioned_throughput.read_capacity_units 76 | end 77 | 78 | # The maximum number of writes consumed per second before DynamoDB 79 | # returns a ThrottlingException 80 | # @return [Integer] 81 | def write_capacity 82 | @table.provisioned_throughput.write_capacity_units 83 | end 84 | 85 | # Represents one or more local secondary indexes on the Table. Each 86 | # index is scoped to a given hash key value 87 | # @return [Array(Hash)] 88 | def local_secondary_indexes 89 | @table.local_secondary_indexes 90 | end 91 | 92 | # The global secondary indexes, if any, on the Table. Each index is 93 | # scoped to a given hash key value 94 | # @return [Array(Hash)] 95 | def global_secondary_indexes 96 | @table.global_secondary_indexes 97 | end 98 | 99 | # The name of the hash key from the key_schema 100 | # @return [String] 101 | def hash_key_name 102 | @table.key_schema.each do |key| 103 | return key.attribute_name if key.key_type == 'HASH' 104 | end 105 | end 106 | 107 | # The type of the hash key from the key_schema. Valid return values: 108 | # :string, :number, or :binary 109 | # @return [Symbol] 110 | def hash_key_type 111 | @table.attribute_definitions.each do |attr| 112 | if attr.attribute_name == hash_key_name 113 | return @type_map[ 114 | attr.attribute_type 115 | ] 116 | end 117 | end 118 | end 119 | 120 | # The name of the range key from the key_schema 121 | # @return [String] 122 | def range_key_name 123 | @table.key_schema.each do |key| 124 | return key.attribute_name if key.key_type == 'RANGE' 125 | end 126 | end 127 | 128 | # The type of the range key from the key_schema. Valid return values: 129 | # :string, :number, or :binary 130 | # @return [Symbol] 131 | def range_key_type 132 | @table.attribute_definitions.each do |attr| 133 | if attr.attribute_name == range_key_name 134 | return @type_map[ 135 | attr.attribute_type 136 | ] 137 | end 138 | end 139 | end 140 | 141 | private 142 | 143 | # @private 144 | def get_table(name) 145 | @table = @aws.describe_table(table_name: name).table 146 | end 147 | 148 | # @private 149 | def key_type?(type) 150 | @table.key_schema.each do |key| 151 | return true if key.key_type.casecmp(type) 152 | end 153 | false 154 | end 155 | end 156 | end 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /lib/resources/ec2/image.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The Instance class exposes the EC2::Image resources 7 | class Image < Base 8 | # The ID of the Instance 9 | attr_reader :instance_id 10 | # The Name tag of the Instance (if available) 11 | attr_reader :image_name 12 | # The ID of the AMI 13 | attr_reader :image_id 14 | 15 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing an 16 | # Instance resource 17 | # @param image_id_name [Array] The ID or Name tag of the Image 18 | # @param instance [Class] Aws::EC2::Client instance 19 | # @raise [RuntimeError] if image_id_name.nil? 20 | # @raise [RuntimeError] if image_id_name.length == 0 21 | # @raise [RuntimeError] if image_id_name.length > 1 22 | def initialize(image_id_name, instance = nil) 23 | check_init_arg 'image_id_name', 'EC2::image', image_id_name 24 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 25 | if image_id_name.match(/^ami-[A-Fa-f0-9]{8,17}$/).nil? 26 | @image_name = image_id_name 27 | get_image_by_name image_id_name 28 | else 29 | @image_id = image_id_name 30 | get_image_by_id image_id_name 31 | end 32 | end 33 | 34 | # Returns the string representation of EC2::Instance 35 | # @return [String] 36 | def to_s 37 | return "EC2 Image ID: #{@image_id}" if @image_name.nil? 38 | "EC2 Image ID: #{@image_id}; Name: #{@image_name}" 39 | end 40 | 41 | def root_volume 42 | root_vol = @image.block_device_mappings.first do |vol| 43 | vol.device_name == @image.root_device_name 44 | end 45 | Volume.new(root_vol, @image.root_device_type) 46 | end 47 | 48 | private 49 | 50 | # @private 51 | def get_image_by_id(id) 52 | results = @aws.describe_images(image_ids: [id]) 53 | @image = results.images[0] 54 | @image_name = @image.name 55 | end 56 | 57 | # @private 58 | def get_image_by_name(name) 59 | @image = @aws.describe_images( 60 | filters: [ 61 | { name: 'name', values: [name] }, 62 | { name: 'state', values: ['available'] } 63 | ] 64 | ).images[0] 65 | @image_id = @image.image_id 66 | @image_name = @image.name 67 | end 68 | end 69 | 70 | # The Volume class exposes the image root volume 71 | # as Aws::EC2::Types::BlockDeviceMapping 72 | class Volume < Base 73 | def initialize(vol, volume_type) 74 | @volume = vol 75 | @type = volume_type 76 | end 77 | 78 | def encrypted? 79 | @volume.ebs.encrypted 80 | end 81 | 82 | def snapshot_id 83 | @volume.ebs.snapshot_id 84 | end 85 | 86 | def device_name 87 | @volume.device_name 88 | end 89 | 90 | attr_reader :type 91 | 92 | # Returns the string representation of EC2::RootVolume 93 | # @return [String] 94 | def to_s 95 | "EC2 Image Root Volume: #{@volume.ebs.snapshot_id}" 96 | end 97 | end 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/resources/ec2/instance.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The Instance class exposes the EC2::Instance resources 7 | class Instance < Base # rubocop:disable ClassLength 8 | # The ID of the Instance 9 | attr_reader :instance_id 10 | # The Name tag of the Instance (if available) 11 | attr_reader :instance_name 12 | 13 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing an 14 | # Instance resource 15 | # @param instance_id_name [String] The ID or Name tag of the Instance 16 | # @param instance [Class] Aws::EC2::Client instance 17 | # @raise [RuntimeError] if instance_id_name.nil? 18 | # @raise [RuntimeError] if instance_id_name.length == 0 19 | # @raise [RuntimeError] if instance_id_name.length > 1 20 | def initialize(instance_id_name, instance = nil) 21 | check_init_arg 'instance_id_name', 'EC2::instance', instance_id_name 22 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 23 | if instance_id_name.match(/^i-[A-Fa-f0-9]{8}$/).nil? 24 | @instance_name = instance_id_name 25 | get_instance_by_name instance_id_name 26 | else 27 | @instance_id = instance_id_name 28 | get_instance_by_id instance_id_name 29 | end 30 | end 31 | 32 | # Returns the string representation of EC2::Instance 33 | # @return [String] 34 | def to_s 35 | return "EC2 Instance ID: #{@instance_id}" if @instance_name.nil? 36 | "EC2 Instance ID: #{@instance_id}; Name: #{@instance_name}" 37 | end 38 | 39 | # Returns true if the Instance state is 'running' 40 | def running? 41 | @instance.state.name == 'running' 42 | end 43 | 44 | # Indicates whether the monitoring is enabled for the Instance 45 | def monitoring_enabled? 46 | @instance.monitoring.state == 'enabled' 47 | end 48 | 49 | # Returns true if the platform is Windows 50 | def on_windows? 51 | @instance.platform.casecmp('windows').zero? 52 | end 53 | 54 | # Specifies whether the Instance launched in a VPC is able to perform 55 | # NAT. The value must be false for the Instance to perform NAT 56 | def source_dest_checked? 57 | @instance.source_dest_check 58 | end 59 | 60 | # Specifies whether the Instance is optimized for EBS I/O 61 | def ebs_optimized? 62 | @instance.ebs_optimized 63 | end 64 | 65 | # Specifies whether enhanced networking is enabled 66 | def enhanced_networked? 67 | @instance.sriov_net_support == 'simple' 68 | end 69 | 70 | # The ID of the AMI used to launch the Instance 71 | # @return [String] 72 | def image_id 73 | @instance.image_id 74 | end 75 | 76 | # The private DNS name assigned to the Instance 77 | # @return [String] 78 | def private_dns_name 79 | @instance.private_dns_name 80 | end 81 | 82 | # The public DNS name assigned to the Instance 83 | # @return [String] 84 | def public_dns_name 85 | @instance.public_dns_name 86 | end 87 | 88 | # The name of the key pair 89 | # @return [String] 90 | def key_name 91 | @instance.key_name 92 | end 93 | 94 | # The Instance type 95 | # @return [String] 96 | def instance_type 97 | @instance.instance_type 98 | end 99 | 100 | # The location where the Instance launched. Indicates the availability 101 | # zone, the placement group, and the Instance tenancy 102 | # @return [Hash] 103 | def placement 104 | @instance.placement 105 | end 106 | 107 | # The kernel associtated to this Instance 108 | # @return [String] 109 | def kernel_id 110 | @instance.kernel_id 111 | end 112 | 113 | # The RAM disk associtated to this Instance 114 | # @return [String] 115 | def ramdisk_id 116 | @instance.ramdisk_id 117 | end 118 | 119 | # The ID of the subnet in which the Instance is running 120 | # @return [String] 121 | def subnet_id 122 | @instance.subnet_id 123 | end 124 | 125 | # The ID of the VPC in which the Instance is running 126 | # @return [String] 127 | def vpc_id 128 | @instance.vpc_id 129 | end 130 | 131 | # The private IP address assigned to the Instance 132 | # @return [String] 133 | def private_ip_address 134 | @instance.private_ip_address 135 | end 136 | 137 | # The public IP address assigned to the Instance 138 | # @return [String] 139 | def public_ip_address 140 | @instance.public_ip_address 141 | end 142 | 143 | # The architecture of the image 144 | # @return [String] 145 | def architecture 146 | @instance.architecture 147 | end 148 | 149 | # The root device type used by the AMI. It can be an EBS volume or 150 | # Instance store 151 | # @return [String] 152 | def root_device_type 153 | @instance.root_device_type 154 | end 155 | 156 | # The root device name (eg: /dev/sda1, /dev/xvda1) 157 | # @return [String] 158 | def root_device_name 159 | @instance.root_device_name 160 | end 161 | 162 | # Any block device mappings for the Instance 163 | # @return [Array(Hash)] 164 | def block_device_mappings 165 | @instance.block_device_mappings 166 | end 167 | 168 | # The virtualization type of the Instance, whether is HVM or PV 169 | # @return [String] 170 | def virtualization_type 171 | @instance.virtualization_type 172 | end 173 | 174 | # Indicates whether this is a spot Instance 175 | # @return [String] 176 | def instance_lifecycle 177 | @instance.instance_lifecycle 178 | end 179 | 180 | # Any tags assigned to the Instance 181 | # @return [Array(Hash)] 182 | def tags 183 | @instance.tags 184 | end 185 | 186 | # One or more SecurityGroups for the Instance 187 | # @return [Array(Hash)] 188 | def security_groups 189 | @instance.security_groups 190 | end 191 | 192 | # The hypervisor type of the Instance 193 | # @return [String] 194 | def hypervisor 195 | @instance.hypervisor 196 | end 197 | 198 | # One or more NetworkInterfaces for the Instance 199 | # @return [Array(Hash)] 200 | def network_interfaces 201 | @instance.network_interfaces 202 | end 203 | 204 | # The IAM Instance profile associtated with the Instance 205 | # @return [Hash] 206 | def iam_instance_profile 207 | @instance.iam_instance_profile 208 | end 209 | 210 | private 211 | 212 | # @private 213 | def get_instance_by_id(id) 214 | res = @aws.describe_instances(instance_ids: [id]).reservations 215 | check_res 'id', res 216 | @instance = res[0].instances[0] 217 | @instance.tags.each do |tag| 218 | @instance_name = tag.value if tag.key == 'Name' 219 | end 220 | end 221 | 222 | # @private 223 | def get_instance_by_name(name) 224 | res = @aws.describe_instances( 225 | filters: [ 226 | { name: 'tag-key', values: ['Name'] }, 227 | { name: 'tag-value', values: [name] }, 228 | { name: 'instance-state-name', values: ['running'] } 229 | ] 230 | ).reservations 231 | check_res 'name', res 232 | @instance = res[0].instances[0] 233 | @instance_id = @instance.instance_id 234 | end 235 | 236 | # @private 237 | def check_res(type, res) 238 | check_length 'reservations', res 239 | check_length "instances by #{type}", res[0].instances 240 | end 241 | end 242 | end 243 | end 244 | end 245 | end 246 | -------------------------------------------------------------------------------- /lib/resources/ec2/internet_gateway.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The InternetGateway class exposes the EC2::InternetGateway resources 7 | class InternetGateway < Base 8 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing an 9 | # InternetGateway resource 10 | # @param igw_id [String] The ID of the InternetGateway 11 | # @param instance [Class] Aws::EC2::Client instance 12 | # @raise [RuntimeError] if igws.nil? 13 | # @raise [RuntimeError] if igws.length == 0 14 | # @raise [RuntimeError] if igws.length > 1 15 | def initialize(igw_id, instance = nil) 16 | check_init_arg 'igw_id', 'EC2::InternetGateway', igw_id 17 | @igw_id = igw_id 18 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 19 | get_igw igw_id 20 | end 21 | 22 | # Returns the string representation of EC2::InternetGateway 23 | # @return [String] 24 | def to_s 25 | "EC2 InternetGateway: #{@igw_id}" 26 | end 27 | 28 | # Returns whether the state of the attachment is available 29 | def available? 30 | @igw.attachments[0].state == 'available' 31 | end 32 | 33 | # The ID of the VPC attached to the InternetGateway 34 | # @return [String] 35 | def vpc_id 36 | @igw.attachments[0].vpc_id 37 | end 38 | 39 | # Any VPCs attached to the InternetGateway 40 | # @return [Array(Hash)] 41 | def attachments 42 | # usually you shouldn't use this as attachments is returned as 43 | # Array, but at least the AWS console doesn't allow more than one 44 | # VPC per InternetGateway 45 | @igw.attachments 46 | end 47 | 48 | # Any tags assigned to the InternetGateway 49 | # @return [Array(Hash)] 50 | def tags 51 | @igw.tags 52 | end 53 | 54 | private 55 | 56 | # @private 57 | def get_igw(id) 58 | igws = @aws.describe_internet_gateways( 59 | internet_gateway_ids: [id] 60 | ).internet_gateways 61 | check_length 'internet gateways', igws 62 | @igw = igws[0] 63 | end 64 | end 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/resources/ec2/network_interface.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The NetworkInterface class exposes the EC2::NetworkInterface resources 7 | class NetworkInterface < Base 8 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing a 9 | # NetworkInterface resource 10 | # @param ni_id [String] The ID of the NetworkInterface 11 | # @param instance [Class] Aws::EC2::Client instance 12 | # @raise [RuntimeError] if nis.nil? 13 | # @raise [RuntimeError] if nis.length == 0 14 | # @raise [RuntimeError] if nis.length > 1 15 | def initialize(ni_id, instance = nil) 16 | check_init_arg 'ni_id', 'EC2::NetworkInterface', ni_id 17 | @ni_id = ni_id 18 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 19 | get_ni ni_id 20 | end 21 | 22 | # Returns the string representation of EC2::NetworkInterface 23 | # @return [String] 24 | def to_s 25 | "EC2 NetworkInterface: #{@ni_id}" 26 | end 27 | 28 | # Returns whether the NetworkInterface is attached 29 | def attached? 30 | @ni.attachment.status == 'attached' 31 | end 32 | 33 | # Indicates whether the NetworkInterface is being managed by AWS 34 | def requester_managed? 35 | @ni.requester_managed 36 | end 37 | 38 | # Indicates whether the status is in-use 39 | def in_use? 40 | @ni.status == 'in-use' 41 | end 42 | 43 | # Indicates whether traffic to or from the Instance is validated 44 | def source_dest_checked? 45 | @ni.source_dest_check 46 | end 47 | 48 | # The ID of the Subnet 49 | # @return [String] 50 | def subnet_id 51 | @ni.subnet_id 52 | end 53 | 54 | # The ID of the VPC 55 | # @return [String] 56 | def vpc_id 57 | @ni.vpc_id 58 | end 59 | 60 | # The availability zone 61 | # @return [String] 62 | def availability_zone 63 | @ni.availability_zone 64 | end 65 | 66 | # The description 67 | # @return [String] 68 | def description 69 | @ni.description 70 | end 71 | 72 | # The AWS account ID of the owner of the NetworkInterface 73 | # @return [String] 74 | def owner_id 75 | @ni.owner_id 76 | end 77 | 78 | # The ID of the entity that launched the instance on your behalf (eg: 79 | # AWS Management Console, Auto Scaling) 80 | # @return [String] 81 | def requester_id 82 | @ni.requester_id 83 | end 84 | 85 | # The status 86 | # @return [String] 87 | def status 88 | @ni.status 89 | end 90 | 91 | # The MAC address 92 | # @return [String] 93 | def mac_address 94 | @ni.mac_address 95 | end 96 | 97 | # The private IP address within the Subnet 98 | # @return [String] 99 | def private_ip_address 100 | @ni.private_ip_address 101 | end 102 | 103 | # The private DNS name 104 | # @return [String] 105 | def private_dns_name 106 | @ni.private_dns_name 107 | end 108 | 109 | # Any SecurityGroups for the NetworkInterface 110 | # @return [Array(Hash)] 111 | def groups 112 | @ni.groups 113 | end 114 | 115 | # The NetworkInterface attachment 116 | # @return [Hash] 117 | def attachment 118 | @ni.attachment 119 | end 120 | 121 | # The association information for an Elastic IP associated to the 122 | # NetworkInterface 123 | # @return [Hash] 124 | def association 125 | @ni.association 126 | end 127 | 128 | # Any tags associated to the NetworkInterface 129 | # @return [Array(Hash)] 130 | def tags 131 | @ni.tag_set 132 | end 133 | 134 | # The private IP addresses associated with the NetworkInterface 135 | # @return [Array(Hash)] 136 | def private_ip_addresses 137 | @ni.private_ip_addresses 138 | end 139 | 140 | private 141 | 142 | # @private 143 | def get_ni(id) 144 | nis = @aws.describe_network_interfaces( 145 | network_interface_ids: [id] 146 | ).network_interfaces 147 | check_length 'network interfaces', nis 148 | @ni = nis[0] 149 | end 150 | end 151 | end 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /lib/resources/ec2/route_table.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The RouteTable class exposes the EC2::RouteTable resources 7 | class RouteTable < Base 8 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing a 9 | # RouteTable resource 10 | # @param rtb_id [String] The ID of the RouteTable 11 | # @param instance [Class] Aws::EC2::Client instance 12 | # @raise [RuntimeError] if rtbs.nil? 13 | # @raise [RuntimeError] if rtbs.length == 0 14 | # @raise [RuntimeError] if rtbs.length > 1 15 | def initialize(rtb_id, instance = nil) 16 | check_init_arg 'rtb_id', 'EC2::RouteTable', rtb_id 17 | @rtb_id = rtb_id 18 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 19 | get_rtb rtb_id 20 | end 21 | 22 | # Returns the string representation of EC2::RouteTable 23 | # @return [String] 24 | def to_s 25 | "EC2 RouteTable: #{@rtb_id}" 26 | end 27 | 28 | # The ID of the VPC 29 | # @return [String] 30 | def vpc_id 31 | @rtb.vpc_id 32 | end 33 | 34 | # The routes in the route table 35 | # @return [Array(Hash)] 36 | def routes 37 | @rtb.routes 38 | end 39 | 40 | # The associations between the route table and one or more subnets 41 | # @return [Array(Hash)] 42 | def associations 43 | @rtb.associations 44 | end 45 | 46 | # Any tags assigned to the route table 47 | # @return [Array(Hash)] 48 | def tags 49 | @rtb.tags 50 | end 51 | 52 | # Any virtual private gateway (VGW) propagating routes 53 | # @return [Array(Hash)] 54 | def propagating_vgws 55 | @rtb.propagating_vgws 56 | end 57 | 58 | private 59 | 60 | # @private 61 | def get_rtb(id) 62 | rtbs = @aws.describe_route_tables( 63 | route_table_ids: [id] 64 | ).route_tables 65 | check_length 'route tables', rtbs 66 | @rtb = rtbs[0] 67 | end 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/resources/ec2/security_group.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The SecurityGroup class exposes the EC2::SecurityGroup resources 7 | class SecurityGroup < Base 8 | require 'netaddr' 9 | 10 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing a 11 | # SecurityGroup resource 12 | # @param sg_id [String] The ID of the SecurityGroup 13 | # @param instance [Class] Aws::EC2::Client instance 14 | # @raise [RuntimeError] if sgs.nil? 15 | # @raise [RuntimeError] if sgs.length == 0 16 | # @raise [RuntimeError] if sgs.length > 1 17 | def initialize(sg_id, instance = nil) 18 | check_init_arg 'sg_id', 'EC2::SecurityGroup', sg_id 19 | @sg_id = sg_id 20 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 21 | get_security_group sg_id 22 | end 23 | 24 | # Returns the string representation of EC2::SecurityGroup 25 | # @return [String] 26 | def to_s 27 | "EC2 SecurityGroup: #{@sg_id}" 28 | end 29 | 30 | # The AWS account ID of the owner of the security group 31 | # @return [String] 32 | def owner_id 33 | @sg.owner_id 34 | end 35 | 36 | # The name of the security group 37 | # @return [String] 38 | def group_name 39 | @sg.group_name 40 | end 41 | 42 | # A description of the security group 43 | # @return [String] 44 | def description 45 | @sg.description 46 | end 47 | 48 | # One or more inbound rules associated with the security group 49 | # @return [Array(Hash)] 50 | def ingress_permissions 51 | @sg.ip_permissions 52 | end 53 | 54 | # [EC2-VPC] One or more outbound rules associated with the security 55 | # group 56 | # @return [Array(Hash)] 57 | def egress_permissions 58 | @sg.ip_permissions_egress 59 | end 60 | 61 | # [EC2-VPC] The ID of the VPC for the security group 62 | # @return [String] 63 | def vpc_id 64 | @sg.vpc_id 65 | end 66 | 67 | # Any tags assigned to the security group 68 | # @return [Array(Hash)] 69 | def tags 70 | @sg.tags 71 | end 72 | 73 | # Do the security group rules permit connections from the given 74 | # CIDR range? 75 | # Returns true iff there is an ingress rule with a source that 76 | # contains the given CIDR range. 77 | # @param cidr_s [String] The CIDR range to test 78 | # @return [Boolean] True if this SG allows access from the given CIDR 79 | def accessible_from?(cidr_s) 80 | return false if ingress_permissions.empty? 81 | 82 | cidr = NetAddr::CIDR.create(cidr_s) 83 | allowed_cidrs = ingress_permissions.map(&:ip_ranges) 84 | .flatten.map(&:cidr_ip) 85 | matching_rules = allowed_cidrs.map do |source_cidr| 86 | cidr == source_cidr || cidr.is_contained?(source_cidr) 87 | end 88 | matching_rules.include? true 89 | end 90 | 91 | private 92 | 93 | # @private 94 | def get_security_group(id) 95 | sgs = @aws.describe_security_groups(group_ids: [id]).security_groups 96 | check_length 'security groups', sgs 97 | @sg = sgs[0] 98 | end 99 | end 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/resources/ec2/subnet.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The Subnet class exposes the EC2::Subnet resources 7 | class Subnet < Base 8 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing a 9 | # Subnet resource 10 | # @param subnet_id [String] The ID of the Subnet 11 | # @param instance [Class] Aws::EC2::Client instance 12 | # @raise [RuntimeError] if subnets.nil? 13 | # @raise [RuntimeError] if subnets.length == 0 14 | # @raise [RuntimeError] if subnets.length > 1 15 | def initialize(subnet_id, instance = nil) 16 | check_init_arg 'subnet_id', 'EC2::Subnet', subnet_id 17 | @subnet_id = subnet_id 18 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 19 | get_subnet subnet_id 20 | end 21 | 22 | # Returns the string representation of EC2::Subnet 23 | # @return [String] 24 | def to_s 25 | "EC2 Subnet: #{@subnet_id}" 26 | end 27 | 28 | # Indicates whether the state is available 29 | def available? 30 | @subnet.state == 'available' 31 | end 32 | 33 | # Indicates whether this is the default subnet for the Availability 34 | # Zone 35 | def az_default? 36 | @subnet.default_for_az 37 | end 38 | 39 | # Indicates whether instances launched in this subnet receive a public 40 | # IP address 41 | def with_public_ip_on_launch? 42 | @subnet.map_public_ip_on_launch 43 | end 44 | 45 | # The ID of the VPC the subnet is in 46 | # @return [String] 47 | def vpc_id 48 | @subnet.vpc_id 49 | end 50 | 51 | # The CIDR block assigned to the subnet 52 | # @return [String] 53 | def cidr_block 54 | @subnet.cidr_block 55 | end 56 | 57 | # The number of unused IP addresses in the subnet. Note that the IP 58 | # addresses for any stopped instances are considered unavailable 59 | # @return [Integer] 60 | def available_ip_address_count 61 | @subnet.available_ip_address_count 62 | end 63 | 64 | # The Availability Zone of the subnet 65 | # @return [String] 66 | def availability_zone 67 | @subnet.availability_zone 68 | end 69 | 70 | # Any tags assigned to the subnet 71 | # @return [Array(Hash)] 72 | def tags 73 | @subnet.tags 74 | end 75 | 76 | private 77 | 78 | # @private 79 | def get_subnet(id) 80 | snets = @aws.describe_subnets(subnet_ids: [id]).subnets 81 | check_length 'subnets', snets 82 | @subnet = snets[0] 83 | end 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/resources/ec2/subnets.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | # Inspired by https://github.com/stelligent/serverspec-aws-resources 4 | 5 | module Serverspec 6 | module Type 7 | module AWS 8 | # The EC2 module contains the EC2 API resources 9 | module EC2 10 | # The Subnets class provides serverspec expectations for a collection 11 | # of EC2::Subnet resources 12 | class Subnets < Base 13 | include Enumerable 14 | extend Forwardable 15 | 16 | def_delegators :@subnets, :each, :<< 17 | 18 | def initialize(subnets) 19 | @subnets = subnets 20 | end 21 | 22 | def evenly_spread_across_minimum_az?(num_azs) 23 | subnet_grouping_by_az = @subnets.group_by(&:availability_zone) 24 | return false if 25 | number_of_sizes_in_sub_arrays(subnet_grouping_by_az) != 1 26 | 27 | return false if 28 | subnet_grouping_by_az.size < num_azs 29 | 30 | true 31 | end 32 | 33 | # Returns the string representation of EC2::Subnet 34 | # @return [String] 35 | def to_s 36 | "EC2 Subnets: #{@subnets.map(&:id)}" 37 | end 38 | 39 | private 40 | 41 | def number_of_sizes_in_sub_arrays(arr) 42 | arr.map(&:size).uniq.size 43 | end 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/resources/ec2/vpc.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The EC2 module contains the EC2 API resources 5 | module EC2 6 | # The VPC class exposes the EC2::VPC resources 7 | class VPC < Base 8 | # AWS SDK for Ruby v2 Aws::EC2::Client wrapper for initializing a 9 | # VPC resource 10 | # @param vpc_id [String] The ID of the VPC 11 | # @param instance [Class] Aws::EC2::Client instance 12 | # @raise [RuntimeError] if vpcs.nil? 13 | # @raise [RuntimeError] if vpcs.length == 0 14 | # @raise [RuntimeError] if vpcs.length > 1 15 | def initialize(vpc_id, instance = nil) 16 | check_init_arg 'vpc_id', 'EC2::VPC', vpc_id 17 | @vpc_id = vpc_id 18 | @aws = instance.nil? ? Aws::EC2::Client.new : instance 19 | get_vpc vpc_id 20 | end 21 | 22 | # Returns the string representation of EC2::VPC 23 | # @return [String] 24 | def to_s 25 | "EC2 VPC: #{@vpc_id}" 26 | end 27 | 28 | # Indicates whether the state is available 29 | def available? 30 | @vpc.state == 'available' 31 | end 32 | 33 | # Indicates whether the VPC is the default VPC 34 | def default? 35 | @vpc.is_default 36 | end 37 | 38 | # Indicates whether the instance_tenancy is default 39 | def default_tenancy? 40 | @vpc.instance_tenancy == 'default' 41 | end 42 | 43 | # The CIDR block for the VPC 44 | # @return [String] 45 | def cidr_block 46 | @vpc.cidr_block 47 | end 48 | 49 | # The ID of the set of DHCP options you've associated with the VPC 50 | # (or default if the default options are associated with the VPC) 51 | # @return [String] 52 | def dhcp_options_id 53 | @vpc.dhcp_options_id 54 | end 55 | 56 | # Any tags assigned to the VPC 57 | # @return [Array(Hash)] 58 | def tags 59 | @vpc.tags 60 | end 61 | 62 | # Get the subnets associated with the VPC 63 | def subnets 64 | Subnets.new Aws::EC2::Vpc.new(@vpc_id, client: @aws).subnets 65 | end 66 | 67 | private 68 | 69 | # @private 70 | def get_vpc(id) 71 | vpcs = @aws.describe_vpcs(vpc_ids: [id]).vpcs 72 | check_length 'vpcs', vpcs 73 | @vpc = vpcs[0] 74 | end 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/resources/elasticloadbalancing/load_balancer.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The ElasticLoadBalancing module contains the ElasticLoadBalancing API 5 | # resources 6 | module ElasticLoadBalancing 7 | # The LoadBalancer class exposes the ElasticLoadBalancing::LoadBalancer 8 | # resources 9 | class LoadBalancer < Base 10 | # AWS SDK for Ruby v2 Aws::ElasticLoadBalancing::LoadBalancer wrapper 11 | # for initializing a LoadBalancer resource 12 | # @param elb_name [String] The name of the LoadBalancer 13 | # rubocop:disable LineLength 14 | # @param instance [Class] Aws::ElasticLoadBalancing::LoadBalancer instance 15 | # rubocop:enable LineLength 16 | # @raise [RuntimeError] if elbs.nil? 17 | # @raise [RuntimeError] if elbs.length == 0 18 | # @raise [RuntimeError] if elbs.length > 1 19 | def initialize(elb_name, instance = nil) 20 | check_init_arg( 21 | 'elb_name', 22 | 'ElasticLoadBalancing::LoadBalancer', 23 | elb_name 24 | ) 25 | @elb_name = elb_name 26 | get_instance instance 27 | get_elb elb_name 28 | end 29 | 30 | # Returns the string representation of 31 | # ElasticLoadBalancing::LoadBalancer 32 | # @return [String] 33 | def to_s 34 | "ElasticLoadBalancing LoadBalancer: #{@elb_name}" 35 | end 36 | 37 | # Indicates whether the scheme is internal 38 | def internal? 39 | @elb.scheme == 'internal' 40 | end 41 | 42 | # Indicates whether the scheme is internet-facing 43 | def internet_facing? 44 | @elb.scheme == 'internet-facing' 45 | end 46 | 47 | # The external DNS name of the load balancer 48 | # @return [String] 49 | def dns_name 50 | @elb.dns_name 51 | end 52 | 53 | # The Amazon Route 53 hosted zone associated with the load balancer 54 | # @return [String] 55 | def canonical_hosted_zone_name 56 | @elb.canonical_hosted_zone_name 57 | end 58 | 59 | # The ID of the Amazon Route 53 hosted zone name associated with the 60 | # load balancer 61 | # @return [String] 62 | def canonical_hosted_zone_name_id 63 | @elb.canonical_hosted_zone_name_id 64 | end 65 | 66 | # The listeners for the load balancer 67 | # @return [Array(Hash)] 68 | def listeners 69 | @elb.listener_descriptions 70 | end 71 | 72 | # The policies defined for the load balancer 73 | # @return [Hash] 74 | def policies 75 | @elb.policies 76 | end 77 | 78 | # Information about the back-end servers 79 | # @return [Array(Hash)] 80 | def backend_server_descriptions 81 | @elb.backend_server_descriptions 82 | end 83 | 84 | # The Availability Zones for the load balancer 85 | # @return [Array(String)] 86 | def availability_zones 87 | @elb.availability_zones 88 | end 89 | 90 | # The IDs of the subnets for the load balancer 91 | # @return [Array(String)] 92 | def subnets 93 | @elb.subnets 94 | end 95 | 96 | # The ID of the VPC for the load balancer 97 | # @return [String] 98 | def vpc_id 99 | @elb.vpc_id 100 | end 101 | 102 | # The IDs of the instances for the load balancer 103 | # @return [Array(Hash)] 104 | def instances 105 | instances = [] 106 | @elb.instances.each do |inst| 107 | instances << inst.instance_id 108 | end 109 | instances 110 | end 111 | 112 | # Information about the health checks conducted on the load balancer 113 | # @return [Hash] 114 | def health_check 115 | @elb.health_check 116 | end 117 | 118 | # The security group that you can use as part of your inbound rules 119 | # for your load balancer's back-end application instances. To only 120 | # allow traffic from load balancers, add a security group rule to your 121 | # back end instance that specifies this source security group as the 122 | # inbound source 123 | # @return [Hash] 124 | def source_security_group 125 | @elb.source_security_group 126 | end 127 | 128 | # The security groups for the load balancer. Valid only for load 129 | # balancers in a VPC 130 | # @return [Array(String)] 131 | def security_groups 132 | @elb.security_groups 133 | end 134 | 135 | private 136 | 137 | # @private 138 | def get_elb(name) 139 | elbs = @aws.describe_load_balancers( 140 | load_balancer_names: [name] 141 | ).load_balancer_descriptions 142 | check_length 'load balancers', elbs 143 | @elb = elbs[0] 144 | end 145 | 146 | # @private 147 | def get_instance(instance) 148 | @aws = ( 149 | instance.nil? ? Aws::ElasticLoadBalancing::Client.new : instance 150 | ) 151 | end 152 | end 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /lib/resources/rds/db_instance.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The RDS module contains the RDS API resources 5 | module RDS 6 | # The DBInstance class exposes the RDS::DBInstance resources 7 | class DBInstance < Base # rubocop:disable ClassLength 8 | require 'netaddr' 9 | 10 | # AWS SDK for Ruby v2 Aws::RDS::Client wrapper for initializing a 11 | # DBInstance resource 12 | # @param dbi_name [String] The name of the DBInstance 13 | # @param instance [Class] Aws::RDS::Client instance 14 | # @param ec2 [Class] Aws::EC2::Client ec2 15 | # @raise [RuntimeError] if dbis.nil? 16 | # @raise [RuntimeError] if dbis.length == 0 17 | # @raise [RuntimeError] if dbis.length > 1 18 | def initialize(dbi_name, instance = nil, ec2 = nil) 19 | check_init_arg 'dbi_name', 'RDS::DBInstance', dbi_name 20 | @dbi_name = dbi_name 21 | @aws = instance.nil? ? Aws::RDS::Client.new : instance 22 | @ec2 = ec2.nil? ? Aws::EC2::Client.new : ec2 23 | get_dbi dbi_name 24 | end 25 | 26 | # Returns the string representation of RDS::DBInstance 27 | # @return [String] 28 | def to_s 29 | "RDS DBInstance: #{@dbi_name}" 30 | end 31 | 32 | # Indicates whether instance status is available 33 | def available? 34 | @dbi.db_instance_status == 'available' 35 | end 36 | 37 | # Specifies if the DB instance is a Multi-AZ deployment 38 | def multi_az? 39 | @dbi.multi_az 40 | end 41 | 42 | # Indicates that minor version patches are applied automatically 43 | def auto_minor_version_upgradeable? 44 | @dbi.auto_minor_version_upgrade 45 | end 46 | 47 | # Specifies the accessibility options for the DB instance. A value of 48 | # true specifies an Internet-facing instance with a publicly 49 | # resolvable DNS name, which resolves to a public IP address. A value 50 | # of false specifies an internal instance with a DNS name that 51 | # resolves to a private IP address 52 | def publicly_accessible? 53 | @dbi.publicly_accessible 54 | end 55 | 56 | # Specifies whether the DB instance is encrypted 57 | def with_encrypted_storage? 58 | @dbi.storage_encrypted 59 | end 60 | 61 | # Tests whether the database should allow connections 62 | # from the given CIDR range. Inspects each of the security groups 63 | # associated with the instance and associated inbound rules 64 | # to check for a rule matching the given CIDR range. 65 | # For external CIDR ranges, it is verified that the database is 66 | # publicly accessible. 67 | # 68 | # Even if this method returns true, it does not necessarily mean that 69 | # actual connections made to the RDS instance will succeed. 70 | # 71 | # Examples that may prevent actual connection are: 72 | # * NACL entries 73 | # * Enabled ports 74 | # * Security Group Egress rules 75 | def accessible_from?(cidr) 76 | return false if public_cidr?(cidr) && !publicly_accessible? 77 | 78 | vpc_security_groups.each do |sg| 79 | security_group = AWS::EC2::SecurityGroup.new(sg, @ec2) 80 | return true if security_group.accessible_from?(cidr) 81 | end 82 | 83 | false 84 | end 85 | 86 | # Contains the name of the compute and memory capacity class of the DB 87 | # instance 88 | # @return [String] 89 | def instance_class 90 | @dbi.db_instance_class 91 | end 92 | 93 | # Provides the name of the database engine to be used for this DB 94 | # instance 95 | # @return [String] 96 | def engine 97 | @dbi.engine 98 | end 99 | 100 | # Contains the master username for the DB instance 101 | # @return [String] 102 | def master_username 103 | @dbi.master_username 104 | end 105 | 106 | # The meaning of this parameter differs according to the database 107 | # engine you use 108 | # @return [String] 109 | def database_name 110 | @dbi.db_name 111 | end 112 | 113 | # Specifies the connection endpoint DNS address of the DB instance 114 | # @return [String] 115 | def endpoint 116 | @dbi.endpoint.address 117 | end 118 | 119 | # Specifies the port that the database engine is listening on 120 | # @return [Integer] 121 | def listening_port 122 | @dbi.endpoint.port 123 | end 124 | 125 | # Specifies the allocated storage size specified in gigabytes 126 | # @return [Integer] 127 | def allocated_storage 128 | @dbi.allocated_storage 129 | end 130 | 131 | # Specifies the daily time range during which automated backups are 132 | # created if automated backups are enabled, as determined by the 133 | # BackupRetentionPeriod 134 | # @return [String] 135 | def preferred_backup_window 136 | @dbi.preferred_backup_window 137 | end 138 | 139 | # Specifies the number of days for which automatic DB snapshots are 140 | # retained 141 | # @return [Integer] 142 | def backup_retention_period 143 | @dbi.backup_retention_period 144 | end 145 | 146 | # Provides List of active DB security group names 147 | # @return [Array(String)] 148 | def security_groups 149 | sgs = [] 150 | @dbi.db_security_groups.each do |sg| 151 | sgs << sg.db_security_group_name if sg.status == 'active' 152 | end 153 | sgs 154 | end 155 | 156 | # Provides List of VPC security group elements that the DB instance 157 | # belongs to 158 | # @return [Array(String)] 159 | def vpc_security_groups 160 | vsgs = [] 161 | @dbi.vpc_security_groups.each do |vsg| 162 | vsgs << vsg.vpc_security_group_id if vsg.status == 'active' 163 | end 164 | vsgs 165 | end 166 | 167 | # Provides the list of DB parameter groups applied to this DB instance 168 | # @return [Array(Hash)] 169 | def parameter_groups 170 | @dbi.db_parameter_groups 171 | end 172 | 173 | # Specifies the name of the Availability Zone the DB instance is 174 | # located in 175 | # @return [String] 176 | def availability_zone 177 | @dbi.availability_zone 178 | end 179 | 180 | # Specifies information on the subnet group associated with the DB 181 | # instance, including the name, description, and subnets in the subnet 182 | # group 183 | # @return [Hash] 184 | def subnet_group 185 | @dbi.db_subnet_group 186 | end 187 | 188 | # Specifies the weekly time range (in UTC) during which system 189 | # maintenance can occur 190 | # @return [String] 191 | def preferred_maintenance_window 192 | @dbi.preferred_maintenance_window 193 | end 194 | 195 | # Specifies that changes to the DB instance are pending. This element 196 | # is only included when changes are pending. Specific changes are 197 | # identified by subelements 198 | # @return [Hash] 199 | def pending_modified_values 200 | @dbi.pending_modified_values 201 | end 202 | 203 | # Indicates the database engine version 204 | # @return [String] 205 | def engine_version 206 | @dbi.engine_version 207 | end 208 | 209 | # Contains the identifier of the source DB instance if this DB 210 | # instance is a Read Replica 211 | # @return [String] 212 | def read_replica_source_db_instance_identifier 213 | @dbi.read_replica_source_db_instance_identifier 214 | end 215 | 216 | # Contains one or more identifiers of the Read Replicas associated 217 | # with this DB instance 218 | # @return [Array(String)] 219 | def read_replica_db_instance_identifiers 220 | @dbi.read_replica_db_instance_identifiers 221 | end 222 | 223 | # License model information for this DB instance 224 | # @return [String] 225 | def license_model 226 | @dbi.license_model 227 | end 228 | 229 | # Specifies the Provisioned IOPS (I/O operations per second) value 230 | # @return [Integer] 231 | def iops 232 | @dbi.iops 233 | end 234 | 235 | # Provides the list of option group memberships for this DB instance 236 | # @return [Array(Hash)] 237 | def option_group_memberships 238 | @dbi.option_group_memberships 239 | end 240 | 241 | # If present, specifies the name of the character set that this 242 | # instance is associated with 243 | # @return [String] 244 | def character_set_name 245 | @dbi.character_set_name 246 | end 247 | 248 | # If present, specifies the name of the secondary Availability Zone 249 | # for a DB instance with multi-AZ support 250 | # @return [String] 251 | def secondary_availability_zone 252 | @dbi.secondary_availability_zone 253 | end 254 | 255 | # The status of a Read Replica. If the instance is not a Read Replica, 256 | # this will be blank 257 | # @return [Array(Hash)] 258 | def status_infos 259 | @dbi.status_infos 260 | end 261 | 262 | # Specifies the storage type associated with DB instance 263 | # @return [String] 264 | def storage_type 265 | @dbi.storage_type 266 | end 267 | 268 | # The ARN from the Key Store with which the instance is associated for 269 | # TDE encryption 270 | # @return [String] 271 | def tde_credential_arn 272 | @dbi.tde_credential_arn 273 | end 274 | 275 | # If StorageEncrypted is true, the KMS key identifier for the 276 | # encrypted DB instance 277 | # @return [String] 278 | def kms_key_id 279 | @dbi.kms_key_id 280 | end 281 | 282 | # If StorageEncrypted is true, the region-unique, immutable identifier 283 | # for the encrypted DB instance. This identifier is found in AWS 284 | # CloudTrail log entries whenever the KMS key for the DB instance is 285 | # accessed 286 | # @return [String] 287 | def dbi_resource_id 288 | @dbi.dbi_resource_id 289 | end 290 | 291 | # The identifier of the CA certificate for this DB instance 292 | # @return [String] 293 | def ca_certificate_identifier 294 | @dbi.ca_certificate_identifier 295 | end 296 | 297 | private 298 | 299 | # @private 300 | def get_dbi(name) 301 | dbs = @aws.describe_db_instances( 302 | db_instance_identifier: name 303 | ).db_instances 304 | check_length 'database instances', dbs 305 | @dbi = dbs[0] 306 | end 307 | 308 | # Is the given CIDR in the public address range 309 | # @param cidr_s String representation of CIDR range. 310 | # e.g. '192.168.0.0/16' 311 | # @see https://en.wikipedia.org/wiki/IP_address#Private_addresses 312 | # @private 313 | def public_cidr?(cidr_s) 314 | cidr = NetAddr::CIDR.create(cidr_s) 315 | # CIDR ranges reserved for internal addresses 316 | [ 317 | '10.0.0.0/8', '172.16.0.0/12', '198.168.0.0/16' 318 | ].each do |private_cidr| 319 | if cidr == private_cidr || cidr.is_contained?(private_cidr) 320 | return false 321 | end 322 | end 323 | true 324 | end 325 | end 326 | end 327 | end 328 | end 329 | end 330 | -------------------------------------------------------------------------------- /lib/resources/redshift/cluster.rb: -------------------------------------------------------------------------------- 1 | module Serverspec 2 | module Type 3 | module AWS 4 | # The Redshift module contains the Redshift API resources 5 | module Redshift 6 | # The Cluster class exposes the Redshift::Cluster resources 7 | class Cluster < Base # rubocop:disable ClassLength 8 | # AWS SDK for Ruby v2 Aws::RDS::Client wrapper for initializing a 9 | # Cluster resource 10 | # @param cluster_id [String] The ID of the Cluster 11 | # @param instance [Class] Aws::Redshift::Client instance 12 | # @raise [RuntimeError] if clusters.nil? 13 | # @raise [RuntimeError] if clusters.length == 0 14 | # @raise [RuntimeError] if clusters.length > 1 15 | def initialize(cluster_id, instance = nil) 16 | check_init_arg 'cluster_id', 'Redshift::Cluster', cluster_id 17 | @cluster_id = cluster_id 18 | @aws = instance.nil? ? Aws::Redshift::Client.new : instance 19 | get_cluster cluster_id 20 | end 21 | 22 | # Returns the string representation of Redshift::Cluster 23 | # @return [String] 24 | def to_s 25 | "Redshift Cluster: #{@cluster_id}" 26 | end 27 | 28 | # Indicates whether cluster_status is available 29 | def available? 30 | @cluster.cluster_status == 'available' 31 | end 32 | 33 | # If true, major version upgrades will be applied automatically to the 34 | # cluster during the maintenance window 35 | def version_upgradeable? 36 | @cluster.allow_version_upgrade 37 | end 38 | 39 | # If true, the cluster can be accessed from a public network 40 | def publicly_accessible? 41 | @cluster.publicly_accessible 42 | end 43 | 44 | # If true, data in the cluster is encrypted at rest 45 | def encrypted? 46 | @cluster.encrypted 47 | end 48 | 49 | # The node type for the nodes in the cluster 50 | # @return [String] 51 | def node_type 52 | @cluster.node_type 53 | end 54 | 55 | # The status of a modify operation, if any, initiated for the cluster 56 | # @return [String] 57 | def modify_status 58 | @cluster.modify_status 59 | end 60 | 61 | # The master user name for the cluster. This name is used to connect 62 | # to the database that is specified in DBName 63 | # @return [String] 64 | def master_username 65 | @cluster.master_username 66 | end 67 | 68 | # The name of the initial database that was created when the cluster 69 | # was created. This same name is returned for the life of the cluster. 70 | # If an initial database was not specified, a database named "dev" was 71 | # created by default 72 | # @return [String] 73 | def database_name 74 | @cluster.db_name 75 | end 76 | 77 | # The connection endpoint DNS address of the Cluster 78 | # @return [String] 79 | def endpoint 80 | @cluster.endpoint.address 81 | end 82 | 83 | # The port that the database engine is listening on 84 | # @return [Integer] 85 | def listening_port 86 | @cluster.endpoint.port 87 | end 88 | 89 | # The number of days that automatic cluster snapshots are retained 90 | # @return [Integer] 91 | def automated_snapshot_retention_period 92 | @cluster.automated_snapshot_retention_period 93 | end 94 | 95 | # A list of active cluster security groups 96 | # @return [Array(String)] 97 | def security_groups 98 | sgs = [] 99 | @cluster.cluster_security_groups.each do |sg| 100 | sgs << sg.cluster_security_group_name if sg.status == 'active' 101 | end 102 | sgs 103 | end 104 | 105 | # A list of active VPC security groups 106 | # @return [Array(String)] 107 | def vpc_security_groups 108 | vsgs = [] 109 | @cluster.vpc_security_groups.each do |vsg| 110 | vsgs << vsg.vpc_security_group_id if vsg.status == 'active' 111 | end 112 | vsgs 113 | end 114 | 115 | # The list of cluster parameter groups 116 | # @return [Array(String)] 117 | def parameter_groups 118 | cpgs = [] 119 | @cluster.cluster_parameter_groups.each do |cpg| 120 | cpgs << cpg.parameter_group_name 121 | end 122 | cpgs 123 | end 124 | 125 | # The name of the subnet group that is associated with the cluster. 126 | # This parameter is valid only when the cluster is in a VPC 127 | # @return [String] 128 | def subnet_group 129 | @cluster.cluster_subnet_group_name 130 | end 131 | 132 | # The identifier of the VPC the cluster is in, if the cluster is in a 133 | # VPC 134 | # @return [String] 135 | def vpc_id 136 | @cluster.vpc_id 137 | end 138 | 139 | # The name of the Availability Zone in which the cluster is located 140 | # @return [String] 141 | def availability_zone 142 | @cluster.availability_zone 143 | end 144 | 145 | # The weekly time range (in UTC) during which system maintenance can 146 | # occur 147 | # @return [String] 148 | def preferred_maintenance_window 149 | @cluster.preferred_maintenance_window 150 | end 151 | 152 | # If present, changes to the cluster are pending. Specific pending 153 | # changes are identified by subelements 154 | # @return [Hash] 155 | def pending_modified_values 156 | @cluster.pending_modified_values 157 | end 158 | 159 | # The version ID of the Amazon Redshift engine that is running on the 160 | # cluster 161 | # @return [String] 162 | def version 163 | @cluster.cluster_version 164 | end 165 | 166 | # The number of compute nodes in the cluster 167 | # @return [Integer] 168 | def number_of_nodes 169 | @cluster.number_of_nodes 170 | end 171 | 172 | # Describes the status of a cluster restore action. Returns null if 173 | # the cluster was not created by restoring a snapshot 174 | # @return [Hash] 175 | def restore_status 176 | @cluster.restore_status 177 | end 178 | 179 | # Reports whether the Amazon Redshift cluster has finished applying 180 | # any HSM settings changes specified in a modify cluster command 181 | # @return [Hash] 182 | def hsm_status 183 | @cluster.hsm_status 184 | end 185 | 186 | # Returns the destination region and retention period that are 187 | # configured for cross-region snapshot copy 188 | # @return [Hash] 189 | def snapshot_copy_status 190 | @cluster.cluster_snapshot_copy_status 191 | end 192 | 193 | # The public key for the cluster 194 | # @return [String] 195 | def public_key 196 | @cluster.cluster_public_key 197 | end 198 | 199 | # The nodes in a cluster 200 | # @return [Array(Hash)] 201 | def nodes 202 | @cluster.cluster_nodes 203 | end 204 | 205 | # Describes the status of the elastic IP address 206 | # @return [Hash] 207 | def elastic_ip_status 208 | @cluster.elastic_ip_status 209 | end 210 | 211 | # The specific revision number of the database in the cluster 212 | # @return [String] 213 | def revision_number 214 | @cluster.cluster_revision_number 215 | end 216 | 217 | # The list of tags for the cluster 218 | # @return [Array(Hash)] 219 | def tags 220 | @cluster.tags 221 | end 222 | 223 | # The AWS Key Management Service key ID of the encryption key used to 224 | # encrypt data in the cluster 225 | # @return [String] 226 | def kms_key_id 227 | @cluster.kms_key_id 228 | end 229 | 230 | private 231 | 232 | # @private 233 | def get_cluster(id) 234 | clstrs = @aws.describe_clusters(cluster_identifier: id).clusters 235 | check_length 'clusters', clstrs 236 | @cluster = clstrs[0] 237 | end 238 | end 239 | end 240 | end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /lib/serverspec-aws.rb: -------------------------------------------------------------------------------- 1 | require 'aws-sdk' 2 | require 'serverspec' 3 | require 'require_all' 4 | require_rel 'resources' 5 | -------------------------------------------------------------------------------- /spec/autoscaling/group_spec.rb: -------------------------------------------------------------------------------- 1 | autoscaling = Aws::AutoScaling::Client.new 2 | 3 | # stub Group 4 | autoscaling.stub_responses( 5 | :describe_auto_scaling_groups, 6 | auto_scaling_groups: [{ 7 | auto_scaling_group_name: 'test-group', 8 | created_time: Time.new, 9 | launch_configuration_name: 'test-config', 10 | min_size: 2, 11 | max_size: 4, 12 | desired_capacity: 2, 13 | default_cooldown: 300, 14 | availability_zones: ['us-east-1a', 'us-east-1b'], 15 | load_balancer_names: ['test-elb'], 16 | health_check_type: 'EC2', 17 | health_check_grace_period: 300, 18 | instances: [{ 19 | instance_id: 'i-aabbccdd', 20 | availability_zone: 'us-east-1a', 21 | lifecycle_state: 'InService', 22 | health_status: 'Healthy', 23 | launch_configuration_name: 'test-config', 24 | protected_from_scale_in: false 25 | }], 26 | suspended_processes: [{ 27 | process_name: 'AZRebalance', 28 | suspension_reason: 'User suspended at 2015-05-25T00:00:00Z' 29 | }], 30 | placement_group: 'test-placement-group', 31 | vpc_zone_identifier: 'subnet-aabbccdd,subnet-ddccbbaa', 32 | enabled_metrics: [{ 33 | metric: 'GroupTotalInstances', 34 | granularity: '1Minute' 35 | }], 36 | status: nil, 37 | tags: [{ 38 | resource_id: 'test-group', 39 | resource_type: 'auto-scaling-group', 40 | key: 'Name', 41 | value: 'test-group', 42 | propagate_at_launch: true 43 | }], 44 | termination_policies: %w[ 45 | OldestLaunchConfiguration 46 | ClosestToNextInstanceHour 47 | ] 48 | }] 49 | ) 50 | 51 | # stub Policies (which are exposed by Group anyway) 52 | autoscaling.stub_responses( 53 | :describe_policies, 54 | scaling_policies: [ 55 | { 56 | auto_scaling_group_name: 'test-group', 57 | policy_name: 'scale-on-high-cpu', 58 | scaling_adjustment: 2, 59 | adjustment_type: 'ChangeInCapacity', 60 | min_adjustment_step: 0, 61 | cooldown: 300, 62 | policy_arn: 'arn:aws:autoscaling:us-east-1:000000000000:scalingPolicy:'\ 63 | 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:autoScalingGroupName/test-group:'\ 64 | 'policyName/scale-on-high-cpu', 65 | alarms: [{ 66 | alarm_name: 'cpu-utilization', 67 | alarm_arn: 'arn:aws:cloudwatch:us-east-1:000000000000:alarm:'\ 68 | 'cpu-utilization' 69 | }] 70 | } 71 | ] 72 | ) 73 | 74 | RSpec.describe group = AutoScaling::Group.new('test-group', autoscaling) do 75 | its(:to_s) { is_expected.to eq 'AutoScaling Group: test-group' } 76 | its(:launch_configuration) { is_expected.to eq 'test-config' } 77 | its(:min_size) { is_expected.to eq 2 } 78 | its(:max_size) { is_expected.to eq 4 } 79 | its(:desired_capacity) { is_expected.to eq 2 } 80 | its(:default_cooldown) { is_expected.to eq 300 } 81 | its(:availability_zones) { is_expected.to eq ['us-east-1a', 'us-east-1b'] } 82 | its(:load_balancer_names) { is_expected.to eq ['test-elb'] } 83 | its(:health_check_type) { is_expected.to eq 'EC2' } 84 | its(:health_check_grace_period) { is_expected.to eq 300 } 85 | 86 | # inconsistent with the above values, but the purpose is to do testing 87 | its(:instances) do 88 | instance = group.instances[0] 89 | expect(instance.instance_id).to eq 'i-aabbccdd' 90 | expect(instance.availability_zone).to eq 'us-east-1a' 91 | expect(instance.lifecycle_state).to eq 'InService' 92 | expect(instance.health_status).to eq 'Healthy' 93 | expect(instance.launch_configuration_name).to eq 'test-config' 94 | end 95 | 96 | its(:instance_count) { is_expected.to eq 1 } 97 | 98 | its(:suspended_processes) do 99 | sp = group.suspended_processes[0] 100 | expect(sp.process_name).to eq 'AZRebalance' 101 | expect(sp.suspension_reason).to eq 'User suspended at 2015-05-25T00:00:00Z' 102 | end 103 | 104 | its(:placement_group) { is_expected.to eq 'test-placement-group' } 105 | its(:vpc_subnets) { is_expected.to eq ['subnet-aabbccdd', 'subnet-ddccbbaa'] } 106 | 107 | its(:enabled_metrics) do 108 | em = group.enabled_metrics[0] 109 | expect(em.metric).to eq 'GroupTotalInstances' 110 | expect(em.granularity).to eq '1Minute' 111 | end 112 | 113 | its(:status) { is_expected.to eq nil } 114 | 115 | its(:tags) do 116 | tag = group.tags[0] 117 | expect(tag.resource_id).to eq 'test-group' 118 | expect(tag.resource_type).to eq 'auto-scaling-group' 119 | expect(tag.value).to eq 'test-group' 120 | expect(tag.propagate_at_launch).to eq true 121 | end 122 | 123 | its(:termination_policies) do 124 | is_expected.to eq %w[OldestLaunchConfiguration ClosestToNextInstanceHour] 125 | end 126 | 127 | its(:scaling_policies) do 128 | sp = group.scaling_policies[0] 129 | expect(sp.auto_scaling_group_name).to eq 'test-group' 130 | expect(sp.policy_name).to eq 'scale-on-high-cpu' 131 | expect(sp.scaling_adjustment).to eq 2 132 | expect(sp.adjustment_type).to eq 'ChangeInCapacity' 133 | expect(sp.cooldown).to eq 300 134 | expect(sp.policy_arn).to eq 'arn:aws:autoscaling:us-east-1:000000000000'\ 135 | ':scalingPolicy:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:'\ 136 | 'autoScalingGroupName/test-group:policyName/scale-on-high-cpu' 137 | expect(sp.min_adjustment_step).to eq 0 138 | 139 | alarm = sp.alarms[0] 140 | expect(alarm.alarm_name).to eq 'cpu-utilization' 141 | expect(alarm.alarm_arn).to eq 'arn:aws:cloudwatch:us-east-1:000000000000'\ 142 | ':alarm:cpu-utilization' 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /spec/autoscaling/launch_configuration_spec.rb: -------------------------------------------------------------------------------- 1 | autoscaling = Aws::AutoScaling::Client.new 2 | 3 | # stub LaunchConfiguration 4 | autoscaling.stub_responses( 5 | :describe_launch_configurations, 6 | launch_configurations: [ 7 | { 8 | launch_configuration_name: 'test-config', 9 | created_time: Time.new, 10 | instance_monitoring: { 11 | enabled: true 12 | }, 13 | ebs_optimized: true, 14 | associate_public_ip_address: true, 15 | placement_tenancy: 'default', 16 | image_id: 'ami-aabbccdd', 17 | key_name: 'test-key', 18 | security_groups: ['sg-aabbccdd', 'sg-ddccbbaa'], 19 | classic_link_vpc_id: 'vpc-aabbccdd', 20 | classic_link_vpc_security_groups: ['sg-aabbccdd', 'sg-ddccbbaa'], 21 | user_data: 'Tm90aGluZyBpbnRlcmVzdGluZyBoZXJlLg==', 22 | instance_type: 't1.micro', 23 | kernel_id: 'aki-aabbccdd', 24 | ramdisk_id: 'ari-aabbccdd', 25 | block_device_mappings: [{ 26 | virtual_name: 'ephemeral0', 27 | device_name: '/dev/sdf' 28 | }], 29 | iam_instance_profile: 'test-instance-profile' 30 | } 31 | ] 32 | ) 33 | 34 | RSpec.describe config = AutoScaling::LaunchConfiguration.new( 35 | 'test-config', 36 | autoscaling 37 | ) do 38 | its(:to_s) do 39 | is_expected.to eq 'AutoScaling LaunchConfiguration: test-config' 40 | end 41 | 42 | it { is_expected.to be_instance_monitored } 43 | it { is_expected.to be_ebs_optimized } 44 | it { is_expected.to be_with_public_ip_address } 45 | it { is_expected.to be_default_tenancy } 46 | 47 | its(:image_id) { is_expected.to eq 'ami-aabbccdd' } 48 | its(:key_name) { is_expected.to eq 'test-key' } 49 | its(:security_groups) { is_expected.to eq ['sg-aabbccdd', 'sg-ddccbbaa'] } 50 | 51 | its(:classic_link_vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 52 | its(:classic_link_vpc_security_groups) do 53 | is_expected.to eq ['sg-aabbccdd', 'sg-ddccbbaa'] 54 | end 55 | 56 | its(:user_data) { is_expected.to eq 'Tm90aGluZyBpbnRlcmVzdGluZyBoZXJlLg==' } 57 | its(:instance_type) { is_expected.to eq 't1.micro' } 58 | its(:kernel_id) { is_expected.to eq 'aki-aabbccdd' } 59 | its(:ramdisk_id) { is_expected.to eq 'ari-aabbccdd' } 60 | 61 | its(:block_device_mappings) do 62 | mapping = config.block_device_mappings[0] 63 | expect(mapping.virtual_name).to eq 'ephemeral0' 64 | expect(mapping.device_name).to eq '/dev/sdf' 65 | end 66 | 67 | its(:iam_instance_profile) { is_expected.to eq 'test-instance-profile' } 68 | end 69 | -------------------------------------------------------------------------------- /spec/cloudformation/stack_spec.rb: -------------------------------------------------------------------------------- 1 | # because RSpec doesn't run tests in order I need to make sure that each 2 | # of the CloudFormation stacks has distinct context 3 | cloudformation1 = Aws::CloudFormation::Client.new 4 | cloudformation1.stub_responses( 5 | :describe_stacks, 6 | stacks: [{ 7 | stack_name: 'test-stack1', 8 | creation_time: Time.new, 9 | # shouldn't be the case in production, but for stubbing, this defaults to 10 | # false and it needs to be changed to actually test the code 11 | disable_rollback: true, 12 | stack_status: 'CREATE_COMPLETE', 13 | description: 'test-stack1 description', 14 | parameters: [{ 15 | parameter_key: 'Param1', 16 | parameter_value: 'Param1Value', 17 | use_previous_value: false 18 | }], 19 | notification_arns: %w[arn:aws:sns:us-east-1:000000000000:sns-topic], 20 | timeout_in_minutes: 5, 21 | capabilities: %w[CAPABILITY_IAM], 22 | outputs: [{ 23 | output_key: 'test-stack1-output', 24 | output_value: 'test-stack1-output-value', 25 | description: 'test-stack1-output description' 26 | }], 27 | tags: [{ 28 | key: 'Name', 29 | value: 'test-stack1' 30 | }] 31 | }] 32 | ) 33 | 34 | cloudformation2 = Aws::CloudFormation::Client.new 35 | cloudformation2.stub_responses( 36 | :describe_stacks, 37 | stacks: [{ 38 | stack_name: 'test-stack2', 39 | creation_time: Time.new, 40 | stack_status: 'UPDATE_COMPLETE' 41 | }] 42 | ) 43 | 44 | cloudformation3 = Aws::CloudFormation::Client.new 45 | cloudformation3.stub_responses( 46 | :describe_stacks, 47 | stacks: [{ 48 | stack_name: 'test-stack3', 49 | creation_time: Time.new, 50 | stack_status: 'UPDATE_ROLLBACK_COMPLETE' 51 | }] 52 | ) 53 | 54 | RSpec.describe stack1 = CloudFormation::Stack.new( 55 | 'test-stack1', 56 | cloudformation1 57 | ) do 58 | its(:to_s) { is_expected.to eq 'CloudFormation Stack: test-stack1' } 59 | 60 | it { is_expected.to be_rollback_disabled } 61 | it { is_expected.to be_ok } 62 | 63 | its(:description) { is_expected.to eq 'test-stack1 description' } 64 | 65 | its(:parameters) do 66 | param = stack1.parameters[0] 67 | expect(param.parameter_key).to eq 'Param1' 68 | expect(param.parameter_value).to eq 'Param1Value' 69 | expect(param.use_previous_value).to eq false 70 | end 71 | 72 | its(:stack_status) { is_expected.to eq 'CREATE_COMPLETE' } 73 | 74 | notification_arns = %w[arn:aws:sns:us-east-1:000000000000:sns-topic] 75 | its(:notification_arns) do 76 | expect(stack1.notification_arns).to eq notification_arns 77 | end 78 | its(:timeout_in_minutes) { is_expected.to eq 5 } 79 | 80 | its(:capabilities) { is_expected.to eq %w[CAPABILITY_IAM] } 81 | 82 | its(:outpus) do 83 | output = stack1.outputs[0] 84 | expect(output.output_key).to eq 'test-stack1-output' 85 | expect(output.output_value).to eq 'test-stack1-output-value' 86 | expect(output.description).to eq 'test-stack1-output description' 87 | end 88 | 89 | its(:tags) do 90 | tag = stack1.tags[0] 91 | expect(tag.key).to eq 'Name' 92 | expect(tag.value).to eq 'test-stack1' 93 | end 94 | end 95 | 96 | RSpec.describe CloudFormation::Stack.new( 97 | 'test-stack2', 98 | cloudformation2 99 | ) do 100 | its(:to_s) { is_expected.to eq 'CloudFormation Stack: test-stack2' } 101 | it { is_expected.to be_ok } 102 | end 103 | 104 | RSpec.describe CloudFormation::Stack.new( 105 | 'test-stack3', 106 | cloudformation3 107 | ) do 108 | its(:to_s) { is_expected.to eq 'CloudFormation Stack: test-stack3' } 109 | it { is_expected.not_to be_ok } 110 | end 111 | -------------------------------------------------------------------------------- /spec/cloudwatch/alarm_spec.rb: -------------------------------------------------------------------------------- 1 | cloudwatch = Aws::CloudWatch::Client.new 2 | cloudwatch.stub_responses( 3 | :describe_alarms, 4 | metric_alarms: [ 5 | { 6 | alarm_description: 'This is the alarm description.', 7 | state_value: 'OK', 8 | actions_enabled: true, 9 | alarm_actions: [ 10 | 'arn:aws:sns:us-east-1:000000000000:sns-topic', 11 | 'arn:aws:autoscaling:us-east-1:000000000000:scalingPolicy:aaaaaaaa-'\ 12 | 'bbbb-cccc-dddd-eeeeeeeeeeee:autoScalingGroupName/test-group:'\ 13 | 'policyName/scale-on-high-cpu' 14 | ], 15 | metric_name: 'CPUUtilization', 16 | namespace: 'AWS/EC2', 17 | statistic: 'Average', 18 | dimensions: [{ 19 | name: 'AutoScalingGroupName', 20 | value: 'test-asg' 21 | }], 22 | period: 300, 23 | evaluation_periods: 1, 24 | threshold: 75.0, 25 | comparison_operator: 'GreaterThanOrEqualToThreshold' 26 | } 27 | ] 28 | ) 29 | 30 | RSpec.describe alarm = CloudWatch::Alarm.new('test-alarm', cloudwatch) do 31 | its(:to_s) { is_expected.to eq 'CloudWatch Alarm: test-alarm' } 32 | 33 | it { is_expected.to be_ok } 34 | it { is_expected.to be_actions_enabled } 35 | 36 | its(:alarm_description) { is_expected.to eq 'This is the alarm description.' } 37 | its(:ok_actions) { is_expected.to eq [] } 38 | 39 | its(:alarm_actions) do 40 | is_expected.to eq [ 41 | 'arn:aws:sns:us-east-1:000000000000:sns-topic', 42 | 'arn:aws:autoscaling:us-east-1:000000000000:scalingPolicy:aaaaaaaa-bbbb-'\ 43 | 'cccc-dddd-eeeeeeeeeeee:autoScalingGroupName/test-group:policyName/'\ 44 | 'scale-on-high-cpu' 45 | ] 46 | end 47 | 48 | its(:insufficient_data_actions) { is_expected.to eq [] } 49 | its(:metric_name) { is_expected.to eq 'CPUUtilization' } 50 | its(:namespace) { is_expected.to eq 'AWS/EC2' } 51 | its(:statistic) { is_expected.to eq 'Average' } 52 | 53 | its(:dimensions) do 54 | dim = alarm.dimensions[0] 55 | expect(dim.name).to eq 'AutoScalingGroupName' 56 | expect(dim.value).to eq 'test-asg' 57 | end 58 | 59 | its(:period) { is_expected.to eq 300 } 60 | its(:evaluation_periods) { is_expected.to eq 1 } 61 | its(:threshold) { is_expected.to eq 75.0 } 62 | its(:comparison_operator) do 63 | is_expected.to eq 'GreaterThanOrEqualToThreshold' 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/dynamodb/table_spec.rb: -------------------------------------------------------------------------------- 1 | key_schema = [ 2 | { 3 | attribute_name: 'test-hash-attrib', 4 | key_type: 'HASH' 5 | }, 6 | { 7 | attribute_name: 'test-range-attrib', 8 | key_type: 'RANGE' 9 | } 10 | ] 11 | 12 | attribute_definitions = [ 13 | { 14 | attribute_name: 'test-hash-attrib', 15 | attribute_type: 'S' 16 | }, 17 | { 18 | attribute_name: 'test-range-attrib', 19 | attribute_type: 'N' 20 | } 21 | ] 22 | 23 | provisioned_throughput = { 24 | last_increase_date_time: Time.new(0), 25 | last_decrease_date_time: Time.new(0), 26 | number_of_decreases_today: 0, 27 | read_capacity_units: 64, 28 | write_capacity_units: 32 29 | } 30 | 31 | local_secondary_indexes = [ 32 | { 33 | index_name: 'test-lsi', 34 | key_schema: key_schema, 35 | projection: { 36 | projection_type: 'ALL', 37 | non_key_attributes: [] 38 | }, 39 | index_size_bytes: 512, 40 | item_count: 32 41 | } 42 | ] 43 | 44 | global_secondary_indexes = [ 45 | { 46 | index_name: 'test-gsi', 47 | key_schema: key_schema, 48 | projection: { 49 | projection_type: 'ALL', 50 | non_key_attributes: [] 51 | }, 52 | index_status: 'ACTIVE', 53 | backfilling: false, 54 | provisioned_throughput: provisioned_throughput, 55 | index_size_bytes: 512, 56 | item_count: 32 57 | } 58 | ] 59 | 60 | dynamodb = Aws::DynamoDB::Client.new 61 | dynamodb.stub_responses( 62 | :describe_table, 63 | table: { 64 | table_status: 'ACTIVE', 65 | attribute_definitions: attribute_definitions, 66 | key_schema: key_schema, 67 | local_secondary_indexes: local_secondary_indexes, 68 | global_secondary_indexes: global_secondary_indexes, 69 | provisioned_throughput: provisioned_throughput 70 | } 71 | ) 72 | 73 | RSpec.describe table = DynamoDB::Table.new('test-table', dynamodb) do 74 | its(:to_s) { is_expected.to eq 'DynamoDB Table: test-table' } 75 | 76 | it { is_expected.to be_valid } 77 | it { is_expected.to be_with_hash_key } 78 | it { is_expected.to be_with_range_key } 79 | it { is_expected.to be_local_secondary_indexed } 80 | it { is_expected.to be_global_secondary_indexed } 81 | 82 | its(:attribute_definitions) do 83 | attr_def = [] 84 | table.attribute_definitions.each do |attr| 85 | attr_def << attr.to_h 86 | end 87 | expect(attr_def).to eq attribute_definitions 88 | end 89 | 90 | its(:key_schema) do 91 | key_sch = [] 92 | table.key_schema.each do |ks| 93 | key_sch << ks.to_h 94 | end 95 | expect(key_sch).to eq key_schema 96 | end 97 | 98 | its(:read_capacity) { is_expected.to eq 64 } 99 | its(:write_capacity) { is_expected.to eq 32 } 100 | 101 | its(:local_secondary_indexes) do 102 | lsi = table.local_secondary_indexes[0].to_h 103 | expect([lsi]).to eq local_secondary_indexes 104 | end 105 | 106 | its(:global_secondary_indexes) do 107 | gsi = table.global_secondary_indexes[0].to_h 108 | expect([gsi]).to eq global_secondary_indexes 109 | end 110 | 111 | its(:hash_key_name) { is_expected.to eq 'test-hash-attrib' } 112 | its(:hash_key_type) { is_expected.to eq :string } 113 | 114 | its(:range_key_name) { is_expected.to eq 'test-range-attrib' } 115 | its(:range_key_type) { is_expected.to eq :number } 116 | end 117 | -------------------------------------------------------------------------------- /spec/ec2/image_spec.rb: -------------------------------------------------------------------------------- 1 | ec21 = Aws::EC2::Client.new 2 | # stub Instance 3 | ec21.stub_responses( 4 | :describe_images, 5 | images: [ 6 | { 7 | image_id: 'ami-aabbccdd', 8 | image_location: '000000000000000000000/my-ami', 9 | state: 'available', 10 | owner_id: '000000000000000000000', 11 | creation_date: '2016-09-29T03:38:31.000Z', 12 | public: false, 13 | product_codes: [], 14 | architecture: 'x86_64', 15 | image_type: 'machine', 16 | kernel_id: nil, 17 | ramdisk_id: nil, 18 | platform: nil, 19 | sriov_net_support: 'simple', 20 | ena_support: nil, 21 | state_reason: nil, 22 | image_owner_alias: nil, 23 | name: 'my-ami', 24 | description: nil, 25 | root_device_type: 'ebs', 26 | root_device_name: '/dev/sda1', 27 | block_device_mappings: [ 28 | { 29 | virtual_name: nil, 30 | device_name: '/dev/sda1', 31 | ebs: { 32 | snapshot_id: 'snap-11111111', #=> String 33 | volume_size: 8, #=> Integer 34 | delete_on_termination: true, #=> true/false 35 | # String, one of "standard", "io1", "gp2", "sc1", "st1" 36 | volume_type: 'gp2', 37 | iops: nil, #=> Integer 38 | encrypted: false #=> true/false 39 | }, 40 | no_device: nil, #=> String 41 | }, 42 | { 43 | virtual_name: 'ephemeral0', 44 | device_name: '/dev/sdb', 45 | ebs: nil, 46 | no_device: nil, #=> String 47 | }, 48 | { 49 | virtual_name: 'ephemeral1', 50 | device_name: '/dev/sdc', 51 | ebs: nil, 52 | no_device: nil, #=> String 53 | } 54 | ], 55 | virtualization_type: 'hvm', #=> String, one of "hvm", "paravirtual" 56 | tags: [], #=> Array 57 | hypervisor: 'xen' #=> String, one of "ovm", "xen" 58 | } 59 | ] 60 | ) 61 | 62 | ec22 = Aws::EC2::Client.new 63 | # stub Instance 64 | ec22.stub_responses( 65 | :describe_images, 66 | images: [ 67 | { 68 | image_id: 'ami-12345678901234567', 69 | image_location: '000000000000000000000/my-ami2', 70 | state: 'available', 71 | owner_id: '000000000000000000000', 72 | creation_date: '2016-09-29T03:38:31.000Z', 73 | public: false, 74 | product_codes: [], 75 | architecture: 'x86_64', 76 | image_type: 'machine', 77 | kernel_id: nil, 78 | ramdisk_id: nil, 79 | platform: nil, 80 | sriov_net_support: 'simple', 81 | ena_support: nil, 82 | state_reason: nil, 83 | image_owner_alias: nil, 84 | name: 'my-ami2', 85 | description: nil, 86 | root_device_type: 'ebs', 87 | root_device_name: '/dev/sda1', 88 | block_device_mappings: [ 89 | { 90 | virtual_name: nil, 91 | device_name: '/dev/sda1', 92 | ebs: { 93 | snapshot_id: 'snap-22222222', #=> String 94 | volume_size: 8, #=> Integer 95 | delete_on_termination: true, #=> true/false 96 | #=> String, one of "standard", "io1", "gp2", "sc1", "st1" 97 | volume_type: 'gp2', 98 | iops: nil, #=> Integer 99 | encrypted: true #=> true/false 100 | }, 101 | no_device: nil, #=> String 102 | }, 103 | { 104 | virtual_name: 'ephemeral0', 105 | device_name: '/dev/sdb', 106 | ebs: nil, 107 | no_device: nil, #=> String 108 | }, 109 | { 110 | virtual_name: 'ephemeral1', 111 | device_name: '/dev/sdc', 112 | ebs: nil, 113 | no_device: nil, #=> String 114 | } 115 | ], 116 | virtualization_type: 'hvm', #=> String, one of "hvm", "paravirtual" 117 | tags: [], #=> Array 118 | hypervisor: 'xen' #=> String, one of "ovm", "xen" 119 | } 120 | ] 121 | ) 122 | 123 | RSpec.describe EC2::Image.new('ami-aabbccdd', ec21) do 124 | its(:to_s) { is_expected.to eq 'EC2 Image ID: ami-aabbccdd; Name: my-ami' } 125 | its(:image_id) { should eq 'ami-aabbccdd' } 126 | its(:image_name) { should eq 'my-ami' } 127 | 128 | its(:root_volume) do 129 | root_volume = subject.root_volume 130 | expect(root_volume.snapshot_id).to eq 'snap-11111111' 131 | expect(root_volume.device_name).to eq '/dev/sda1' 132 | expect(root_volume.type).to eq 'ebs' 133 | end 134 | 135 | its(:root_volume) { should_not be_encrypted } 136 | end 137 | 138 | RSpec.describe EC2::Image.new('my-ami2', ec22) do 139 | its(:image_id) { should eq 'ami-12345678901234567' } 140 | its(:image_name) { should eq 'my-ami2' } 141 | 142 | its(:to_s) do 143 | is_expected.to eq 'EC2 Image ID: ami-12345678901234567; Name: my-ami2' 144 | end 145 | 146 | its(:root_volume) { should be_encrypted } 147 | end 148 | -------------------------------------------------------------------------------- /spec/ec2/instance_spec.rb: -------------------------------------------------------------------------------- 1 | ec21 = Aws::EC2::Client.new 2 | # stub Instance 3 | ec21.stub_responses( 4 | :describe_instances, 5 | reservations: [ 6 | { 7 | reservation_id: 'r-aabbccdd', 8 | instances: [ 9 | { 10 | instance_id: 'i-aabbccdd', 11 | image_id: 'ami-aabbccdd', 12 | state: { 13 | code: 16, 14 | name: 'running' 15 | }, 16 | private_dns_name: 'ip-10-0-0-1.us-east-1.compute.internal', 17 | public_dns_name: 'ec2-1-2-3-4.compute-1.amazonaws.com', 18 | key_name: 'test-key', 19 | instance_type: 't2.micro', 20 | placement: { 21 | availability_zone: 'us-east-1a', 22 | group_name: '', 23 | tenancy: 'default' 24 | }, 25 | kernel_id: 'aki-aabbccdd', 26 | ramdisk_id: 'ari-aabbccdd', 27 | # this is not consistent, but the testing must be done 28 | platform: 'windows', 29 | monitoring: { 30 | state: 'enabled' 31 | }, 32 | subnet_id: 'subnet-aabbccdd', 33 | vpc_id: 'vpc-aabbccdd', 34 | private_ip_address: '10.0.0.1', 35 | public_ip_address: '1.2.3.4', 36 | architecture: 'x86_64', 37 | root_device_type: 'ebs', 38 | root_device_name: '/dev/xvda', 39 | block_device_mappings: [{ 40 | device_name: '/dev/xvda', 41 | ebs: { 42 | volume_id: 'vol-aabbccdd', 43 | status: 'attached', 44 | attach_time: Time.new, 45 | delete_on_termination: true 46 | } 47 | }], 48 | virtualization_type: 'hvm', 49 | instance_lifecycle: nil, 50 | tags: [ 51 | { 52 | key: 'test-key', 53 | value: 'test-value' 54 | } 55 | ], 56 | security_groups: [ 57 | { 58 | group_name: 'test-group', 59 | group_id: 'sg-aabbccdd' 60 | } 61 | ], 62 | source_dest_check: true, 63 | hypervisor: 'xen', 64 | network_interfaces: [ 65 | { 66 | network_interface_id: 'eni-aabbccdd', 67 | subnet_id: 'subnet-aabbccdd', 68 | vpc_id: 'vpc-aabbccdd', 69 | description: 'eni description', 70 | owner_id: '000000000000000000000', 71 | status: 'in-use', 72 | mac_address: 'aa:bb:cc:dd:ee:ff', 73 | private_ip_address: '10.0.0.1', 74 | private_dns_name: nil, 75 | source_dest_check: true, 76 | groups: [ 77 | { 78 | group_name: 'test-group', 79 | group_id: 'sg-aabbccdd' 80 | } 81 | ], 82 | attachment: { 83 | attachment_id: 'eni-attach-aabbccdd', 84 | device_index: 0, 85 | status: 'attached', 86 | attach_time: Time.new, 87 | delete_on_termination: true 88 | }, 89 | association: nil, 90 | private_ip_addresses: [ 91 | { 92 | private_ip_address: '10.0.0.1', 93 | private_dns_name: nil, 94 | primary: true, 95 | association: nil 96 | } 97 | ] 98 | } 99 | ], 100 | iam_instance_profile: { 101 | arn: 'arn:aws:iam::000000000000000000000:instance-profile/'\ 102 | 'test-instance-profile', 103 | id: 'AAAAAAAAAAAAAAAAAAAAA' 104 | }, 105 | ebs_optimized: true, 106 | sriov_net_support: 'simple' 107 | } 108 | ] 109 | } 110 | ] 111 | ) 112 | 113 | ec22 = Aws::EC2::Client.new 114 | ec22.stub_responses( 115 | :describe_instances, 116 | reservations: [ 117 | { 118 | reservation_id: 'r-aabbccdd', 119 | instances: [ 120 | { 121 | instance_id: 'i-aabbccdd', 122 | tags: [ 123 | { 124 | key: 'Name', 125 | value: 'test-instance' 126 | } 127 | ] 128 | } 129 | ] 130 | } 131 | ] 132 | ) 133 | 134 | RSpec.describe instance1 = EC2::Instance.new('i-aabbccdd', ec21) do 135 | its(:to_s) { is_expected.to eq 'EC2 Instance ID: i-aabbccdd' } 136 | 137 | it { is_expected.to be_running } 138 | it { is_expected.to be_monitoring_enabled } 139 | it { is_expected.to be_on_windows } 140 | it { is_expected.to be_source_dest_checked } 141 | it { is_expected.to be_ebs_optimized } 142 | it { is_expected.to be_enhanced_networked } 143 | 144 | its(:image_id) { is_expected.to eq 'ami-aabbccdd' } 145 | its(:private_dns_name) do 146 | is_expected.to eq 'ip-10-0-0-1.us-east-1.compute.internal' 147 | end 148 | its(:public_dns_name) do 149 | is_expected.to eq 'ec2-1-2-3-4.compute-1.amazonaws.com' 150 | end 151 | its(:key_name) { is_expected.to eq 'test-key' } 152 | its(:instance_type) { is_expected.to eq 't2.micro' } 153 | 154 | its(:placement) do 155 | placement = instance1.placement 156 | expect(placement.availability_zone).to eq 'us-east-1a' 157 | expect(placement.group_name).to eq '' 158 | expect(placement.tenancy).to eq 'default' 159 | end 160 | 161 | its(:kernel_id) { is_expected.to eq 'aki-aabbccdd' } 162 | its(:ramdisk_id) { is_expected.to eq 'ari-aabbccdd' } 163 | its(:subnet_id) { is_expected.to eq 'subnet-aabbccdd' } 164 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 165 | its(:private_ip_address) { is_expected.to eq '10.0.0.1' } 166 | its(:public_ip_address) { is_expected.to eq '1.2.3.4' } 167 | its(:architecture) { is_expected.to eq 'x86_64' } 168 | its(:root_device_type) { is_expected.to eq 'ebs' } 169 | its(:root_device_name) { is_expected.to eq '/dev/xvda' } 170 | 171 | its(:block_device_mappings) do 172 | dev = instance1.block_device_mappings[0] 173 | expect(dev.device_name).to eq '/dev/xvda' 174 | expect(dev.ebs.volume_id).to eq 'vol-aabbccdd' 175 | expect(dev.ebs.status).to eq 'attached' 176 | expect(dev.ebs.delete_on_termination).to eq true 177 | end 178 | 179 | its(:virtualization_type) { is_expected.to eq 'hvm' } 180 | its(:instance_lifecycle) { is_expected.to eq nil } 181 | 182 | its(:tags) do 183 | tag = instance1.tags[0] 184 | expect(tag.key).to eq 'test-key' 185 | expect(tag.value).to eq 'test-value' 186 | end 187 | 188 | its(:security_groups) do 189 | sg = instance1.security_groups[0] 190 | expect(sg.group_name).to eq 'test-group' 191 | expect(sg.group_id).to eq 'sg-aabbccdd' 192 | end 193 | 194 | its(:hypervisor) { is_expected.to eq 'xen' } 195 | 196 | its(:network_interfaces) do 197 | eni = instance1.network_interfaces[0] 198 | expect(eni.network_interface_id).to eq 'eni-aabbccdd' 199 | expect(eni.subnet_id).to eq 'subnet-aabbccdd' 200 | expect(eni.vpc_id).to eq 'vpc-aabbccdd' 201 | expect(eni.description).to eq 'eni description' 202 | expect(eni.owner_id).to eq '000000000000000000000' 203 | expect(eni.status).to eq 'in-use' 204 | expect(eni.mac_address).to eq 'aa:bb:cc:dd:ee:ff' 205 | expect(eni.private_ip_address).to eq '10.0.0.1' 206 | expect(eni.private_dns_name).to eq nil 207 | expect(eni.source_dest_check).to eq true 208 | expect(eni.attachment.attachment_id).to eq 'eni-attach-aabbccdd' 209 | expect(eni.attachment.device_index).to eq 0 210 | expect(eni.attachment.status).to eq 'attached' 211 | expect(eni.attachment.delete_on_termination).to eq true 212 | expect(eni.association).to eq nil 213 | 214 | sg = eni.groups[0] 215 | expect(sg.group_name).to eq 'test-group' 216 | expect(sg.group_id).to eq 'sg-aabbccdd' 217 | 218 | pi = eni.private_ip_addresses[0] 219 | expect(pi.private_ip_address).to eq '10.0.0.1' 220 | expect(pi.private_dns_name).to eq nil 221 | expect(pi.primary).to eq true 222 | expect(pi.association).to eq nil 223 | end 224 | 225 | its(:iam_instance_profile) do 226 | prof = instance1.iam_instance_profile 227 | expect(prof.arn).to eq 'arn:aws:iam::000000000000000000000:'\ 228 | 'instance-profile/test-instance-profile' 229 | expect(prof.id).to eq 'AAAAAAAAAAAAAAAAAAAAA' 230 | end 231 | end 232 | 233 | RSpec.describe EC2::Instance.new('test-instance', ec22) do 234 | its(:to_s) do 235 | is_expected.to eq 'EC2 Instance ID: i-aabbccdd; Name: test-instance' 236 | end 237 | end 238 | -------------------------------------------------------------------------------- /spec/ec2/internet_gateway_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | # stub InternetGateway 3 | ec2.stub_responses( 4 | :describe_internet_gateways, 5 | internet_gateways: [ 6 | { 7 | attachments: [ 8 | { 9 | vpc_id: 'vpc-aabbccdd', 10 | state: 'available' 11 | } 12 | ], 13 | tags: [ 14 | { 15 | key: 'Name', 16 | value: 'test-internet-gateway' 17 | } 18 | ] 19 | } 20 | ] 21 | ) 22 | 23 | RSpec.describe igw = EC2::InternetGateway.new('igw-aabbccdd', ec2) do 24 | its(:to_s) { is_expected.to eq 'EC2 InternetGateway: igw-aabbccdd' } 25 | it { is_expected.to be_available } 26 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 27 | 28 | its(:attachments) do 29 | att = igw.attachments[0] 30 | expect(att.vpc_id).to eq 'vpc-aabbccdd' 31 | expect(att.state).to eq 'available' 32 | end 33 | 34 | its(:tags) do 35 | tag = igw.tags[0] 36 | expect(tag.key).to eq 'Name' 37 | expect(tag.value).to eq 'test-internet-gateway' 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/ec2/network_interface_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | # stub NetworkInterface 3 | ec2.stub_responses( 4 | :describe_network_interfaces, 5 | network_interfaces: [ 6 | { 7 | subnet_id: 'subnet-aabbccdd', 8 | vpc_id: 'vpc-aabbccdd', 9 | availability_zone: 'us-east-1a', 10 | description: 'network interface description', 11 | owner_id: '000000000000000000000', 12 | requester_id: '111111111111111111111', 13 | requester_managed: true, 14 | status: 'in-use', 15 | mac_address: 'aa:bb:cc:dd:ee:ff', 16 | private_ip_address: '10.0.0.1', 17 | private_dns_name: nil, 18 | source_dest_check: true, 19 | groups: [ 20 | { 21 | group_name: 'test-group', 22 | group_id: 'sg-aabbccdd' 23 | } 24 | ], 25 | attachment: { 26 | attachment_id: 'eni-attach-aabbccdd', 27 | instance_id: nil, 28 | instance_owner_id: 'amazon-elb', 29 | device_index: 1, 30 | status: 'attached', 31 | delete_on_termination: true 32 | }, 33 | association: nil, 34 | tag_set: [ 35 | { 36 | key: 'Name', 37 | value: 'test-network-interface' 38 | } 39 | ], 40 | private_ip_addresses: [ 41 | { 42 | private_ip_address: '10.0.0.1', 43 | private_dns_name: nil, 44 | primary: true, 45 | association: nil 46 | } 47 | ] 48 | } 49 | ] 50 | ) 51 | 52 | RSpec.describe ni = EC2::NetworkInterface.new('eni-aabbccdd', ec2) do 53 | its(:to_s) { is_expected.to eq 'EC2 NetworkInterface: eni-aabbccdd' } 54 | 55 | it { is_expected.to be_attached } 56 | it { is_expected.to be_requester_managed } 57 | it { is_expected.to be_in_use } 58 | it { is_expected.to be_source_dest_checked } 59 | 60 | its(:subnet_id) { is_expected.to eq 'subnet-aabbccdd' } 61 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 62 | its(:availability_zone) { is_expected.to eq 'us-east-1a' } 63 | its(:description) { is_expected.to eq 'network interface description' } 64 | its(:owner_id) { is_expected.to eq '000000000000000000000' } 65 | its(:requester_id) { is_expected.to eq '111111111111111111111' } 66 | its(:status) { is_expected.to eq 'in-use' } 67 | its(:mac_address) { is_expected.to eq 'aa:bb:cc:dd:ee:ff' } 68 | its(:private_ip_address) { is_expected.to eq '10.0.0.1' } 69 | its(:private_dns_name) { is_expected.to eq nil } 70 | 71 | its(:groups) do 72 | group = ni.groups[0] 73 | expect(group.group_name).to eq 'test-group' 74 | expect(group.group_id).to eq 'sg-aabbccdd' 75 | end 76 | 77 | its(:attachment) do 78 | attch = ni.attachment 79 | expect(attch.attachment_id).to eq 'eni-attach-aabbccdd' 80 | expect(attch.instance_id).to eq nil 81 | expect(attch.instance_owner_id).to eq 'amazon-elb' 82 | expect(attch.device_index).to eq 1 83 | expect(attch.delete_on_termination).to eq true 84 | end 85 | 86 | its(:association) { is_expected.to eq nil } 87 | 88 | its(:tags) do 89 | tag = ni.tags[0] 90 | expect(tag.key).to eq 'Name' 91 | expect(tag.value).to eq 'test-network-interface' 92 | end 93 | 94 | its(:private_ip_addresses) do 95 | pi = ni.private_ip_addresses[0] 96 | expect(pi.private_ip_address).to eq '10.0.0.1' 97 | expect(pi.private_dns_name).to eq nil 98 | expect(pi.primary).to eq true 99 | expect(pi.association).to eq nil 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/ec2/route_table_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | # stub RouteTable 3 | ec2.stub_responses( 4 | :describe_route_tables, 5 | route_tables: [ 6 | { 7 | vpc_id: 'vpc-aabbccdd', 8 | routes: [ 9 | { 10 | destination_cidr_block: '10.0.0.0/16', 11 | state: 'active', 12 | origin: 'CreateRouteTable' 13 | } 14 | ], 15 | associations: [ 16 | { 17 | route_table_association_id: 'rtbassoc-aabbccdd', 18 | route_table_id: 'rtb-aabbccdd', 19 | main: true 20 | } 21 | ], 22 | tags: [ 23 | { 24 | key: 'Name', 25 | value: 'test-route-table' 26 | } 27 | ] 28 | } 29 | ] 30 | ) 31 | 32 | RSpec.describe rtb = EC2::RouteTable.new('rtb-aabbccdd', ec2) do 33 | its(:to_s) { is_expected.to eq 'EC2 RouteTable: rtb-aabbccdd' } 34 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 35 | 36 | its(:routes) do 37 | route = rtb.routes[0] 38 | expect(route.destination_cidr_block).to eq '10.0.0.0/16' 39 | expect(route.state).to eq 'active' 40 | expect(route.origin).to eq 'CreateRouteTable' 41 | end 42 | 43 | its(:associations) do 44 | assoc = rtb.associations[0] 45 | expect(assoc.route_table_association_id).to eq 'rtbassoc-aabbccdd' 46 | expect(assoc.route_table_id).to eq 'rtb-aabbccdd' 47 | expect(assoc.main).to eq true 48 | end 49 | 50 | its(:tags) do 51 | tag = rtb.tags[0] 52 | expect(tag.key).to eq 'Name' 53 | expect(tag.value).to eq 'test-route-table' 54 | end 55 | 56 | its(:propagating_vgws) { is_expected.to eq [] } 57 | end 58 | -------------------------------------------------------------------------------- /spec/ec2/security_group_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | 3 | # rubocop:disable Metrics/MethodLength 4 | def security_group(with_overrides) 5 | template = { 6 | owner_id: '000000000000000000000', 7 | group_name: 'test-group', 8 | description: 'test-group description', 9 | ip_permissions: [], 10 | ip_permissions_egress: [ 11 | { 12 | ip_protocol: '-1', 13 | from_port: nil, 14 | to_port: nil, 15 | user_id_group_pairs: [], 16 | ip_ranges: [ 17 | { 18 | cidr_ip: '0.0.0.0/0' 19 | } 20 | ], 21 | prefix_list_ids: [] 22 | } 23 | ], 24 | vpc_id: 'vpc-aabbccdd', 25 | tags: [ 26 | { 27 | key: 'Name', 28 | value: 'test-group' 29 | } 30 | ] 31 | } 32 | 33 | stub_response(template, with_overrides) 34 | end 35 | # rubocop:enable Metrics/MethodLength 36 | 37 | # rubocop:disable Metrics/MethodLength 38 | def rule(cidr) 39 | { 40 | ip_protocol: 'tcp', 41 | from_port: 443, 42 | to_port: 443, 43 | user_id_group_pairs: [ 44 | { 45 | user_id: '000000000000000000000', 46 | group_name: nil, 47 | group_id: 'sg-ddccbbaa' 48 | } 49 | ], 50 | ip_ranges: [ 51 | { 52 | cidr_ip: cidr 53 | } 54 | ], 55 | prefix_list_ids: [] 56 | } 57 | end 58 | # rubocop:enable Metrics/MethodLength 59 | 60 | RSpec.context 'Security Group allows ingress from anywhere' do 61 | ec2.stub_responses( 62 | :describe_security_groups, 63 | security_groups: [ 64 | security_group(ip_permissions: [rule('0.0.0.0/0')]) 65 | ] 66 | ) 67 | 68 | describe sg = EC2::SecurityGroup.new('sg-aabbccdd', ec2) do 69 | its(:to_s) { is_expected.to eq 'EC2 SecurityGroup: sg-aabbccdd' } 70 | 71 | its(:owner_id) { is_expected.to eq '000000000000000000000' } 72 | its(:group_name) { is_expected.to eq 'test-group' } 73 | its(:description) { is_expected.to eq 'test-group description' } 74 | 75 | its(:ingress_permissions) do 76 | ingress = sg.ingress_permissions[0] 77 | expect(ingress.ip_protocol).to eq 'tcp' 78 | expect(ingress.from_port).to eq 443 79 | expect(ingress.to_port).to eq 443 80 | 81 | expect(ingress.ip_ranges.size).to eq 1 82 | expect(ingress.ip_ranges[0].cidr_ip).to eq '0.0.0.0/0' 83 | expect(ingress.prefix_list_ids).to eq [] 84 | 85 | pair = ingress.user_id_group_pairs[0] 86 | expect(pair.user_id).to eq '000000000000000000000' 87 | expect(pair.group_name).to eq nil 88 | expect(pair.group_id).to eq 'sg-ddccbbaa' 89 | end 90 | 91 | its(:egress_permissions) do 92 | egress = sg.egress_permissions[0] 93 | expect(egress.ip_protocol).to eq '-1' 94 | expect(egress.from_port).to eq nil 95 | expect(egress.to_port).to eq nil 96 | expect(egress.user_id_group_pairs).to eq [] 97 | expect(egress.prefix_list_ids).to eq [] 98 | 99 | range = egress.ip_ranges[0] 100 | expect(range.cidr_ip).to eq '0.0.0.0/0' 101 | end 102 | 103 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 104 | 105 | its(:tags) do 106 | tag = sg.tags[0] 107 | expect(tag.key).to eq 'Name' 108 | expect(tag.value).to eq 'test-group' 109 | end 110 | 111 | it { should be_accessible_from('0.0.0.0/0') } 112 | end 113 | end 114 | 115 | RSpec.context 'Security Group has no inbound rules' do 116 | ec2.stub_responses( 117 | :describe_security_groups, 118 | security_groups: [ 119 | security_group(ip_permissions: []) 120 | ] 121 | ) 122 | 123 | describe EC2::SecurityGroup.new('sg-aabbccdd', ec2) do 124 | it { should_not be_accessible_from('0.0.0.0/0') } 125 | end 126 | end 127 | 128 | RSpec.context 'Security Group allows connection from within a VPC' do 129 | ec2.stub_responses( 130 | :describe_security_groups, 131 | security_groups: [ 132 | security_group(ip_permissions: [rule('10.0.0.1/16')]) 133 | ] 134 | ) 135 | 136 | describe EC2::SecurityGroup.new('sg-aabbccdd', ec2) do 137 | it { should_not be_accessible_from('0.0.0.0/0') } 138 | it { should_not be_accessible_from('11.0.0.1/16') } 139 | it { should be_accessible_from('10.0.0.1/16') } 140 | it { should be_accessible_from('10.0.0.1/24') } 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/ec2/subnet_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | # stub Subnet 3 | ec2.stub_responses( 4 | :describe_subnets, 5 | subnets: [ 6 | { 7 | state: 'available', 8 | vpc_id: 'vpc-aabbccdd', 9 | cidr_block: '10.0.0.0/24', 10 | available_ip_address_count: 240, 11 | availability_zone: 'us-east-1a', 12 | default_for_az: true, 13 | map_public_ip_on_launch: true, 14 | tags: [ 15 | { 16 | key: 'Name', 17 | value: 'test-subnet' 18 | } 19 | ] 20 | } 21 | ] 22 | ) 23 | 24 | RSpec.describe subnet = EC2::Subnet.new('subnet-aabbccdd', ec2) do 25 | its(:to_s) { is_expected.to eq 'EC2 Subnet: subnet-aabbccdd' } 26 | 27 | it { is_expected.to be_available } 28 | it { is_expected.to be_az_default } 29 | it { is_expected.to be_with_public_ip_on_launch } 30 | 31 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 32 | its(:cidr_block) { is_expected.to eq '10.0.0.0/24' } 33 | its(:available_ip_address_count) { is_expected.to eq 240 } 34 | its(:availability_zone) { is_expected.to eq 'us-east-1a' } 35 | 36 | its(:tags) do 37 | tag = subnet.tags[0] 38 | expect(tag.key).to eq 'Name' 39 | expect(tag.value).to eq 'test-subnet' 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/ec2/vpc_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | # stub VPC 3 | ec2.stub_responses( 4 | :describe_vpcs, vpcs: [ 5 | { 6 | vpc_id: 'vpc-aabbccdd', 7 | state: 'available', 8 | cidr_block: '10.0.0.0/16', 9 | dhcp_options_id: 'dopt-aabbccdd', 10 | tags: [ 11 | { 12 | key: 'Name', 13 | value: 'test-vpc' 14 | } 15 | ], 16 | instance_tenancy: 'default', 17 | is_default: false 18 | } 19 | ] 20 | ) 21 | 22 | RSpec.describe vpc = EC2::VPC.new('vpc-aabbccdd', ec2) do 23 | its(:to_s) { is_expected.to eq 'EC2 VPC: vpc-aabbccdd' } 24 | 25 | it { is_expected.to be_available } 26 | it { is_expected.not_to be_default } 27 | it { is_expected.to be_default_tenancy } 28 | 29 | its(:cidr_block) { is_expected.to eq '10.0.0.0/16' } 30 | its(:dhcp_options_id) { is_expected.to eq 'dopt-aabbccdd' } 31 | 32 | its(:tags) do 33 | tag = vpc.tags[0] 34 | expect(tag.key).to eq 'Name' 35 | expect(tag.value).to eq 'test-vpc' 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/ec2/vpc_subnets_spec.rb: -------------------------------------------------------------------------------- 1 | ec2 = Aws::EC2::Client.new 2 | 3 | ec2.stub_responses( 4 | :describe_vpcs, vpcs: 5 | [ 6 | { 7 | vpc_id: 'vpc-aabbccdd', 8 | state: 'available', 9 | cidr_block: '10.0.0.0/16', 10 | dhcp_options_id: 'dopt-aabbccdd', 11 | tags: 12 | [ 13 | { 14 | key: 'Name', 15 | value: 'test-vpc' 16 | } 17 | ], 18 | instance_tenancy: 'default', 19 | is_default: false 20 | } 21 | ] 22 | ) 23 | 24 | ec2.stub_responses( 25 | :describe_subnets, subnets: 26 | [ 27 | { 28 | subnet_id: 'subnet-aaaaaa', 29 | vpc_id: 'vpc-aabbccdd', 30 | cidr_block: '10.0.1.0/24', 31 | available_ip_address_count: 255, 32 | availability_zone: 'us-east-1a', 33 | default_for_az: true, 34 | map_public_ip_on_launch: true, 35 | tags: [ 36 | { 37 | key: 'Name', 38 | value: 'test-subnet1a' 39 | } 40 | ] 41 | }, 42 | { 43 | subnet_id: 'subnet-aaaaab', 44 | vpc_id: 'vpc-aabbccdd', 45 | cidr_block: '10.0.1.0/24', 46 | available_ip_address_count: 255, 47 | availability_zone: 'us-east-1b', 48 | default_for_az: true, 49 | map_public_ip_on_launch: true, 50 | tags: [ 51 | { 52 | key: 'Name', 53 | value: 'test-subnet1b' 54 | } 55 | ] 56 | } 57 | ] 58 | ) 59 | 60 | RSpec.describe vpc = EC2::VPC.new('vpc-aabbccdd', ec2) do 61 | describe subnets = vpc.subnets do 62 | its(:to_s) do 63 | is_expected.to eq 'EC2 Subnets: ["subnet-aaaaaa", "subnet-aaaaab"]' 64 | end 65 | 66 | it { should be_evenly_spread_across_minimum_az(2) } 67 | 68 | subnets.each do |subnet| 69 | describe subnet do 70 | its(:availability_zone) { should start_with 'us-east-1' } 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/elasticloadbalancing/load_balancer_spec.rb: -------------------------------------------------------------------------------- 1 | elb1 = Aws::ElasticLoadBalancing::Client.new 2 | elb1.stub_responses( 3 | :describe_load_balancers, 4 | load_balancer_descriptions: [ 5 | { 6 | dns_name: 'internal-test-elb1-000000000.us-east-1.elb.amazonaws.com', 7 | canonical_hosted_zone_name: nil, 8 | canonical_hosted_zone_name_id: 'AAAAAAAAAAAAAA', 9 | listener_descriptions: [ 10 | { 11 | listener: { 12 | protocol: 'TCP', 13 | load_balancer_port: 80, 14 | instance_protocol: 'TCP', 15 | instance_port: 80, 16 | ssl_certificate_id: nil 17 | }, 18 | policy_names: [] 19 | } 20 | ], 21 | policies: {}, 22 | availability_zones: ['us-east-1a', 'us-east-1b'], 23 | subnets: ['subnet-aabbccdd', 'subnet-ddccbbaa'], 24 | vpc_id: 'vpc-aabbccdd', 25 | instances: [ 26 | { 27 | instance_id: 'i-aabbccdd' 28 | }, 29 | { 30 | instance_id: 'i-ddccbbaa' 31 | } 32 | ], 33 | health_check: { 34 | target: 'HTTP:80/health_check', 35 | interval: 10, 36 | timeout: 5, 37 | unhealthy_threshold: 2, 38 | healthy_threshold: 5 39 | }, 40 | source_security_group: { 41 | owner_alias: '111111111111', 42 | group_name: 'test-group' 43 | }, 44 | security_groups: ['sg-aabbccdd', 'sg-ddccbbaa'], 45 | scheme: 'internal' 46 | } 47 | ] 48 | ) 49 | 50 | elb2 = Aws::ElasticLoadBalancing::Client.new 51 | elb2.stub_responses( 52 | :describe_load_balancers, 53 | load_balancer_descriptions: [ 54 | { 55 | scheme: 'internet-facing' 56 | } 57 | ] 58 | ) 59 | 60 | RSpec.describe elb1 = ElasticLoadBalancing::LoadBalancer.new( 61 | 'test-elb1', 62 | elb1 63 | ) do 64 | its(:to_s) do 65 | is_expected.to eq 'ElasticLoadBalancing LoadBalancer: test-elb1' 66 | end 67 | 68 | it { is_expected.to be_internal } 69 | 70 | its(:dns_name) do 71 | is_expected.to eq 'internal-test-elb1-000000000.us-east-1.elb.amazonaws.com' 72 | end 73 | 74 | its(:canonical_hosted_zone_name) { is_expected.to eq nil } 75 | its(:canonical_hosted_zone_name_id) { is_expected.to eq 'AAAAAAAAAAAAAA' } 76 | 77 | its(:listeners) do 78 | listen = elb1.listeners[0].listener 79 | expect(listen.protocol).to eq 'TCP' 80 | expect(listen.load_balancer_port).to eq 80 81 | expect(listen.instance_protocol).to eq 'TCP' 82 | expect(listen.instance_port).to eq 80 83 | expect(listen.ssl_certificate_id).to eq nil 84 | expect(elb1.listeners[0].policy_names).to eq [] 85 | end 86 | 87 | its(:policies) do 88 | pol = elb1.policies 89 | expect(pol.app_cookie_stickiness_policies).to eq [] 90 | expect(pol.lb_cookie_stickiness_policies).to eq [] 91 | expect(pol.other_policies).to eq [] 92 | end 93 | 94 | its(:backend_server_descriptions) { is_expected.to eq [] } 95 | its(:availability_zones) { is_expected.to eq ['us-east-1a', 'us-east-1b'] } 96 | its(:subnets) { is_expected.to eq ['subnet-aabbccdd', 'subnet-ddccbbaa'] } 97 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 98 | its(:instances) { is_expected.to eq ['i-aabbccdd', 'i-ddccbbaa'] } 99 | 100 | its(:health_check) do 101 | hc = elb1.health_check 102 | expect(hc.target).to eq 'HTTP:80/health_check' 103 | expect(hc.interval).to eq 10 104 | expect(hc.timeout).to eq 5 105 | expect(hc.unhealthy_threshold).to eq 2 106 | expect(hc.healthy_threshold).to eq 5 107 | end 108 | 109 | its(:source_security_group) do 110 | ssg = elb1.source_security_group 111 | expect(ssg.owner_alias).to eq '111111111111' 112 | expect(ssg.group_name).to eq 'test-group' 113 | end 114 | 115 | its(:security_groups) { is_expected.to eq ['sg-aabbccdd', 'sg-ddccbbaa'] } 116 | end 117 | 118 | RSpec.describe ElasticLoadBalancing::LoadBalancer.new( 119 | 'test-elb2', 120 | elb2 121 | ) do 122 | its(:to_s) do 123 | is_expected.to eq 'ElasticLoadBalancing LoadBalancer: test-elb2' 124 | end 125 | 126 | it { is_expected.to be_internet_facing } 127 | end 128 | -------------------------------------------------------------------------------- /spec/rds/db_instance_spec.rb: -------------------------------------------------------------------------------- 1 | rds = Aws::RDS::Client.new 2 | ec2 = Aws::EC2::Client.new 3 | 4 | db_stub_response = { 5 | db_instance_class: 'db.t2.micro', 6 | engine: 'postgres', 7 | db_instance_status: 'available', 8 | master_username: 'test-username', 9 | db_name: 'test-database', 10 | endpoint: { 11 | address: 'test-rds-db.aaaaaaaaaaaa.us-east-1.rds.amazonaws.com' 12 | }, 13 | allocated_storage: 5, 14 | preferred_backup_window: '23:00-00:00', 15 | backup_retention_period: 1, 16 | db_security_groups: [], 17 | vpc_security_groups: [ 18 | { 19 | vpc_security_group_id: 'sg-aabbccdd', 20 | status: 'active' 21 | }, 22 | { 23 | vpc_security_group_id: 'sg-ddccbbaa', 24 | status: 'active' 25 | } 26 | ], 27 | db_parameter_groups: [ 28 | { 29 | db_parameter_group_name: 'default.postgres9.3', 30 | parameter_apply_status: 'in-sync' 31 | } 32 | ], 33 | availability_zone: 'us-east-1a', 34 | db_subnet_group: { 35 | db_subnet_group_name: 'test-subnet-group', 36 | db_subnet_group_description: 'test-subnet-group desc', 37 | vpc_id: 'vpc-aabbccdd', 38 | subnet_group_status: 'Complete', 39 | subnets: [ 40 | { 41 | subnet_identifier: 'subnet-aabbccdd', 42 | subnet_availability_zone: { 43 | name: 'us-east-1a' 44 | }, 45 | subnet_status: 'Active' 46 | }, 47 | { 48 | subnet_identifier: 'subnet-ddccbbcc', 49 | subnet_availability_zone: { 50 | name: 'us-east-1b' 51 | }, 52 | subnet_status: 'Active' 53 | } 54 | ] 55 | }, 56 | preferred_maintenance_window: 'sun:00:00-sun:01:00', 57 | pending_modified_values: { 58 | db_instance_class: nil, 59 | allocated_storage: nil, 60 | master_user_password: nil, 61 | port: nil, 62 | backup_retention_period: nil, 63 | multi_az: nil, 64 | engine_version: nil, 65 | iops: nil, 66 | db_instance_identifier: nil, 67 | storage_type: nil, 68 | ca_certificate_identifier: nil 69 | }, 70 | multi_az: true, 71 | engine_version: '9.3.3', 72 | auto_minor_version_upgrade: true, 73 | read_replica_source_db_instance_identifier: nil, 74 | read_replica_db_instance_identifiers: [], 75 | license_model: 'postgresql-license', 76 | iops: 100, 77 | option_group_memberships: [ 78 | { 79 | option_group_name: 'default:postgres-9-3', 80 | status: 'in-sync' 81 | } 82 | ], 83 | character_set_name: nil, 84 | secondary_availability_zone: 'us-east-1b', 85 | publicly_accessible: true, 86 | status_infos: [], 87 | storage_type: 'gp2', 88 | tde_credential_arn: nil, 89 | storage_encrypted: true, 90 | kms_key_id: nil, 91 | dbi_resource_id: 'db-AAAAAAAAAAAAAAAAAAAAAAAAAA', 92 | ca_certificate_identifier: 'rds-ca-2015' 93 | } 94 | 95 | # rubocop:disable Metrics/MethodLength 96 | def security_group(id, overrides = {}) 97 | template = { 98 | owner_id: '000000000000000000000', 99 | group_name: 'test-group', 100 | group_id: id, 101 | description: 'test-group description', 102 | ip_permissions: [], 103 | ip_permissions_egress: [ 104 | { 105 | ip_protocol: '-1', 106 | from_port: nil, 107 | to_port: nil, 108 | user_id_group_pairs: [], 109 | ip_ranges: [ 110 | { 111 | cidr_ip: '0.0.0.0/0' 112 | } 113 | ], 114 | prefix_list_ids: [] 115 | } 116 | ], 117 | vpc_id: 'vpc-aabbccdd', 118 | tags: [ 119 | { 120 | key: 'Name', 121 | value: 'test-group' 122 | } 123 | ] 124 | } 125 | 126 | stub_response(template, overrides) 127 | end 128 | # rubocop:enable Metrics/MethodLength 129 | 130 | def rule(cidr) 131 | { 132 | ip_protocol: 'tcp', 133 | from_port: 3306, 134 | to_port: 3306, 135 | user_id_group_pairs: [], 136 | ip_ranges: [ 137 | { cidr_ip: cidr } 138 | ], 139 | prefix_list_ids: [] 140 | } 141 | end 142 | 143 | def stub_security_groups(ec2, sg_aabbccdd, sg_ddccbbaa) 144 | ec2.stub_responses( 145 | :describe_security_groups, 146 | [ 147 | { security_groups: [sg_aabbccdd] }, 148 | { security_groups: [sg_ddccbbaa] } 149 | ] 150 | ) 151 | end 152 | 153 | rds.stub_responses( 154 | :describe_db_instances, 155 | db_instances: [ 156 | db_stub_response 157 | ] 158 | ) 159 | 160 | RSpec.describe db_inst = RDS::DBInstance.new('test-rds-db', rds) do 161 | its(:to_s) { is_expected.to eq 'RDS DBInstance: test-rds-db' } 162 | 163 | it { is_expected.to be_available } 164 | it { is_expected.to be_multi_az } 165 | it { is_expected.to be_auto_minor_version_upgradeable } 166 | it { is_expected.to be_publicly_accessible } 167 | it { is_expected.to be_with_encrypted_storage } 168 | 169 | its(:instance_class) { is_expected.to eq 'db.t2.micro' } 170 | its(:engine) { is_expected.to eq 'postgres' } 171 | its(:master_username) { is_expected.to eq 'test-username' } 172 | its(:database_name) { is_expected.to eq 'test-database' } 173 | 174 | its(:endpoint) do 175 | is_expected.to eq 'test-rds-db.aaaaaaaaaaaa.us-east-1.rds.amazonaws.com' 176 | end 177 | 178 | its(:allocated_storage) { is_expected.to eq 5 } 179 | its(:preferred_backup_window) { is_expected.to eq '23:00-00:00' } 180 | its(:backup_retention_period) { is_expected.to eq 1 } 181 | its(:security_groups) { is_expected.to eq [] } 182 | its(:vpc_security_groups) { is_expected.to eq ['sg-aabbccdd', 'sg-ddccbbaa'] } 183 | 184 | its(:parameter_groups) do 185 | pg = db_inst.parameter_groups[0] 186 | expect(pg.db_parameter_group_name).to eq 'default.postgres9.3' 187 | expect(pg.parameter_apply_status).to eq 'in-sync' 188 | end 189 | 190 | its(:availability_zone) { is_expected.to eq 'us-east-1a' } 191 | 192 | its(:subnet_group) do 193 | group = db_inst.subnet_group 194 | expect(group.db_subnet_group_name).to eq 'test-subnet-group' 195 | expect(group.db_subnet_group_description).to eq 'test-subnet-group desc' 196 | expect(group.vpc_id).to eq 'vpc-aabbccdd' 197 | expect(group.subnet_group_status).to eq 'Complete' 198 | 199 | subnet0 = group.subnets[0] 200 | expect(subnet0.subnet_identifier).to eq 'subnet-aabbccdd' 201 | expect(subnet0.subnet_availability_zone.name).to eq 'us-east-1a' 202 | expect(subnet0.subnet_status).to eq 'Active' 203 | 204 | subnet1 = group.subnets[1] 205 | expect(subnet1.subnet_identifier).to eq 'subnet-ddccbbcc' 206 | expect(subnet1.subnet_availability_zone.name).to eq 'us-east-1b' 207 | expect(subnet1.subnet_status).to eq 'Active' 208 | end 209 | 210 | its(:preferred_maintenance_window) { is_expected.to eq 'sun:00:00-sun:01:00' } 211 | 212 | its(:pending_modified_values) do 213 | pmv = db_inst.pending_modified_values 214 | expect(pmv.db_instance_class).to eq nil 215 | expect(pmv.allocated_storage).to eq nil 216 | expect(pmv.master_user_password).to eq nil 217 | expect(pmv.port).to eq nil 218 | expect(pmv.backup_retention_period).to eq nil 219 | expect(pmv.multi_az).to eq nil 220 | expect(pmv.engine_version).to eq nil 221 | expect(pmv.iops).to eq nil 222 | expect(pmv.db_instance_identifier).to eq nil 223 | expect(pmv.storage_type).to eq nil 224 | expect(pmv.ca_certificate_identifier).to eq nil 225 | end 226 | 227 | its(:read_replica_source_db_instance_identifier) { is_expected.to eq nil } 228 | its(:read_replica_db_instance_identifiers) { is_expected.to eq [] } 229 | its(:engine_version) { is_expected.to eq '9.3.3' } 230 | its(:license_model) { is_expected.to eq 'postgresql-license' } 231 | its(:iops) { is_expected.to eq 100 } 232 | 233 | its(:option_group_memberships) do 234 | ogm = db_inst.option_group_memberships[0] 235 | expect(ogm.option_group_name).to eq 'default:postgres-9-3' 236 | expect(ogm.status).to eq 'in-sync' 237 | end 238 | 239 | its(:character_set_name) { is_expected.to eq nil } 240 | its(:secondary_availability_zone) { is_expected.to eq 'us-east-1b' } 241 | its(:status_infos) { is_expected.to eq [] } 242 | its(:storage_type) { is_expected.to eq 'gp2' } 243 | its(:tde_credential_arn) { is_expected.to eq nil } 244 | its(:kms_key_id) { is_expected.to eq nil } 245 | its(:dbi_resource_id) { is_expected.to eq 'db-AAAAAAAAAAAAAAAAAAAAAAAAAA' } 246 | its(:ca_certificate_identifier) { is_expected.to eq 'rds-ca-2015' } 247 | end 248 | 249 | RSpec.describe 'DBInstance#accessible_from?' do 250 | context 'The database is publicly accessible' do 251 | before(:each) do 252 | db_stub_response[:publicly_accessible] = true 253 | 254 | rds.stub_responses( 255 | :describe_db_instances, 256 | db_instances: [ 257 | db_stub_response 258 | ] 259 | ) 260 | end 261 | 262 | context 'Security Group Rule allows ingress from anywhere' do 263 | before(:each) do 264 | stub_security_groups( 265 | ec2, 266 | security_group('sg-aabbccdd', ip_permissions: [rule('0.0.0.0/0')]), 267 | security_group('sg-ddccbbaa') 268 | ) 269 | 270 | # Cannot use this as part of a describe statement, 271 | # because stubbing ec2 multiple times 272 | db_inst = RDS::DBInstance.new('test-rds-db', rds, ec2) 273 | end 274 | 275 | it 'should be accessible from anywhere' do 276 | expect(db_inst).to be_accessible_from('0.0.0.0/0') 277 | end 278 | end 279 | end 280 | 281 | context 'The database is not publicly accessible' do 282 | before(:each) do 283 | db_stub_response[:publicly_accessible] = false 284 | rds.stub_responses( 285 | :describe_db_instances, 286 | db_instances: [ 287 | db_stub_response 288 | ] 289 | ) 290 | 291 | db_inst = RDS::DBInstance.new('test-rds-db', rds, ec2) 292 | end 293 | 294 | context 'Security Group Rule allows ingress from public CIDR' do 295 | before(:each) do 296 | stub_security_groups( 297 | ec2, 298 | security_group('sg-aabbccdd', ip_permissions: [rule('52.1.0.0/16')]), 299 | security_group('sg-ddccbbaa') 300 | ) 301 | end 302 | 303 | it 'should be accessible' do 304 | expect(db_inst).not_to be_accessible_from('52.1.0.0/16') 305 | end 306 | end 307 | 308 | context 'Security Groups Rule allows ingress from private CIDR' do 309 | before(:each) do 310 | stub_security_groups( 311 | ec2, 312 | security_group('sg-aabbccdd', ip_permissions: [rule('10.0.0.0/8')]), 313 | security_group('sg-ddccbbaa') 314 | ) 315 | end 316 | 317 | it 'should be accessible from the entire CIDR range' do 318 | expect(db_inst).to be_accessible_from('10.0.0.0/8') 319 | end 320 | 321 | it 'should be accessible from a subset of the CIDR range' do 322 | expect(db_inst).to be_accessible_from('10.1.1.0/24') 323 | end 324 | end 325 | end 326 | end 327 | -------------------------------------------------------------------------------- /spec/redshift/cluster_spec.rb: -------------------------------------------------------------------------------- 1 | rs = Aws::Redshift::Client.new 2 | rs.stub_responses( 3 | :describe_clusters, 4 | clusters: [ 5 | { 6 | cluster_status: 'available', 7 | allow_version_upgrade: true, 8 | publicly_accessible: true, 9 | encrypted: true, 10 | node_type: 'dw2.large', 11 | modify_status: nil, 12 | master_username: 'test-user', 13 | db_name: 'test-database', 14 | endpoint: { 15 | address: 'test-cluster.aaaaaaaaaaaa.us-east-1.amazonaws.com', 16 | port: 5439 17 | }, 18 | automated_snapshot_retention_period: 7, 19 | cluster_security_groups: [], 20 | vpc_security_groups: [ 21 | { 22 | vpc_security_group_id: 'sg-aabbccdd', 23 | status: 'active' 24 | }, 25 | { 26 | vpc_security_group_id: 'sg-ddccbbaa', 27 | status: 'active' 28 | } 29 | ], 30 | cluster_parameter_groups: [ 31 | { 32 | parameter_group_name: 'test-parameter-group' 33 | } 34 | ], 35 | cluster_subnet_group_name: 'test-subnet-group', 36 | vpc_id: 'vpc-aabbccdd', 37 | availability_zone: 'us-east-1a', 38 | preferred_maintenance_window: 'wed:23:30-thu:00:00', 39 | pending_modified_values: { 40 | master_user_password: nil, 41 | node_type: nil, 42 | number_of_nodes: nil, 43 | cluster_type: nil, 44 | cluster_version: nil, 45 | automated_snapshot_retention_period: nil, 46 | cluster_identifier: nil 47 | }, 48 | cluster_version: '1.0', 49 | number_of_nodes: 3, 50 | restore_status: nil, 51 | hsm_status: nil, 52 | cluster_snapshot_copy_status: nil, 53 | cluster_public_key: 'ssh-rsa AAAAbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 54 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 55 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 56 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 57 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 58 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb '\ 59 | 'Amazon-Redshift\n', 60 | cluster_nodes: [ 61 | { 62 | node_role: 'SHARED', 63 | private_ip_address: '10.0.0.10', 64 | public_ip_address: '55.55.55.55' 65 | } 66 | ], 67 | cluster_revision_number: '934', 68 | tags: [ 69 | { 70 | key: 'Name', 71 | value: 'test-cluster' 72 | } 73 | ], 74 | kms_key_id: nil 75 | } 76 | ] 77 | ) 78 | 79 | RSpec.describe cluster = Redshift::Cluster.new('test-cluster', rs) do 80 | its(:to_s) { is_expected.to eq 'Redshift Cluster: test-cluster' } 81 | 82 | it { is_expected.to be_available } 83 | it { is_expected.to be_version_upgradeable } 84 | it { is_expected.to be_publicly_accessible } 85 | it { is_expected.to be_encrypted } 86 | 87 | its(:node_type) { is_expected.to eq 'dw2.large' } 88 | its(:modify_status) { is_expected.to eq nil } 89 | its(:master_username) { is_expected.to eq 'test-user' } 90 | its(:database_name) { is_expected.to eq 'test-database' } 91 | 92 | its(:endpoint) do 93 | is_expected.to eq 'test-cluster.aaaaaaaaaaaa.us-east-1.amazonaws.com' 94 | end 95 | 96 | its(:listening_port) { is_expected.to eq 5439 } 97 | its(:automated_snapshot_retention_period) { is_expected.to eq 7 } 98 | its(:security_groups) { is_expected.to eq [] } 99 | its(:vpc_security_groups) { is_expected.to eq ['sg-aabbccdd', 'sg-ddccbbaa'] } 100 | its(:parameter_groups) { is_expected.to eq ['test-parameter-group'] } 101 | its(:subnet_group) { is_expected.to eq 'test-subnet-group' } 102 | its(:vpc_id) { is_expected.to eq 'vpc-aabbccdd' } 103 | its(:availability_zone) { is_expected.to eq 'us-east-1a' } 104 | its(:preferred_maintenance_window) { is_expected.to eq 'wed:23:30-thu:00:00' } 105 | 106 | its(:pending_modified_values) do 107 | pmv = cluster.pending_modified_values 108 | expect(pmv.master_user_password).to eq nil 109 | expect(pmv.node_type).to eq nil 110 | expect(pmv.number_of_nodes).to eq nil 111 | expect(pmv.cluster_type).to eq nil 112 | expect(pmv.cluster_version).to eq nil 113 | expect(pmv.automated_snapshot_retention_period).to eq nil 114 | expect(pmv.cluster_identifier).to eq nil 115 | end 116 | 117 | its(:version) { is_expected.to eq '1.0' } 118 | its(:number_of_nodes) { is_expected.to eq 3 } 119 | its(:restore_status) { is_expected.to eq nil } 120 | its(:hsm_status) { is_expected.to eq nil } 121 | its(:snapshot_copy_status) { is_expected.to eq nil } 122 | 123 | its(:public_key) do 124 | is_expected.to eq 'ssh-rsa AAAAbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 125 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 126 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 127 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 128 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\ 129 | 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb Amazon-Redshift\n' 130 | end 131 | 132 | its(:nodes) do 133 | node = cluster.nodes[0] 134 | expect(node.node_role).to eq 'SHARED' 135 | expect(node.private_ip_address).to eq '10.0.0.10' 136 | expect(node.public_ip_address).to eq '55.55.55.55' 137 | end 138 | 139 | its(:elastic_ip_status) { is_expected.to eq nil } 140 | its(:revision_number) { is_expected.to eq '934' } 141 | 142 | its(:tags) do 143 | tag = cluster.tags[0] 144 | expect(tag.key).to eq 'Name' 145 | expect(tag.value).to eq 'test-cluster' 146 | end 147 | 148 | its(:kms_key_id) { is_expected.to eq nil } 149 | end 150 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 2 | RSpec.configure do |config| 3 | config.expect_with :rspec do |expectations| 4 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 5 | end 6 | 7 | config.mock_with :rspec do |mocks| 8 | mocks.verify_partial_doubles = true 9 | end 10 | 11 | config.disable_monkey_patching! 12 | config.default_formatter = 'doc' if config.files_to_run.one? 13 | config.profile_examples = 5 14 | config.order = :random 15 | Kernel.srand config.seed 16 | end 17 | 18 | require_relative '../lib/serverspec-aws' 19 | # rubocop:disable Style/MixinUsage 20 | include Serverspec::Type::AWS 21 | # rubocop:enable Style/MixinUsage 22 | set :backend, :exec 23 | 24 | # without this bit, the SDK is painfully slow 25 | Aws.config.update( 26 | region: 'us-east-1', 27 | credentials: Aws::Credentials.new('akid', 'secret') 28 | ) 29 | 30 | # the client stubbing makes these tests possible 31 | Aws.config[:stub_responses] = true 32 | 33 | def stub_response(template, with) 34 | with.each_key do |key| 35 | template[key] = with[key] 36 | end 37 | 38 | template 39 | end 40 | --------------------------------------------------------------------------------