cyoi 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ChangeLog.md ADDED
@@ -0,0 +1,14 @@
1
+ # Change Log
2
+
3
+ Cyoi (choose-your-own-infrastructure) is a library to ask an end-user to choose an infrastructure (AWS, OpenStack, etc), region, and login credentials.
4
+
5
+ ## v0.2
6
+
7
+ * executable `cyoi` became `cyoi provider`
8
+ * added `cyoi address` to prompt or provision an IP address (AWS only at moment)
9
+
10
+ ## v0.1
11
+
12
+ Initial release
13
+
14
+ * executable `cyoi [settings.yml]` - asks for provider information and stores in settings.yml (AWS & OpenStack)
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard 'rspec' do
2
2
  watch(%r{^spec})
3
- watch(%r{^lib/cyoi}) { |m| "spec" }
4
- # watch(%r{^lib/cyoi/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
3
+ # watch(%r{^lib/cyoi}) { |m| "spec" }
4
+ watch(%r{^lib/cyoi/cli/(?:|provider_)address}) { |m| "spec/integration/cli/address" }
5
5
  end
6
6
 
data/README.md CHANGED
@@ -41,7 +41,14 @@ Confirming: Using aws/us-west-2
41
41
 
42
42
  ## Usage
43
43
 
44
- ```
44
+ ``` ruby
45
+ provider_cli = Cyoi::Cli::Provider.new([settings_dir])
46
+ provider_cli.execute!
47
+ settings = YAML.load_file(File.join(settings_dir, "settings.yml"))
48
+
49
+ settings["provider"]["name"] # aws, openstack
50
+ settings["provider"]["region"] # us-east-1
51
+ settings["provider"]["credentials"] # aws or openstack URLs & credentials
45
52
  ```
46
53
 
47
54
  ## Installation
data/Rakefile CHANGED
@@ -17,10 +17,16 @@ if defined?(RSpec)
17
17
  t.pattern = "spec/unit/**/*_spec.rb"
18
18
  t.rspec_opts = %w(--format progress --color)
19
19
  end
20
+
21
+ desc "Run AWS Integration Tests"
22
+ RSpec::Core::RakeTask.new(:integration) do |t|
23
+ t.pattern = "spec/integration/**/*_spec.rb"
24
+ t.rspec_opts = %w(--format progress --color)
25
+ end
20
26
  end
21
27
 
22
28
  desc "Run tests"
23
- task :spec => %w(spec:unit)
29
+ task :spec => %w(spec:unit spec:integration)
24
30
  end
25
31
 
26
32
  task :default => ["spec"]
data/bin/cyoi CHANGED
@@ -2,8 +2,26 @@
2
2
  $:.push File.dirname(__FILE__) + '/../lib'
3
3
 
4
4
  require "rubygems"
5
- require "cyoi/cli/provider"
6
5
 
7
6
  # interface assumed by aruba in-process testing
8
7
  # https://github.com/cucumber/aruba#testing-ruby-cli-programs-without-spawning-a-new-ruby-process
9
- Cyoi::Cli::Provider.new(ARGV.dup).execute!
8
+
9
+ section, *argv = ARGV.dup
10
+ cli = case (section || "").downcase.to_sym
11
+ when :provider
12
+ require "cyoi/cli/provider"
13
+ Cyoi::Cli::Provider.new(argv)
14
+ when :address
15
+ require "cyoi/cli/address"
16
+ Cyoi::Cli::Address.new(argv)
17
+ else
18
+ $stderr.puts "USAGE: cyoi [provider|address]"
19
+ exit 1
20
+ end
21
+
22
+ # TODO put this behind a flag --fog-mock
23
+ require "fog"
24
+ Fog.mock!
25
+
26
+ cli.show_settings
27
+ cli.execute!
data/cyoi.gemspec CHANGED
@@ -22,6 +22,7 @@ README
22
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
23
  spec.require_paths = ["lib"]
24
24
 
25
+ spec.add_dependency "fog"
25
26
  spec.add_dependency "highline", "~> 1.6"
26
27
  spec.add_dependency "settingslogic", "~> 2.0.9" # need to_nested_hash method
27
28
 
@@ -0,0 +1,59 @@
1
+ require "cyoi/cli"
2
+ require "cyoi/cli/auto_detection"
3
+ require "cyoi/cli/helpers"
4
+ class Cyoi::Cli::Address
5
+ include Cyoi::Cli::Helpers
6
+
7
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
8
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
9
+ @settings_dir = @argv.shift || "/tmp/provider_settings"
10
+ @settings_dir = File.expand_path(@settings_dir)
11
+ end
12
+
13
+ # TODO run Cyoi::Cli::Provider first if settings.provider.name missing
14
+ def execute!
15
+ unless settings.exists?("provider.name")
16
+ $stderr.puts("Please run 'cyoi provider' first")
17
+ exit 1
18
+ end
19
+ unless valid_address?
20
+ settings["address"] = address_cli.perform_and_return_attributes
21
+ save_settings!
22
+ end
23
+ address_cli.display_confirmation
24
+ end
25
+
26
+ # Continue the interactive session with the user
27
+ # specific to the address/infrastructure they have
28
+ # chosen.
29
+ #
30
+ # The returned object is a class from cyoi/cli/addresss/provier_cli_NAME.rb
31
+ # The class loads itself into `@address_clis` via `register_address_cli`
32
+ #
33
+ # Returns nil if settings.address.name not set
34
+ def address_cli
35
+ @address_cli ||= begin
36
+ provider_name = settings.exists?("provider.name")
37
+ return nil unless provider_name
38
+ require "cyoi/cli/provider_addresses/address_cli_#{settings.provider.name}"
39
+ klass = self.class.address_cli(settings.provider.name)
40
+ settings["address"] ||= {}
41
+ klass.new(provider_client, settings.address, hl)
42
+ end
43
+ end
44
+
45
+ def self.register_address_cli(name, klass)
46
+ @address_clis ||= {}
47
+ @address_clis[name] = klass
48
+ end
49
+
50
+ def self.address_cli(name)
51
+ @address_clis[name]
52
+ end
53
+
54
+ protected
55
+ def valid_address?
56
+ address_cli && address_cli.valid_address?
57
+ end
58
+
59
+ end
@@ -0,0 +1,14 @@
1
+ require "cyoi/providers"
2
+
3
+ module Cyoi::Cli::Helpers::Provider
4
+ def provider_client
5
+ @provider_client ||= begin
6
+ Cyoi::Providers.provider_client(settings.provider)
7
+ end
8
+ end
9
+
10
+ # If the +provider_client+ uses fog, then this will return its +fog_compute+ client object
11
+ def fog_compute
12
+ provider_client.respond_to?(:fog_compute) ? provider_client.fog_compute : nil
13
+ end
14
+ end
@@ -40,6 +40,10 @@ module Cyoi::Cli::Helpers::Settings
40
40
  settings.create_accessors!
41
41
  end
42
42
 
43
+ def show_settings
44
+ puts "Using settings file #{settings_path}"
45
+ end
46
+
43
47
  def migrate_old_settings
44
48
  end
45
49
  end
@@ -2,9 +2,11 @@ module Cyoi::Cli::Helpers
2
2
  end
3
3
 
4
4
  require "cyoi/cli/helpers/interactions"
5
+ require "cyoi/cli/helpers/provider"
5
6
  require "cyoi/cli/helpers/settings"
6
7
 
7
8
  module Cyoi::Cli::Helpers
8
9
  include Cyoi::Cli::Helpers::Interactions
10
+ include Cyoi::Cli::Helpers::Provider
9
11
  include Cyoi::Cli::Helpers::Settings
10
12
  end
@@ -0,0 +1,44 @@
1
+ module Cyoi::Cli::Addresses; end
2
+ class Cyoi::Cli::Addresses::AddressCliAws
3
+ attr_reader :provider_client
4
+ attr_reader :attributes
5
+ attr_reader :hl
6
+
7
+ def initialize(provider_client, attributes, highline)
8
+ @provider_client = provider_client
9
+ @hl = highline
10
+ @attributes = attributes.is_a?(Hash) ? Settingslogic.new(attributes) : attributes
11
+ raise "@attributes must be Settingslogic (or Hash)" unless @attributes.is_a?(Settingslogic)
12
+ end
13
+
14
+ def perform_and_return_attributes
15
+ unless valid_address?
16
+ provision_address
17
+ end
18
+ export_attributes
19
+ end
20
+
21
+ # helper to export the complete nested attributes.
22
+ def export_attributes
23
+ attributes.to_nested_hash
24
+ end
25
+
26
+
27
+ def valid_address?
28
+ attributes["ip"]
29
+ end
30
+
31
+ def display_confirmation
32
+ puts "\n"
33
+ puts "Confirming: Using address #{attributes.ip}"
34
+ end
35
+
36
+ protected
37
+ def provision_address
38
+ print "Acquiring a public IP address... "
39
+ attributes["ip"] = provider_client.provision_public_ip_address
40
+ puts attributes.ip
41
+ end
42
+ end
43
+
44
+ Cyoi::Cli::Address.register_address_cli("aws", Cyoi::Cli::Addresses::AddressCliAws)
@@ -0,0 +1,5 @@
1
+ # Providers
2
+
3
+ Currently these files are kept in sync with bosh-cloudfoundry project.
4
+
5
+ TODO - extract into shared library.
@@ -0,0 +1,168 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Cyoi; module Providers; module Clients; end; end; end
4
+
5
+ require "cyoi/providers/clients/fog_provider_client"
6
+ require "cyoi/providers/constants/aws_constants"
7
+
8
+ class Cyoi::Providers::Clients::AwsProviderClient < Cyoi::Providers::Clients::FogProviderClient
9
+ include Cyoi::Providers::Constants::AwsConstants
10
+
11
+ # @return [Integer] megabytes of RAM for requested flavor of server
12
+ def ram_for_server_flavor(server_flavor_id)
13
+ if flavor = fog_compute_flavor(server_flavor_id)
14
+ flavor[:ram]
15
+ else
16
+ raise "Unknown AWS flavor '#{server_flavor_id}'"
17
+ end
18
+ end
19
+
20
+ # @return [Hash] e.g. { :bits => 0, :cores => 2, :disk => 0,
21
+ # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
22
+ # or nil if +server_flavor_id+ is not a supported flavor ID
23
+ def fog_compute_flavor(server_flavor_id)
24
+ aws_compute_flavors.find { |fl| fl[:id] == server_flavor_id }
25
+ end
26
+
27
+ # @return [Array] of [Hash] for each supported compute flavor
28
+ # Example [Hash] { :bits => 0, :cores => 2, :disk => 0,
29
+ # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
30
+ def aws_compute_flavors
31
+ Fog::Compute::AWS::FLAVORS
32
+ end
33
+
34
+ def aws_compute_flavor_ids
35
+ aws_compute_flavors.map { |fl| fl[:id] }
36
+ end
37
+
38
+ # Provision an EC2 or VPC elastic IP addess.
39
+ # * VPC - provision_public_ip_address(vpc: true)
40
+ # * EC2 - provision_public_ip_address
41
+ # @return [String] provisions a new public IP address in target region
42
+ # TODO nil if none available
43
+ def provision_public_ip_address(options={})
44
+ if options.delete(:vpc)
45
+ options[:domain] = "vpc"
46
+ else
47
+ options[:domain] = options.delete(:domain) || "standard"
48
+ end
49
+ address = fog_compute.addresses.create(options)
50
+ address.public_ip
51
+ # TODO catch error and return nil
52
+ end
53
+
54
+ def associate_ip_address_with_server(ip_address, server)
55
+ address = fog_compute.addresses.get(ip_address)
56
+ address.server = server
57
+ end
58
+
59
+ def create_vpc(name, cidr_block)
60
+ vpc = fog_compute.vpcs.create(name: name, cidr_block: cidr_block)
61
+ vpc.id
62
+ end
63
+
64
+ # Creates a VPC subnet
65
+ # @return [String] the subnet_id
66
+ def create_subnet(vpc_id, cidr_block)
67
+ subnet = fog_compute.subnets.create(vpc_id: vpc_id, cidr_block: cidr_block)
68
+ subnet.subnet_id
69
+ end
70
+
71
+ def create_internet_gateway(vpc_id)
72
+ gateway = fog_compute.internet_gateways.create(vpc_id: vpc_id)
73
+ gateway.id
74
+ end
75
+
76
+ def find_server_device(server, device)
77
+ server.volumes.all.find {|v| v.device == device}
78
+ end
79
+
80
+ def create_and_attach_volume(name, disk_size, server, device)
81
+ volume = fog_compute.volumes.create(
82
+ size: disk_size,
83
+ name: name,
84
+ description: '',
85
+ device: device,
86
+ availability_zone: server.availability_zone)
87
+ # TODO: the following works in fog 1.9.0+ (but which has a bug in bootstrap)
88
+ # https://github.com/fog/fog/issues/1516
89
+ #
90
+ # volume.wait_for { volume.status == 'available' }
91
+ # volume.attach(server.id, "/dev/vdc")
92
+ # volume.wait_for { volume.status == 'in-use' }
93
+ #
94
+ # Instead, using:
95
+ volume.server = server
96
+ end
97
+
98
+ # Ubuntu 13.04
99
+ def raring_image_id(region=nil)
100
+ region = fog_compute.region
101
+ # http://cloud-images.ubuntu.com/locator/ec2/
102
+ image_id = case region.to_s
103
+ when "ap-northeast-1"
104
+ "ami-6b26ab6a"
105
+ when "ap-southeast-1"
106
+ "ami-2b511e79"
107
+ when "eu-west-1"
108
+ "ami-3d160149"
109
+ when "sa-east-1"
110
+ "ami-28e43e35"
111
+ when "us-east-1"
112
+ "ami-c30360aa"
113
+ when "us-west-1"
114
+ "ami-d383af96"
115
+ when "ap-southeast-2"
116
+ "ami-84a333be"
117
+ when "us-west-2"
118
+ "ami-bf1d8a8f"
119
+ end
120
+ image_id || raise("Please add Ubuntu 13.04 64bit (EBS) AMI image id to aws.rb#raring_image_id method for region '#{region}'")
121
+ end
122
+
123
+ def bootstrap(new_attributes = {})
124
+ new_attributes[:image_id] ||= raring_image_id(fog_compute.region)
125
+ vpc = new_attributes[:subnet_id]
126
+
127
+ server = fog_compute.servers.new(new_attributes)
128
+
129
+ unless new_attributes[:key_name]
130
+ raise "please provide :key_name attribute"
131
+ end
132
+ unless private_key_path = new_attributes.delete(:private_key_path)
133
+ raise "please provide :private_key_path attribute"
134
+ end
135
+
136
+ if vpc
137
+ # TODO setup security group on new server
138
+ else
139
+ # make sure port 22 is open in the first security group
140
+ security_group = fog_compute.security_groups.get(server.groups.first)
141
+ authorized = security_group.ip_permissions.detect do |ip_permission|
142
+ ip_permission['ipRanges'].first && ip_permission['ipRanges'].first['cidrIp'] == '0.0.0.0/0' &&
143
+ ip_permission['fromPort'] == 22 &&
144
+ ip_permission['ipProtocol'] == 'tcp' &&
145
+ ip_permission['toPort'] == 22
146
+ end
147
+ unless authorized
148
+ security_group.authorize_port_range(22..22)
149
+ end
150
+ end
151
+
152
+ server.save
153
+ unless Fog.mocking?
154
+ server.wait_for { ready? }
155
+ server.setup(:keys => [private_key_path])
156
+ end
157
+ server
158
+ end
159
+
160
+ # Construct a Fog::Compute object
161
+ # Uses +attributes+ which normally originates from +settings.provider+
162
+ def setup_fog_connection
163
+ configuration = Fog.symbolize_credentials(attributes.credentials)
164
+ configuration[:provider] = "AWS"
165
+ configuration[:region] = attributes.region
166
+ @fog_compute = Fog::Compute.new(configuration)
167
+ end
168
+ end
@@ -0,0 +1,161 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ require "fog"
4
+ module Cyoi; module Providers; module Clients; end; end; end
5
+
6
+ class Cyoi::Providers::Clients::FogProviderClient
7
+ attr_reader :fog_compute
8
+ attr_reader :attributes
9
+
10
+ def initialize(attributes)
11
+ @attributes = attributes.is_a?(Hash) ? Settingslogic.new(attributes) : attributes
12
+ raise "@attributes must be Settingslogic (or Hash)" unless @attributes.is_a?(Settingslogic)
13
+ setup_fog_connection
14
+ end
15
+
16
+ def setup_fog_connection
17
+ raise "must implement"
18
+ end
19
+
20
+ def create_key_pair(key_pair_name)
21
+ fog_compute.key_pairs.create(:name => key_pair_name)
22
+ end
23
+
24
+ # set_resource_name(fog_server, "inception")
25
+ # set_resource_name(volume, "inception-root")
26
+ # set_resource_name(volume, "inception-store")
27
+ def set_resource_name(resource, name)
28
+ fog_compute.tags.create :key => "Name", :value => name, :resource_id => resource.id
29
+ end
30
+
31
+ def delete_key_pair_if_exists(key_pair_name)
32
+ if fog_key_pair = fog_compute.key_pairs.get(key_pair_name)
33
+ fog_key_pair.destroy
34
+ end
35
+ end
36
+
37
+ def delete_servers_with_name(name)
38
+ fog_compute.servers.select {|s| s.tags["Name"].downcase == name.downcase }.each do |server|
39
+ puts "Destroying server #{server.id}..."
40
+ server.destroy
41
+ end
42
+ end
43
+
44
+ def delete_volumes_with_name(name)
45
+ fog_compute.volumes.select do |v|
46
+ volume_name = v.tags["Name"]
47
+ volume_name && volume_name.downcase == name.downcase
48
+ end.each do |volume|
49
+ puts "Destroying volume #{volume.id}..."
50
+ volume.destroy
51
+ end
52
+ end
53
+
54
+ # Destroy all IP addresses that aren't bound to a server
55
+ def cleanup_unused_ip_addresses
56
+ fog_compute.addresses.each do |a|
57
+ unless a.server
58
+ puts "Deleting unused IP address #{a.public_ip}..."
59
+ a.destroy
60
+ end
61
+ end
62
+ end
63
+
64
+ # Creates or reuses an security group and opens ports.
65
+ #
66
+ # +security_group_name+ is the name to be created or reused
67
+ # +ports+ is a hash of name/port for ports to open, for example:
68
+ # {
69
+ # ssh: 22,
70
+ # http: 80,
71
+ # https: 443
72
+ # }
73
+ # protocol defaults to TCP
74
+ # You can also use a more verbose +ports+ using the format:
75
+ # {
76
+ # ssh: 22,
77
+ # http: { ports: (80..82) },
78
+ # mosh: { protocol: "udp", ports: (60000..60050) }
79
+ # mosh: { protocol: "rdp", ports: (3398..3398), ip_ranges: [ { cidrIp: "196.212.12.34/32" } ] }
80
+ # }
81
+ # In this example,
82
+ # * TCP 22 will be opened for ssh from any ip_range,
83
+ # * TCP ports 80, 81, 82 for http from any ip_range,
84
+ # * UDP 60000 -> 60050 for mosh from any ip_range and
85
+ # * TCP 3398 for RDP from ip range: 96.212.12.34/32
86
+ def create_security_group(security_group_name, description, ports)
87
+ security_groups = fog_compute.security_groups
88
+ unless sg = security_groups.find { |s| s.name == security_group_name }
89
+ sg = fog_compute.security_groups.create(name: security_group_name, description: description)
90
+ puts "Created security group #{security_group_name}"
91
+ else
92
+ puts "Reusing security group #{security_group_name}"
93
+ end
94
+ ip_permissions = ip_permissions(sg)
95
+ ports_opened = 0
96
+ ports.each do |name, port_defn|
97
+ (protocol, port_range, ip_range) = extract_port_definition(port_defn)
98
+ unless port_open?(ip_permissions, port_range, protocol, ip_range)
99
+ authorize_port_range(sg, port_range, protocol, ip_range)
100
+ puts " -> opened #{name} ports #{protocol.upcase} #{port_range.min}..#{port_range.max} from IP range #{ip_range}"
101
+ ports_opened += 1
102
+ end
103
+ end
104
+ puts " -> no additional ports opened" if ports_opened == 0
105
+ true
106
+ end
107
+
108
+ def port_open?(ip_permissions, port_range, protocol, ip_range)
109
+ ip_permissions && ip_permissions.find do |ip|
110
+ ip["ipProtocol"] == protocol \
111
+ && ip["ipRanges"].detect { |range| range["cidrIp"] == ip_range } \
112
+ && ip["fromPort"] <= port_range.min \
113
+ && ip["toPort"] >= port_range.max
114
+ end
115
+ end
116
+
117
+ def authorize_port_range(sg, port_range, protocol, ip_range)
118
+ sg.authorize_port_range(port_range, {:ip_protocol => protocol, :cidr_ip => ip_range})
119
+ end
120
+
121
+ def ip_permissions(sg)
122
+ sg.ip_permissions
123
+ end
124
+
125
+ # Any of the following +port_defn+ can be used:
126
+ # {
127
+ # ssh: 22,
128
+ # http: { ports: (80..82) },
129
+ # mosh: { protocol: "udp", ports: (60000..60050) }
130
+ # mosh: { protocol: "rdp", ports: (3398..3398), ip_range: "196.212.12.34/32" }
131
+ # }
132
+ # In this example,
133
+ # * TCP 22 will be opened for ssh from any ip_range,
134
+ # * TCP ports 80, 81, 82 for http from any ip_range,
135
+ # * UDP 60000 -> 60050 for mosh from any ip_range and
136
+ # * TCP 3398 for RDP from ip range: 96.212.12.34/32
137
+ def extract_port_definition(port_defn)
138
+ protocol = "tcp"
139
+ ip_range = "0.0.0.0/0"
140
+ if port_defn.is_a? Integer
141
+ port_range = (port_defn..port_defn)
142
+ elsif port_defn.is_a? Range
143
+ port_range = port_defn
144
+ elsif port_defn.is_a? Hash
145
+ protocol = port_defn[:protocol] if port_defn[:protocol]
146
+ port_range = port_defn[:ports] if port_defn[:ports]
147
+ ip_range = port_defn[:ip_range] if port_defn[:ip_range]
148
+ end
149
+ [protocol, port_range, ip_range]
150
+ end
151
+
152
+ def provision_or_reuse_public_ip_address(options={})
153
+ provision_public_ip_address(options) || find_unused_public_ip_address(options)
154
+ end
155
+
156
+ def find_unused_public_ip_address(options={})
157
+ if address = fog_compute.addresses.find { |s| s.server_id.nil? }
158
+ address.public_ip
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Cyoi; module Providers; module Clients; end; end; end
4
+
5
+ require "cyoi/providers/clients/fog_provider_client"
6
+ require "cyoi/providers/constants/openstack_constants"
7
+
8
+ class Cyoi::Providers::Clients::OpenStackProviderClient < Cyoi::Providers::Clients::FogProviderClient
9
+ # @return [String] provisions a new public IP address in target region
10
+ # TODO nil if none available
11
+ def provision_public_ip_address(options={})
12
+ address = fog_compute.addresses.create
13
+ address.ip
14
+ # TODO catch error and return nil
15
+ end
16
+
17
+ def associate_ip_address_with_server(ip_address, server)
18
+ address = fog_compute.addresses.find { |a| a.ip == ip_address }
19
+ address.server = server
20
+ end
21
+
22
+ # Hook method for FogProviderClient#create_security_group
23
+ def ip_permissions(sg)
24
+ sg.rules
25
+ end
26
+
27
+ # Hook method for FogProviderClient#create_security_group
28
+ def authorize_port_range(sg, port_range, protocol, ip_range)
29
+ sg.create_security_group_rule(port_range.min, port_range.max, protocol, ip_range)
30
+ end
31
+
32
+ def find_server_device(server, device)
33
+ va = fog_compute.get_server_volumes(server.id).body['volumeAttachments']
34
+ va.find { |v| v["device"] == device }
35
+ end
36
+
37
+ def create_and_attach_volume(name, disk_size, server, device)
38
+ volume = fog_compute.volumes.create(:name => name,
39
+ :description => "",
40
+ :size => disk_size,
41
+ :availability_zone => server.availability_zone)
42
+ volume.wait_for { volume.status == 'available' }
43
+ volume.attach(server.id, device)
44
+ volume.wait_for { volume.status == 'in-use' }
45
+ end
46
+
47
+ def delete_security_group_and_servers(sg_name)
48
+ raise "not implemented yet"
49
+ end
50
+
51
+ # Construct a Fog::Compute object
52
+ # Uses +attributes+ which normally originates from +settings.provider+
53
+ def setup_fog_connection
54
+ configuration = Fog.symbolize_credentials(attributes.credentials)
55
+ configuration[:provider] = "OpenStack"
56
+ unless attributes.region == openstack_constants.no_region_code
57
+ configuration[:openstack_region] = attributes.region
58
+ end
59
+ @fog_compute = Fog::Compute.new(configuration)
60
+ end
61
+
62
+ def openstack_constants
63
+ Cyoi::Providers::Constants::OpenStackConstants
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Cyoi; module Providers; module Constants; end; end; end
4
+
5
+ module Cyoi::Providers::Constants::AwsConstants
6
+ extend self
7
+
8
+ # http://docs.aws.amazon.com/general/latest/gr/rande.html#region
9
+ def region_labels
10
+ [
11
+ { label: "US East (Northern Virginia) Region", code: "us-east-1" },
12
+ { label: "US West (Oregon) Region", code: "us-west-2" },
13
+ { label: "US West (Northern California) Region", code: "us-west-1" },
14
+ { label: "EU (Ireland) Region", code: "eu-west-1" },
15
+ { label: "Asia Pacific (Singapore) Region", code: "ap-southeast-1" },
16
+ { label: "Asia Pacific (Sydney) Region", code: "ap-southeast-2" },
17
+ { label: "Asia Pacific (Tokyo) Region", code: "ap-northeast-1" },
18
+ { label: "South America (Sao Paulo) Region", code: "sa-east-1" },
19
+ ]
20
+ end
21
+
22
+ def default_region_code
23
+ "us-east-1"
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Cyoi; module Providers; module Constants; end; end; end
4
+
5
+ module Cyoi::Providers::Constants::OpenStackConstants
6
+ extend self
7
+
8
+ # explicit value representing "no region requested"
9
+ def no_region_code
10
+ "no-region-requested"
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Cyoi; end
4
+
5
+ module Cyoi::Providers
6
+ extend self
7
+
8
+ # returns a Infrastructure provider specific object
9
+ # with helpers related to that provider
10
+ # returns nil if +attributes.name+ is unknown
11
+ def provider_client(attributes)
12
+ attributes = attributes.is_a?(Hash) ? Settingslogic.new(attributes) : attributes
13
+ case attributes.name.to_sym
14
+ when :aws
15
+ @aws_provider_client ||= begin
16
+ require "cyoi/providers/clients/aws_provider_client"
17
+ Cyoi::Providers::Clients::AwsProviderClient.new(attributes)
18
+ end
19
+ when :openstack
20
+ @openstack_provider_client ||= begin
21
+ require "cyoi/providers/clients/openstack_provider_client"
22
+ Cyoi::Providers::Clients::OpenStackProviderClient.new(attributes)
23
+ end
24
+ else
25
+ nil
26
+ end
27
+ end
28
+ end
data/lib/cyoi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Cyoi
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,36 @@
1
+ describe "cyoi address aws" do
2
+ include Cyoi::Cli::Helpers::Settings
3
+ include SettingsHelper
4
+ include Aruba::Api
5
+ before { @settings_dir = File.expand_path("~/.cyoi_client_lib") }
6
+
7
+ it "fails nicely if provider.name missing" do
8
+ run_interactive(unescape("cyoi address #{settings_dir}"))
9
+ assert_failing_with("Please run 'cyoi provider' first")
10
+ end
11
+
12
+ describe "provider setup and" do
13
+ before do
14
+ setting "provider.name", "aws"
15
+ setting "provider.credentials.aws_access_key_id", "aws_access_key_id"
16
+ setting "provider.credentials.aws_secret_access_key", "aws_secret_access_key"
17
+ setting "provider.region", "us-west-2"
18
+ end
19
+
20
+ it "address aleady assigned" do
21
+ setting "provider.name", "aws"
22
+ setting "provider.credentials.aws_access_key_id", "aws_access_key_id"
23
+ setting "provider.credentials.aws_secret_access_key", "aws_secret_access_key"
24
+ setting "provider.region", "us-west-2"
25
+ setting "address.ip", "1.2.3.4"
26
+ run_interactive(unescape("cyoi address #{settings_dir}"))
27
+ assert_passing_with("Confirming: Using address 1.2.3.4")
28
+ end
29
+
30
+ it "address is provisioned from AWS" do
31
+ run_interactive(unescape("cyoi address #{settings_dir}"))
32
+ assert_passing_with("Confirming: Using address")
33
+ end
34
+ end
35
+
36
+ end
@@ -1,4 +1,4 @@
1
- describe "cyoi aws" do
1
+ describe "cyoi provider aws" do
2
2
  include Cyoi::Cli::Helpers::Settings
3
3
  include SettingsHelper
4
4
  include Aruba::Api
@@ -9,12 +9,12 @@ describe "cyoi aws" do
9
9
  setting "provider.credentials.aws_access_key_id", "aws_access_key_id"
10
10
  setting "provider.credentials.aws_secret_access_key", "aws_secret_access_key"
11
11
  setting "provider.region", "us-west-2"
12
- run_interactive(unescape("cyoi #{settings_dir}"))
12
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
13
13
  assert_passing_with("Confirming: Using AWS/us-west-2")
14
14
  end
15
15
 
16
16
  it "prompts for provider, user chooses aws" do
17
- run_interactive(unescape("cyoi #{settings_dir}"))
17
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
18
18
  type("1")
19
19
  type("ACCESS")
20
20
  type("SECRET")
@@ -43,7 +43,7 @@ Confirming: Using AWS/us-west-2
43
43
  it "auto-detects aws options in ~/.fog" do
44
44
  setup_home_dir
45
45
  setup_fog_with_various_accounts_setup
46
- run_interactive(unescape("cyoi #{settings_dir}"))
46
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
47
47
  type("3")
48
48
  type("6")
49
49
  type("")
@@ -1,4 +1,4 @@
1
- describe "cyoi openstack" do
1
+ describe "cyoi provider openstack" do
2
2
  include Cyoi::Cli::Helpers::Settings
3
3
  include SettingsHelper
4
4
  include Aruba::Api
@@ -11,12 +11,12 @@ describe "cyoi openstack" do
11
11
  setting "provider.credentials.openstack_tenant", "TENANT"
12
12
  setting "provider.credentials.openstack_auth_url", "TOKENURL"
13
13
  setting "provider.region", "us-west"
14
- run_interactive(unescape("cyoi #{settings_dir}"))
14
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
15
15
  assert_passing_with("Confirming: Using OpenStack/us-west")
16
16
  end
17
17
 
18
18
  it "prompts for everything (no region)" do
19
- run_interactive(unescape("cyoi #{settings_dir}"))
19
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
20
20
  type("2")
21
21
  type("USERNAME")
22
22
  type("PASSWORD")
@@ -37,7 +37,7 @@ Confirming: Using OpenStack
37
37
  end
38
38
 
39
39
  it "prompts for everything (with region)" do
40
- run_interactive(unescape("cyoi #{settings_dir}"))
40
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
41
41
  type("2")
42
42
  type("USERNAME")
43
43
  type("PASSWORD")
@@ -60,7 +60,7 @@ Confirming: Using OpenStack/REGION
60
60
  it "auto-detects several openstack options in ~/.fog" do
61
61
  setup_home_dir
62
62
  setup_fog_with_various_accounts_setup
63
- run_interactive(unescape("cyoi #{settings_dir}"))
63
+ run_interactive(unescape("cyoi provider #{settings_dir}"))
64
64
  type("4")
65
65
  type("REGION")
66
66
  type("")
data/spec/spec_helper.rb CHANGED
@@ -11,6 +11,7 @@ $:.unshift(File.expand_path("../../lib", __FILE__))
11
11
  require "rspec/core"
12
12
  require "cyoi"
13
13
  require "cyoi/cli/provider"
14
+ require "cyoi/cli/address"
14
15
 
15
16
  require "aruba/api"
16
17
 
@@ -1,4 +1,4 @@
1
- # assumes @cmd is Inception::Cli instance
1
+ # assumes @cmd is Cyoi::Cli instance
2
2
  module SettingsHelper
3
3
  # Set a nested setting with "key1.key2.key3" notation
4
4
  def setting(nested_key, value)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cyoi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-13 00:00:00.000000000 Z
12
+ date: 2013-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: highline
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -119,6 +135,7 @@ files:
119
135
  - .gitignore
120
136
  - .rspec
121
137
  - .travis.yml
138
+ - ChangeLog.md
122
139
  - Gemfile
123
140
  - Guardfile
124
141
  - LICENSE.txt
@@ -128,19 +145,30 @@ files:
128
145
  - cyoi.gemspec
129
146
  - lib/cyoi.rb
130
147
  - lib/cyoi/cli.rb
148
+ - lib/cyoi/cli/address.rb
131
149
  - lib/cyoi/cli/auto_detection.rb
132
150
  - lib/cyoi/cli/auto_detection/auto_detection_fog.rb
133
151
  - lib/cyoi/cli/helpers.rb
134
152
  - lib/cyoi/cli/helpers/interactions.rb
153
+ - lib/cyoi/cli/helpers/provider.rb
135
154
  - lib/cyoi/cli/helpers/settings.rb
136
155
  - lib/cyoi/cli/provider.rb
156
+ - lib/cyoi/cli/provider_addresses/address_cli_aws.rb
137
157
  - lib/cyoi/cli/providers/provider_cli.rb
138
158
  - lib/cyoi/cli/providers/provider_cli_aws.rb
139
159
  - lib/cyoi/cli/providers/provider_cli_openstack.rb
160
+ - lib/cyoi/providers.rb
161
+ - lib/cyoi/providers/README.md
162
+ - lib/cyoi/providers/clients/aws_provider_client.rb
163
+ - lib/cyoi/providers/clients/fog_provider_client.rb
164
+ - lib/cyoi/providers/clients/openstack_provider_client.rb
165
+ - lib/cyoi/providers/constants/aws_constants.rb
166
+ - lib/cyoi/providers/constants/openstack_constants.rb
140
167
  - lib/cyoi/version.rb
141
168
  - spec/.DS_Store
142
- - spec/integration/cli/provider_aws_spec.rb
143
- - spec/integration/cli/provider_openstack_spec.rb
169
+ - spec/integration/cli/address/address_aws_spec.rb
170
+ - spec/integration/cli/provider/provider_aws_spec.rb
171
+ - spec/integration/cli/provider/provider_openstack_spec.rb
144
172
  - spec/spec_helper.rb
145
173
  - spec/support/settings_helper.rb
146
174
  - spec/support/stdout_capture.rb
@@ -160,7 +188,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
188
  version: '0'
161
189
  segments:
162
190
  - 0
163
- hash: -525265882681601916
191
+ hash: -3618245978349613620
164
192
  required_rubygems_version: !ruby/object:Gem::Requirement
165
193
  none: false
166
194
  requirements:
@@ -169,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
197
  version: '0'
170
198
  segments:
171
199
  - 0
172
- hash: -525265882681601916
200
+ hash: -3618245978349613620
173
201
  requirements: []
174
202
  rubyforge_project:
175
203
  rubygems_version: 1.8.25
@@ -183,8 +211,9 @@ summary: A library to ask an end-user to choose an infrastructure (AWS, OpenStac
183
211
  their infrastructure (say via [fog](http://fog.io)).
184
212
  test_files:
185
213
  - spec/.DS_Store
186
- - spec/integration/cli/provider_aws_spec.rb
187
- - spec/integration/cli/provider_openstack_spec.rb
214
+ - spec/integration/cli/address/address_aws_spec.rb
215
+ - spec/integration/cli/provider/provider_aws_spec.rb
216
+ - spec/integration/cli/provider/provider_openstack_spec.rb
188
217
  - spec/spec_helper.rb
189
218
  - spec/support/settings_helper.rb
190
219
  - spec/support/stdout_capture.rb