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 +14 -0
- data/Guardfile +2 -2
- data/README.md +8 -1
- data/Rakefile +7 -1
- data/bin/cyoi +20 -2
- data/cyoi.gemspec +1 -0
- data/lib/cyoi/cli/address.rb +59 -0
- data/lib/cyoi/cli/helpers/provider.rb +14 -0
- data/lib/cyoi/cli/helpers/settings.rb +4 -0
- data/lib/cyoi/cli/helpers.rb +2 -0
- data/lib/cyoi/cli/provider_addresses/address_cli_aws.rb +44 -0
- data/lib/cyoi/providers/README.md +5 -0
- data/lib/cyoi/providers/clients/aws_provider_client.rb +168 -0
- data/lib/cyoi/providers/clients/fog_provider_client.rb +161 -0
- data/lib/cyoi/providers/clients/openstack_provider_client.rb +65 -0
- data/lib/cyoi/providers/constants/aws_constants.rb +25 -0
- data/lib/cyoi/providers/constants/openstack_constants.rb +12 -0
- data/lib/cyoi/providers.rb +28 -0
- data/lib/cyoi/version.rb +1 -1
- data/spec/integration/cli/address/address_aws_spec.rb +36 -0
- data/spec/integration/cli/{provider_aws_spec.rb → provider/provider_aws_spec.rb} +4 -4
- data/spec/integration/cli/{provider_openstack_spec.rb → provider/provider_openstack_spec.rb} +5 -5
- data/spec/spec_helper.rb +1 -0
- data/spec/support/settings_helper.rb +1 -1
- metadata +37 -8
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
|
-
|
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
|
-
|
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
|
data/lib/cyoi/cli/helpers.rb
CHANGED
@@ -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,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
@@ -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("")
|
data/spec/integration/cli/{provider_openstack_spec.rb → provider/provider_openstack_spec.rb}
RENAMED
@@ -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
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.
|
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-
|
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/
|
143
|
-
- spec/integration/cli/
|
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: -
|
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: -
|
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/
|
187
|
-
- spec/integration/cli/
|
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
|