diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/Gemfile b/Gemfile index dbbbcee..5a0d621 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,7 @@ source 'https://rubygems.org' gem 'aws-sdk', '~> 2.0.22' gem 'uuid', '~> 2.3.7' + +group :test do + gem 'rspec', '~> 3.2' +end diff --git a/README.md b/README.md index 7579032..24011f8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can also install the SDK directly with: ## Basic Configuration You need to set up your AWS security credentials before the sample code is able -to connect to AWS. You can do this by creating a file named "credentials" at ~/.aws/ +to connect to AWS. You can do this by creating a file named "credentials" at ~/.aws/ (C:\Users\USER_NAME\.aws\ for Windows users) and saving the following lines in the file: [default] @@ -37,6 +37,28 @@ you need to do is run it: The S3 documentation has a good overview of the [restrictions for bucket names](http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html) for when you start making your own buckets. +## Running the InstanceManager sample + +This sample application connects to Amazon's [Elastic Compute Cloud (EC2)](http://aws.amazon.com/ec2/), +lists the available instances, filters them based on Name tag, and then prints +useful information about each instance. + +All you need to do is run it: + + ruby list_instances.rb + +The default regular expression will probably not match any instances in your +account. To list all instances run: + + ruby list_instances.rb '.*' + +RSpec tests for `InstanceManager` are included to demonstrate use of +[ClientStubs](http://docs.aws.amazon.com/sdkforruby/api/Aws/ClientStubs.html). + +Execute rspec to run all the tests: + + rspec + ## License This sample application is distributed under the diff --git a/lib/instance_manager.rb b/lib/instance_manager.rb new file mode 100644 index 0000000..98f3245 --- /dev/null +++ b/lib/instance_manager.rb @@ -0,0 +1,42 @@ +require "aws-sdk" + +# An example using AWS Ruby SDK v2 to list and filter EC2 instances. +class InstanceManager + # By default manage EC2 instance whose Name tag matches this pattern. + DEFAULT_NAME_RE = /^example-\d+$/ + + attr_reader :ec2_resource + attr_reader :name_re + + # @param instance [Aws::EC2::Instance] + # @return [String] Instance name as judged by its Name tag. + def self.instance_name(instance) + name_tag = instance.tags.find { |t| t.key == "Name" } + name_tag.value if name_tag + end + + # @param ec2_client [Aws::EC2::Client] + # @param name_re [Regexp] Manage EC2 instance whose Name tag matches this + # pattern. + def initialize(ec2_client, name_re = DEFAULT_NAME_RE) + @ec2_resource = Aws::EC2::Resource.new(:client => ec2_client) + @name_re = name_re + end + + # @return [Array] Instance whose Name tag matches the + # given pattern. + def instances + ec2_resource.instances.select { |i| name_matches?(i) } + end + + protected + + # @param instance [Aws::EC2::Instance] + # @return [Boolean] true if the given instance has a Name tag that matches + # the requested pattern. + def name_matches?(instance) + name = self.class.instance_name(instance) + name && name.match(name_re) + end + +end diff --git a/list_instances.rb b/list_instances.rb new file mode 100755 index 0000000..4cd08a0 --- /dev/null +++ b/list_instances.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "instance_manager" + +name_re = Regexp.new(ARGV[0] || InstanceManager::DEFAULT_NAME_RE) +client = Aws::EC2::Client.new(:region => 'us-east-1') +manager = InstanceManager.new(client, name_re) + +puts "Listing instances with Name tag matching #{manager.name_re}" + +manager.instances.each do |instance| + name = InstanceManager.instance_name(instance) + # Stopped instances do not have any private IP address. + private_ip_display = " (#{instance.private_ip_address})" if instance.private_ip_address + puts "#{name} (#{instance.id}) => #{instance.state.name}#{private_ip_display}" +end diff --git a/spec/ec2_stub_spec.rb b/spec/ec2_stub_spec.rb new file mode 100644 index 0000000..def3f89 --- /dev/null +++ b/spec/ec2_stub_spec.rb @@ -0,0 +1,29 @@ +require "aws-sdk" + +RSpec.describe Aws::EC2 do + let(:client) { Aws::EC2::Client.new(:region => 'us-east-1', :stub_responses => true) } + let(:resource) { Aws::EC2::Resource.new(:client => client) } + + context "when describe_instances is stubbed to return a single instance" do + before do + client.stub_responses( + :describe_instances, { + :next_token => nil, + :reservations => [{:instances=>[{:instance_id => "i-1", :state => {:name => "running"}, :tags =>[{:key => "Name", :value => "example-1" }]}]}] + } + ) + + end + context Aws::EC2::Client do + it "#describe_instances to return 1 instance" do + expect(client.describe_instances.reservations.first.instances.size).to eq 1 + end + end + + context Aws::EC2::Resource do + it "#instances to return 1 instance" do + expect(resource.instances.to_a.size).to eq 1 + end + end + end +end diff --git a/spec/instance_manager_spec.rb b/spec/instance_manager_spec.rb new file mode 100644 index 0000000..401aa3d --- /dev/null +++ b/spec/instance_manager_spec.rb @@ -0,0 +1,59 @@ +require "instance_manager" +RSpec.describe InstanceManager do + let(:ec2_client) do + Aws::EC2::Client.new(:region => 'us-east-1', :stub_responses => true) + end + + subject do + described_class.new(ec2_client) + end + + context "#instances" do + before do + ec2_client.stub_responses( + :describe_instances, + { + :next_token => nil, + :reservations => [{ :instances => [ + { + :instance_id => "i-1", + :state => {:name => "running" }, + :tags => [ + { :key => "Name", :value => "example-1" }, + { :key => "color", :value => "red" }, + ] + }, + { + :instance_id => "i-2", + :state => {:name => "running" }, + :tags => [ + { :key => "Name", :value => "example-2" }, + { :key => "color", :value => "green" }, + ] + }, + { + :instance_id => "i-3", + :state => {:name => "running" }, + :tags => [ + { :key => "Name", :value => "webapp-3" }, + { :key => "color", :value => "green" }, + ] + }, + { + :instance_id => "i-4", + :state => {:name => "stopped" }, + :tags => [], + }, + ]}]}, + ) + end + + it "returns two matching EC2 instances" do + expect(subject.instances.to_a.size).to eq 2 + end + + it "returns only EC2 instances with matching Name tag" do + expect(subject.instances.map(&:id)).to eq ["i-1", "i-2"] + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..4ce0464 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,15 @@ +# This is the configuration recommended by rspec --init with RSpec 3.2.0 +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + config.filter_run :focus + config.run_all_when_everything_filtered = true + config.disable_monkey_patching! + config.default_formatter = 'doc' if config.files_to_run.one? + config.order = :random + Kernel.srand config.seed +end