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 +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
|