cyoi 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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