digitalronin-rackspace 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba0533cf37abea8c1ea65902886a5268dc5a82d2
4
+ data.tar.gz: aca6b07411ab9a37a6d96241ead65b4ee55b11d7
5
+ SHA512:
6
+ metadata.gz: 05025ecc9ae813517a0d615b9afa49c40283c0d51534e3909605c90761f43a58da0f01346146c0ab993398357711fdee89626f3d61c8156b4bf4794c4db6e9e9
7
+ data.tar.gz: 0a88db9edd98d95b489cfe523b1e8f058fe08882dc06b5c196ad7f7d29039220807b26773944d1ad0415e360d1a4ea28e653dfabc2b3df2ae7379e26627eae99
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "fog"
4
+
5
+ group :development do
6
+ gem "rake"
7
+ gem "pry-byebug"
8
+ gem "rspec"
9
+ gem "gem-this"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,152 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ CFPropertyList (2.3.1)
5
+ builder (3.2.2)
6
+ byebug (4.0.5)
7
+ columnize (= 0.9.0)
8
+ coderay (1.1.0)
9
+ columnize (0.9.0)
10
+ diff-lcs (1.2.5)
11
+ excon (0.45.3)
12
+ fission (0.5.0)
13
+ CFPropertyList (~> 2.2)
14
+ fog (1.30.0)
15
+ fog-atmos
16
+ fog-aws (~> 0.0)
17
+ fog-brightbox (~> 0.4)
18
+ fog-core (~> 1.27, >= 1.27.4)
19
+ fog-ecloud
20
+ fog-google (>= 0.0.2)
21
+ fog-json
22
+ fog-local
23
+ fog-powerdns (>= 0.1.1)
24
+ fog-profitbricks
25
+ fog-radosgw (>= 0.0.2)
26
+ fog-riakcs
27
+ fog-sakuracloud (>= 0.0.4)
28
+ fog-serverlove
29
+ fog-softlayer
30
+ fog-storm_on_demand
31
+ fog-terremark
32
+ fog-vmfusion
33
+ fog-voxel
34
+ fog-xml (~> 0.1.1)
35
+ ipaddress (~> 0.5)
36
+ nokogiri (~> 1.5, >= 1.5.11)
37
+ fog-atmos (0.1.0)
38
+ fog-core
39
+ fog-xml
40
+ fog-aws (0.1.2)
41
+ fog-core (~> 1.27)
42
+ fog-json (~> 1.0)
43
+ fog-xml (~> 0.1)
44
+ ipaddress (~> 0.8)
45
+ fog-brightbox (0.7.1)
46
+ fog-core (~> 1.22)
47
+ fog-json
48
+ inflecto (~> 0.0.2)
49
+ fog-core (1.30.0)
50
+ builder
51
+ excon (~> 0.45)
52
+ formatador (~> 0.2)
53
+ mime-types
54
+ net-scp (~> 1.1)
55
+ net-ssh (>= 2.1.3)
56
+ fog-ecloud (0.1.1)
57
+ fog-core
58
+ fog-xml
59
+ fog-google (0.0.5)
60
+ fog-core
61
+ fog-json
62
+ fog-xml
63
+ fog-json (1.0.1)
64
+ fog-core (~> 1.0)
65
+ multi_json (~> 1.0)
66
+ fog-local (0.2.1)
67
+ fog-core (~> 1.27)
68
+ fog-powerdns (0.1.1)
69
+ fog-core (~> 1.27)
70
+ fog-json (~> 1.0)
71
+ fog-xml (~> 0.1)
72
+ fog-profitbricks (0.0.2)
73
+ fog-core
74
+ fog-xml
75
+ nokogiri
76
+ fog-radosgw (0.0.4)
77
+ fog-core (>= 1.21.0)
78
+ fog-json
79
+ fog-xml (>= 0.0.1)
80
+ fog-riakcs (0.1.0)
81
+ fog-core
82
+ fog-json
83
+ fog-xml
84
+ fog-sakuracloud (1.0.1)
85
+ fog-core
86
+ fog-json
87
+ fog-serverlove (0.1.2)
88
+ fog-core
89
+ fog-json
90
+ fog-softlayer (0.4.5)
91
+ fog-core
92
+ fog-json
93
+ fog-storm_on_demand (0.1.1)
94
+ fog-core
95
+ fog-json
96
+ fog-terremark (0.1.0)
97
+ fog-core
98
+ fog-xml
99
+ fog-vmfusion (0.1.0)
100
+ fission
101
+ fog-core
102
+ fog-voxel (0.1.0)
103
+ fog-core
104
+ fog-xml
105
+ fog-xml (0.1.2)
106
+ fog-core
107
+ nokogiri (~> 1.5, >= 1.5.11)
108
+ formatador (0.2.5)
109
+ gem-this (0.3.7)
110
+ inflecto (0.0.2)
111
+ ipaddress (0.8.0)
112
+ method_source (0.8.2)
113
+ mime-types (2.5)
114
+ mini_portile (0.6.2)
115
+ multi_json (1.11.0)
116
+ net-scp (1.2.1)
117
+ net-ssh (>= 2.6.5)
118
+ net-ssh (2.9.2)
119
+ nokogiri (1.6.6.2)
120
+ mini_portile (~> 0.6.0)
121
+ pry (0.10.1)
122
+ coderay (~> 1.1.0)
123
+ method_source (~> 0.8.1)
124
+ slop (~> 3.4)
125
+ pry-byebug (3.1.0)
126
+ byebug (~> 4.0)
127
+ pry (~> 0.10)
128
+ rake (10.4.2)
129
+ rspec (3.2.0)
130
+ rspec-core (~> 3.2.0)
131
+ rspec-expectations (~> 3.2.0)
132
+ rspec-mocks (~> 3.2.0)
133
+ rspec-core (3.2.3)
134
+ rspec-support (~> 3.2.0)
135
+ rspec-expectations (3.2.1)
136
+ diff-lcs (>= 1.2.0, < 2.0)
137
+ rspec-support (~> 3.2.0)
138
+ rspec-mocks (3.2.1)
139
+ diff-lcs (>= 1.2.0, < 2.0)
140
+ rspec-support (~> 3.2.0)
141
+ rspec-support (3.2.2)
142
+ slop (3.6.0)
143
+
144
+ PLATFORMS
145
+ ruby
146
+
147
+ DEPENDENCIES
148
+ fog
149
+ gem-this
150
+ pry-byebug
151
+ rake
152
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 David Salgado
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ A tool to manage Rackspace Cloud servers and Block Storage volumes
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/digitalronin-rackspace.svg)](http://badge.fury.io/rb/digitalronin-rackspace)
4
+ [![Build Status](https://travis-ci.org/digitalronin/digitalronin-rackspace.svg?branch=master)](https://travis-ci.org/digitalronin/digitalronin-rackspace)
5
+ [![Dependency Status](https://gemnasium.com/digitalronin/digitalronin-rackspace.svg)](https://gemnasium.com/digitalronin/digitalronin-rackspace)
6
+
7
+ [fog](http://fog.io) is awesome, but the API can be a bit cryptic and long-winded, especially for things like creating a VM with an attached block storage volume
8
+
9
+ USAGE
10
+
11
+ Create an 8G General Purpose VM with an attached 75G SATA block storage volume;
12
+
13
+ vm = Rackspace::Vm.new(
14
+ size: 8,
15
+ account: 'myaccount',
16
+ name: 'testserver',
17
+ volume: { size: 75 }
18
+ ).create
19
+
20
+ Destroy a VM and its attached storage volume
21
+
22
+ Rackspace::Vm.new(account: 'myaccount', name: 'testserver').destroy(destroy_volume: true)
23
+
24
+ See the scripts in the examples directory
25
+
26
+ ASSUMPTIONS/PRE-REQUISITES
27
+
28
+ * Ruby 2.2.0 (1.9+ should work, but I haven't tested any versions except 2.2)
29
+ * Valid Rackspace Cloud account and API key (see rackspace_credentials.yml.example for how to supply these via a YAML file)
30
+ * By default, VMs are "General Purpose" Ubuntu 14.04 machines - this can be overridden by supplying "image_name" and "flavor_id"
31
+
32
+ CAVEATS
33
+
34
+ * This gem will create and destroy cloud servers and block storage volumes in your account. This could cost you money and might destroy data you care about. Use it with caution and at your own risk.
35
+
36
+ * The gem doesn't support attaching multiple volumes to a server.
37
+
38
+ * Deleting server with an attached volume can fail if the call to destroy the volume happens before it has finished detaching.
39
+
40
+ TODO
41
+
42
+ * The gem should wait to ensure success of any API operations before returning from the relevant method call
43
+
data/lib/rackspace.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'fog'
2
+ require 'yaml'
3
+
4
+ module Rackspace
5
+ # Delay between checks, when waiting for the Rackspace API
6
+ SLEEP_DELAY = 20
7
+ # Maximum iterations before we give up waiting for the Rackspace API
8
+ MAX_ITERATIONS_ON_CREATE = 20
9
+
10
+ # Cloud servers
11
+ DEFAULT_SIZE = 1
12
+ DEFAULT_IMAGE_NAME = 'Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)'
13
+ DEFAULT_FLAVOUR = 'general1'
14
+
15
+ # Block storage volumes
16
+ SATA = 'SATA'
17
+ SSD = 'SSD'
18
+ MIN_SIZE = 75
19
+ MAX_SIZE = 1024
20
+ AVAILABLE = 'available'
21
+
22
+ DEFAULT_REGION = :ord
23
+ end
24
+
25
+ libdir = File.join(File.dirname(__FILE__), 'rackspace')
26
+
27
+ require File.join(libdir, 'logger.rb')
28
+ require File.join(libdir, 'base.rb')
29
+ require File.join(libdir, 'api.rb')
30
+ require File.join(libdir, 'server_api.rb')
31
+ require File.join(libdir, 'storage_api.rb')
32
+ require File.join(libdir, 'vm.rb')
33
+ require File.join(libdir, 'volume.rb')
34
+
@@ -0,0 +1,40 @@
1
+ module Rackspace
2
+ class Api
3
+ attr_reader :logger
4
+
5
+ def initialize(params)
6
+ @logger = params.fetch(:logger, Rackspace::Logger.new)
7
+ end
8
+
9
+ private
10
+
11
+ def wait_for(name)
12
+ logger.log "Waiting for #{object_type} #{name}"
13
+
14
+ obj = nil; ready = false; iterations = 1
15
+
16
+ while !ready do
17
+ obj = find_by_name(name)
18
+
19
+ if obj.nil?
20
+ logger.log "Waiting for #{object_type} #{name} to appear in API list..."
21
+ else
22
+ logger.log "Found #{object_type} #{name}. Status: #{obj.state}, check #{iterations} of #{MAX_ITERATIONS_ON_CREATE}"
23
+
24
+ if obj.state == ready_state
25
+ ready = true
26
+ end
27
+ end
28
+
29
+ iterations += 1
30
+
31
+ raise "#{object_type} not ready after #{iterations} sleeps. Aborting." if iterations > MAX_ITERATIONS_ON_CREATE
32
+
33
+ sleep(SLEEP_DELAY) unless ready
34
+ end
35
+
36
+ obj
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ module Rackspace
2
+ class Base
3
+ attr_reader :account, :region, :credentials, :logger
4
+
5
+ DEFAULT_CREDENTIALS = 'rackspace_credentials.yml'
6
+
7
+ def initialize(params)
8
+ @logger = params.fetch(:logger, Rackspace::Logger.new)
9
+ @account = params.fetch(:account)
10
+ @region = params.fetch(:region, DEFAULT_REGION)
11
+ @credentials = params.fetch(:credentials) {
12
+ credentials_file = params.fetch(:credentials_file, DEFAULT_CREDENTIALS)
13
+ YAML.load(File.read(credentials_file))
14
+ }
15
+ end
16
+
17
+ def log(msg)
18
+ logger.log(msg)
19
+ end
20
+
21
+ def server_api
22
+ @server_api ||= ServerApi.new(rackspace_credentials)
23
+ end
24
+
25
+ def storage_api
26
+ @storage_api ||= StorageApi.new(rackspace_credentials)
27
+ end
28
+
29
+ private
30
+
31
+ def accounts
32
+ credentials.fetch(:accounts) { raise("No :accounts section found in credentials") }
33
+ end
34
+
35
+ def rackspace_credentials
36
+ {
37
+ rackspace_username: account,
38
+ rackspace_api_key: accounts.fetch(account) { raise("No API key found for account: #{account}") },
39
+ rackspace_region: region,
40
+ }
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ module Rackspace
2
+ class Logger
3
+ def log(msg)
4
+ puts [Time.now.strftime("%Y-%m-%d %H:%M:%S"), msg].join(' ')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ module Rackspace
2
+ class ServerApi < Api
3
+ attr_reader :api
4
+
5
+ def initialize(rackspace_credentials)
6
+ super rackspace_credentials
7
+ @api ||= Fog::Compute.new(
8
+ rackspace_credentials.merge(
9
+ version: :v2,
10
+ provider: 'Rackspace',
11
+ )
12
+ )
13
+ end
14
+
15
+ def find_by_name(name)
16
+ api.servers.detect {|i| i.name == name}
17
+ end
18
+
19
+ def create(params)
20
+ api.servers.create(params)
21
+ wait_for params.fetch(:name)
22
+ end
23
+
24
+ def image_by_name(img_name)
25
+ img = api.images.detect {|i| i.name == img_name}
26
+ raise "No such server image #{img_name}" if img.nil?
27
+ img
28
+ end
29
+
30
+ private
31
+
32
+ def object_type
33
+ 'server'
34
+ end
35
+
36
+ def ready_state
37
+ 'ACTIVE'
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ module Rackspace
2
+ class StorageApi < Api
3
+ attr_reader :api
4
+
5
+ def initialize(rackspace_credentials)
6
+ super rackspace_credentials
7
+ @api ||= Fog::Rackspace::BlockStorage.new(rackspace_credentials)
8
+ end
9
+
10
+ def find_by_name(name)
11
+ api.volumes.detect { |v| v.display_name == name }
12
+ end
13
+
14
+ def find_by_id(volume_id)
15
+ api.get_volume volume_id
16
+ end
17
+
18
+ def create(params)
19
+ name = params.fetch(:name)
20
+
21
+ api.volumes.create(
22
+ size: params.fetch(:size),
23
+ display_name: name,
24
+ volume_type: params.fetch(:volume_type),
25
+ )
26
+ wait_for(name)
27
+ end
28
+
29
+ def destroy(name)
30
+ find_by_name(name).destroy
31
+ end
32
+
33
+ private
34
+
35
+ def object_type
36
+ 'volume'
37
+ end
38
+
39
+ def ready_state
40
+ AVAILABLE
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,102 @@
1
+ module Rackspace
2
+ class Vm < Base
3
+ attr_reader :name, :size, :flavor_id, :image_name, :volume
4
+
5
+ def initialize(params)
6
+ super params
7
+ @name = params.fetch(:name)
8
+ @image_name = params.fetch(:image_name, DEFAULT_IMAGE_NAME)
9
+ @size = params.fetch(:size, DEFAULT_SIZE)
10
+ @flavor_id = params.fetch(:flavor_id, "#{DEFAULT_FLAVOUR}-#{size}")
11
+ @volume_definition = params[:volume]
12
+ end
13
+
14
+ def create
15
+ log "Creating server #{name} in account #{account}"
16
+ @server = server_api.create(
17
+ name: name,
18
+ flavor_id: flavor_id,
19
+ image_id: image_id,
20
+ personality: personality
21
+ )
22
+ build_and_attach_volume if @volume_definition
23
+ self
24
+ end
25
+
26
+ def destroy(options = {})
27
+ log "Destroying server #{name} in account #{account}"
28
+ server.attachments.map(&:destroy)
29
+ volume.destroy if options[:destroy_volume]
30
+ server.destroy
31
+ self
32
+ end
33
+
34
+ def public_ip
35
+ server.ipv4_address
36
+ end
37
+
38
+ def private_ip
39
+ server.private_ip_address
40
+ end
41
+
42
+ def state
43
+ server.state
44
+ end
45
+
46
+ def attach_volume(vol)
47
+ server.attach_volume(vol.volume)
48
+ end
49
+
50
+ def volume
51
+ @volume ||= begin
52
+ if attachments.any?
53
+ Rackspace::Volume.new(
54
+ account: account,
55
+ region: region,
56
+ credentials: credentials,
57
+ volume_id: attachments.first.volume_id
58
+ )
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def attachments
66
+ server.attachments
67
+ end
68
+
69
+ def build_and_attach_volume
70
+ vol = Rackspace::Volume.new(@volume_definition.merge(
71
+ account: account,
72
+ region: region,
73
+ credentials: credentials,
74
+ name: "#{name}-vol",
75
+ )).create
76
+
77
+ log "Attaching block storage volume #{vol.name}"
78
+ server.attach_volume(vol.volume)
79
+ @volume = nil # force an API query next time volume is called
80
+ end
81
+
82
+ def server
83
+ @server ||= server_api.find_by_name(name)
84
+ end
85
+
86
+ def personality
87
+ [{
88
+ 'path' => '/root/.ssh/authorized_keys',
89
+ 'contents' => Base64.encode64(credentials.fetch(:public_ssh_key)),
90
+ }]
91
+ end
92
+
93
+ def image_id
94
+ server_api.image_by_name(image_name).id
95
+ end
96
+
97
+ def find_by_name
98
+ server_api.find_by_name(name)
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,55 @@
1
+ module Rackspace
2
+ class Volume < Base
3
+ attr_reader :volume_type, :size, :name, :volume_id, :device
4
+
5
+ def initialize(params)
6
+ super params
7
+ if @volume_id = params[:volume_id]
8
+ fetch_volume_by_id
9
+ else
10
+ @volume_type = params.fetch(:volume_type, SATA)
11
+ @size = params.fetch(:size, MIN_SIZE)
12
+ @name = params.fetch(:name)
13
+ end
14
+ end
15
+
16
+ def create
17
+ log "Creating #{size}GB #{volume_type} block storage volume: #{name}"
18
+ storage_api.create(
19
+ size: size,
20
+ name: name,
21
+ volume_type: volume_type,
22
+ )
23
+ self
24
+ end
25
+
26
+ def destroy
27
+ log "Destroying volume #{name} in account #{account}"
28
+ volume.destroy
29
+ self
30
+ end
31
+
32
+ def volume
33
+ @volume ||= storage_api.find_by_name(name)
34
+ end
35
+
36
+ private
37
+
38
+ # Find an existing volume by its volume_id
39
+ def fetch_volume_by_id
40
+ vol = storage_api.find_by_id(volume_id)
41
+ data = vol.body.fetch("volume")
42
+ @name = data.fetch('display_name')
43
+ @volume_type = data.fetch('volume_type')
44
+ @size = data.fetch('size')
45
+
46
+ # Sometimes the Rackspace API takes time to update,
47
+ # and newly-attached volumes don't report any attachments
48
+ # yet.
49
+ attachments = data.fetch('attachments')
50
+ if attachments.any?
51
+ @device = attachments.first.fetch('device')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,193 @@
1
+ require './spec/spec_helper.rb'
2
+
3
+ describe Rackspace::Vm do
4
+ let(:credentials) { {
5
+ accounts: { 'myaccount' => 'dummy api key' },
6
+ public_ssh_key: 'dummy ssh key',
7
+ } }
8
+
9
+ let(:params) { {
10
+ credentials: credentials,
11
+ account: 'myaccount',
12
+ name: 'myserver',
13
+ } }
14
+
15
+ subject(:vm) { described_class.new(params) }
16
+
17
+ let(:logger) { double(Rackspace::Logger, log: true) }
18
+
19
+ let(:rackspace_credentials) { {
20
+ rackspace_username: 'myaccount',
21
+ rackspace_api_key: 'dummy api key',
22
+ rackspace_region: :ord,
23
+ } }
24
+
25
+ let(:personality) { [
26
+ 'path' => '/root/.ssh/authorized_keys',
27
+ 'contents' => Base64.encode64('dummy ssh key')
28
+ ] }
29
+
30
+ let(:image) { double(id: 'some image') }
31
+
32
+ let(:api) { double(Rackspace::ServerApi, image_by_name: image, create: true) }
33
+
34
+ before do
35
+ allow(Rackspace::Logger).to receive(:new).and_return(logger)
36
+ end
37
+
38
+ before do
39
+ allow(Rackspace::ServerApi).to receive(:new).and_return(api)
40
+ end
41
+
42
+ context "with defaults" do
43
+ it "creates ServerApi instance" do
44
+ expect(Rackspace::ServerApi).to receive(:new).with(rackspace_credentials).and_return(api)
45
+ vm.create
46
+ end
47
+
48
+ it "creates a vm" do
49
+ expect(api).to receive(:create).with({
50
+ name: 'myserver',
51
+ flavor_id: 'general1-1',
52
+ image_id: 'some image',
53
+ personality: personality,
54
+ })
55
+
56
+ vm.create
57
+ end
58
+ end
59
+
60
+ context "overriding vm size" do
61
+ let(:params) { super().merge(size: 2) }
62
+
63
+ it "creates a vm" do
64
+ expect(api).to receive(:create).with(hash_including(flavor_id: 'general1-2'))
65
+ vm.create
66
+ end
67
+ end
68
+
69
+ context "overriding image" do
70
+ let(:image2) { double(id: 'image2-id') }
71
+ let(:params) { super().merge(image_name: 'another image') }
72
+
73
+ it "creates a vm" do
74
+ allow(api).to receive(:image_by_name).with('another image').and_return(image2)
75
+ expect(api).to receive(:create).with(hash_including(image_id: 'image2-id'))
76
+ vm.create
77
+ end
78
+ end
79
+
80
+ context "overriding flavor_id" do
81
+ let(:params) { super().merge(flavor_id: 'some-flavor-id') }
82
+
83
+ it "creates a vm" do
84
+ expect(api).to receive(:create).with(hash_including(flavor_id: 'some-flavor-id'))
85
+ vm.create
86
+ end
87
+ end
88
+
89
+ context "overriding region" do
90
+ let(:params) { super().merge(region: :iad) }
91
+
92
+ it "creates ServerApi instance" do
93
+ expect(Rackspace::ServerApi).to receive(:new).with(hash_including(rackspace_region: :iad))
94
+ vm.create
95
+ end
96
+ end
97
+
98
+ context "destroying" do
99
+ let(:attachment) { double(destroy: true) }
100
+ let(:server) { double(attachments: [attachment], destroy: true) }
101
+
102
+ before do
103
+ allow(api).to receive(:attachments).and_return([])
104
+ allow(api).to receive(:find_by_name).with('myserver').and_return(server)
105
+ end
106
+
107
+ it "finds the vm" do
108
+ expect(api).to receive(:find_by_name).with('myserver')
109
+ vm.destroy
110
+ end
111
+
112
+ it "destroys the vm" do
113
+ expect(server).to receive(:destroy)
114
+ vm.destroy
115
+ end
116
+
117
+ it "detaches volumes" do
118
+ expect(attachment).to receive(:destroy)
119
+ vm.destroy
120
+ end
121
+ end
122
+
123
+ context "attached block storage volumes" do
124
+ context "attaching volumes" do
125
+ let(:vol) { double }
126
+ let(:volume) { double(Rackspace::Volume, volume: vol) }
127
+ let(:server) { double }
128
+
129
+ before do
130
+ allow(api).to receive(:find_by_name).with('myserver').and_return(server)
131
+ end
132
+
133
+ it "attaches" do
134
+ expect(server).to receive(:attach_volume).with(vol)
135
+ vm.attach_volume(volume)
136
+ end
137
+ end
138
+
139
+ context "building and attaching a volume" do
140
+ let(:params) { super().merge(volume: {size: 100}) }
141
+ let(:vol) { double }
142
+ let(:volume) { double(Rackspace::Volume, volume: vol, name: 'myvol') }
143
+ let(:server) { double(attach_volume: true) }
144
+
145
+ let(:v_params) { {
146
+ account: 'myaccount',
147
+ region: :ord,
148
+ credentials: credentials,
149
+ name: 'myserver-vol',
150
+ size: 100,
151
+ } }
152
+
153
+ before do
154
+ allow(api).to receive(:find_by_name).with('myserver').and_return(server)
155
+ allow(api).to receive(:create).and_return(server)
156
+ allow(Rackspace::Volume).to receive(:new).with(v_params).and_return(volume)
157
+ allow(volume).to receive(:create).and_return(volume)
158
+ end
159
+
160
+ it "creates the volume" do
161
+ expect(Rackspace::Volume).to receive(:new).with(v_params).and_return(volume)
162
+ expect(volume).to receive(:create).and_return(volume)
163
+ vm.create
164
+ end
165
+
166
+ it "attaches the volume" do
167
+ expect(server).to receive(:attach_volume).with(vol)
168
+ vm.create
169
+ end
170
+
171
+ context "querying volume" do
172
+ let(:attachment) { double(volume_id: 'foo') }
173
+
174
+ let(:v_params) { {
175
+ account: 'myaccount',
176
+ region: :ord,
177
+ credentials: credentials,
178
+ volume_id: 'foo',
179
+ } }
180
+
181
+ before do
182
+ allow(server).to receive(:attachments).and_return([attachment])
183
+ end
184
+
185
+ it "returns the first attached volume" do
186
+ expect(Rackspace::Volume).to receive(:new).with(v_params)
187
+ vm.volume
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ end
@@ -0,0 +1,116 @@
1
+ require './spec/spec_helper.rb'
2
+
3
+ describe Rackspace::Volume do
4
+ let(:credentials) { {
5
+ accounts: { 'myaccount' => 'dummy api key' },
6
+ public_ssh_key: 'dummy ssh key',
7
+ } }
8
+
9
+ let(:params) { {
10
+ credentials: credentials,
11
+ account: 'myaccount',
12
+ name: 'myvolume',
13
+ } }
14
+
15
+ subject(:volume) { described_class.new(params) }
16
+
17
+ let(:logger) { double(Rackspace::Logger, log: true) }
18
+
19
+ let(:rackspace_credentials) { {
20
+ rackspace_username: 'myaccount',
21
+ rackspace_api_key: 'dummy api key',
22
+ rackspace_region: :ord,
23
+ } }
24
+
25
+ let(:api) { double(Rackspace::StorageApi, create: true) }
26
+
27
+ before do
28
+ allow(Rackspace::Logger).to receive(:new).and_return(logger)
29
+ end
30
+
31
+ before do
32
+ allow(Rackspace::StorageApi).to receive(:new).and_return(api)
33
+ end
34
+
35
+ context "finding by id" do
36
+ let(:params) { super().merge(volume_id: "dummy-volume-id") }
37
+ let(:body) { {
38
+ "volume" => {
39
+ "display_name" => "myvol",
40
+ "attachments" => [{"device" => "/dev/xvdb"}],
41
+ "volume_type" => "SATA",
42
+ "size" => 75
43
+ }
44
+ } }
45
+ let(:obj) { double(body: body) }
46
+
47
+ it "finds" do
48
+ expect(Rackspace::StorageApi).to receive(:new).with(rackspace_credentials).and_return(api)
49
+ expect(api).to receive(:find_by_id).with("dummy-volume-id").and_return(obj)
50
+ volume.device
51
+ end
52
+ end
53
+
54
+ context "with defaults" do
55
+ it "creates StorageApi instance" do
56
+ expect(Rackspace::StorageApi).to receive(:new).with(rackspace_credentials).and_return(api)
57
+ volume.create
58
+ end
59
+
60
+ it "creates a volume" do
61
+ expect(api).to receive(:create).with({
62
+ name: 'myvolume',
63
+ volume_type: 'SATA',
64
+ size: 75,
65
+ })
66
+
67
+ volume.create
68
+ end
69
+ end
70
+
71
+ context "overriding volume size" do
72
+ let(:params) { super().merge(size: 200) }
73
+
74
+ it "creates a volume" do
75
+ expect(api).to receive(:create).with(hash_including(size: 200))
76
+ volume.create
77
+ end
78
+ end
79
+
80
+ context "overriding volume type" do
81
+ let(:params) { super().merge(volume_type: 'SSD') }
82
+
83
+ it "creates a volume" do
84
+ expect(api).to receive(:create).with(hash_including(volume_type: 'SSD'))
85
+ volume.create
86
+ end
87
+ end
88
+
89
+ context "overriding region" do
90
+ let(:params) { super().merge(region: :iad) }
91
+
92
+ it "creates StorageApi instance" do
93
+ expect(Rackspace::StorageApi).to receive(:new).with(hash_including(rackspace_region: :iad))
94
+ volume.create
95
+ end
96
+ end
97
+
98
+ context "destroying" do
99
+ let(:vol) { double(destroy: true) }
100
+
101
+ before do
102
+ allow(api).to receive(:find_by_name).with('myvolume').and_return(vol)
103
+ end
104
+
105
+ it "finds the volume" do
106
+ expect(api).to receive(:find_by_name).with('myvolume')
107
+ volume.destroy
108
+ end
109
+
110
+ it "destroys the volume" do
111
+ expect(vol).to receive(:destroy)
112
+ volume.destroy
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,8 @@
1
+ require './lib/rackspace'
2
+
3
+ require 'pry-byebug'
4
+ require 'rspec'
5
+
6
+ Fog.mock!
7
+
8
+
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: digitalronin-rackspace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - David Salgado
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fog
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.30'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.30'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ description: Create/Query/Destroy rackspace cloud VMs and attached (or not) block
56
+ storage volumes
57
+ email: david@digitalronin.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files:
61
+ - README.md
62
+ files:
63
+ - Gemfile
64
+ - Gemfile.lock
65
+ - LICENSE
66
+ - README.md
67
+ - lib/rackspace.rb
68
+ - lib/rackspace/api.rb
69
+ - lib/rackspace/base.rb
70
+ - lib/rackspace/logger.rb
71
+ - lib/rackspace/server_api.rb
72
+ - lib/rackspace/storage_api.rb
73
+ - lib/rackspace/vm.rb
74
+ - lib/rackspace/volume.rb
75
+ - spec/rackspace/vm_spec.rb
76
+ - spec/rackspace/volume_spec.rb
77
+ - spec/spec_helper.rb
78
+ homepage: https://github.com/digitalronin/digitalronin-rackspace
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options:
84
+ - "--main"
85
+ - README.md
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.4.6
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Rackspace Cloud Servers and Block Storage Volumes
104
+ test_files: []