coral_cloud 0.1.1

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/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = coral_cloud
2
+
3
+ This library provides the ability to define and manage servers. These servers
4
+ can be local virtual machines (interfaced by Vagrant) or (in the near future)
5
+ remote servers on various IAAS providers, such as Rackspace and Amazon.
6
+
7
+ This library utilizes the Puppet provisioner to build servers and Vagrant
8
+ to run them locally. Eventually a cohesive API will be developed that will
9
+ allow for easily switching and performing the same operations on different
10
+ providers. For now this library focuses on integration with Vagrant.
11
+
12
+ More to come soon...
13
+
14
+ Note: This library is still very early in development!
15
+
16
+ == Contributing to coral_cloud
17
+
18
+ * Check out the latest master to make sure the feature hasn't been implemented
19
+ or the bug hasn't been fixed yet.
20
+ * Check out the issue tracker to make sure someone already hasn't requested
21
+ it and/or contributed it.
22
+ * Fork the project.
23
+ * Start a feature/bugfix branch.
24
+ * Commit and push until you are happy with your contribution.
25
+ * Make sure to add tests for it. This is important so I don't break it in a
26
+ future version unintentionally.
27
+ * Please try not to mess with the Rakefile, version, or history. If you want
28
+ to have your own version, or is otherwise necessary, that is fine, but
29
+ please isolate to its own commit so I can cherry-pick around it.
30
+
31
+ == Copyright
32
+
33
+ Licensed under GPLv3. See LICENSE.txt for further details.
34
+
35
+ Copyright (c) 2013 Adrian Webb <adrian.webb@coraltech.net>
36
+ Coral Technology Group LLC
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+ require 'rdoc/task'
8
+ require 'jeweler'
9
+
10
+ require './lib/coral_cloud.rb'
11
+
12
+ #-------------------------------------------------------------------------------
13
+ # Dependencies
14
+
15
+ begin
16
+ Bundler.setup(:default, :development)
17
+ rescue Bundler::BundlerError => e
18
+ $stderr.puts e.message
19
+ $stderr.puts "Run `bundle install` to install missing gems"
20
+ exit e.status_code
21
+ end
22
+
23
+ #-------------------------------------------------------------------------------
24
+ # Gem specification
25
+
26
+ Jeweler::Tasks.new do |gem|
27
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
28
+ gem.name = "coral_cloud"
29
+ gem.homepage = "http://github.com/coraltech/ruby-coral_cloud"
30
+ gem.rubyforge_project = 'coral_cloud'
31
+ gem.license = "GPLv3"
32
+ gem.email = "adrian.webb@coraltech.net"
33
+ gem.authors = ["Adrian Webb"]
34
+ gem.summary = %Q{Provides the ability to define and manage clouds of local and remote servers}
35
+ gem.description = File.read('README.rdoc')
36
+ gem.required_ruby_version = '>= 1.8.1'
37
+ gem.has_rdoc = true
38
+ gem.rdoc_options << '--title' << 'Coral Cloud library' <<
39
+ '--main' << 'README.rdoc' <<
40
+ '--line-numbers'
41
+
42
+ # Dependencies defined in Gemfile
43
+ end
44
+
45
+ Jeweler::RubygemsDotOrgTasks.new
46
+
47
+ #-------------------------------------------------------------------------------
48
+ # Testing
49
+
50
+ Rake::TestTask.new(:test) do |test|
51
+ test.libs << 'lib' << 'test'
52
+ test.pattern = 'test/**/test_*.rb'
53
+ test.verbose = true
54
+ end
55
+
56
+ task :default => :test
57
+
58
+ #-------------------------------------------------------------------------------
59
+ # Documentation
60
+
61
+ Rake::RDocTask.new do |rdoc|
62
+ version = Coral::Cloud::VERSION
63
+
64
+ rdoc.rdoc_dir = 'rdoc'
65
+ rdoc.title = "coral_cloud #{version}"
66
+ rdoc.rdoc_files.include('README*')
67
+ rdoc.rdoc_files.include('lib/**/*.rb')
68
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,236 @@
1
+
2
+ module Coral
3
+ module Cloud
4
+ class Base < Memory
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Properties
8
+
9
+ @@instances = {}
10
+
11
+ #-----------------------------------------------------------------------------
12
+ # Constructor / Destructor
13
+
14
+ def self.create(name, options = {})
15
+ options[:name] = name unless options.has_key?(:name)
16
+ @@instances[name] = new(options)
17
+ return @@instances[name]
18
+ end
19
+
20
+ #---
21
+
22
+ def self.delete(name)
23
+ if @@instances.has_key?(name) && @@instances[name]
24
+ @@instances.delete(name)
25
+ end
26
+ end
27
+
28
+ #-----------------------------------------------------------------------------
29
+
30
+ def self.[](name)
31
+ if ! @@instances.has_key?(name) || ! @@instances[name]
32
+ @@instances[name] = new({ :name => name })
33
+ end
34
+ return @@instances[name]
35
+ end
36
+
37
+ #-----------------------------------------------------------------------------
38
+
39
+ def initialize(options = {})
40
+ super(options)
41
+
42
+ @repositories = {}
43
+ @shares = {}
44
+ @servers = {}
45
+ end
46
+
47
+ #-----------------------------------------------------------------------------
48
+ # Property accessors / modifiers
49
+
50
+ def repositories(reset = false)
51
+ if reset || @repositories.empty?
52
+ @repositories = {}
53
+ get('repositories', {}, :hash).each do |submodule, remote_dir|
54
+ @repositories[submodule] = Coral::Repository.new({
55
+ :name => submodule,
56
+ :directory => @directory,
57
+ :submodule => submodule,
58
+ :remote_dir => remote_dir,
59
+ })
60
+ end
61
+ end
62
+ return @repositories
63
+ end
64
+
65
+ #---
66
+
67
+ def set_repositories(values = {})
68
+ return set('repositories', values)
69
+ end
70
+
71
+ #---
72
+
73
+ def repository(local_repo, default = '', format = false)
74
+ return get_group('repositories', local_repo, nil, default, format)
75
+ end
76
+
77
+ #---
78
+
79
+ def set_repository(local_repo, remote_repo)
80
+ return set_group('repositories', local_repo, nil, remote_repo)
81
+ end
82
+
83
+ #---
84
+
85
+ def delete_repository(local_repo)
86
+ return delete_group('repositories', local_repo, nil)
87
+ end
88
+
89
+ #-----------------------------------------------------------------------------
90
+
91
+ def settings(group)
92
+ return get_group('settings', group, nil, {}, :hash)
93
+ end
94
+
95
+ #---
96
+
97
+ def set_settings(group, values = {})
98
+ return set_group('settings', group, nil, values)
99
+ end
100
+
101
+ #---
102
+
103
+ def delete_settings(group)
104
+ return delete_group('settings', group, nil)
105
+ end
106
+
107
+ #---
108
+
109
+ def setting(group, key, default = '', format = false)
110
+ return get_group('settings', group, key, default, format)
111
+ end
112
+
113
+ #---
114
+
115
+ def set_setting(group, key, value = '')
116
+ return set_group('settings', group, key, value)
117
+ end
118
+
119
+ #---
120
+
121
+ def delete_setting(group, key)
122
+ return delete_group('settings', group, key)
123
+ end
124
+
125
+ #-----------------------------------------------------------------------------
126
+
127
+ def shares(reset = false)
128
+ if reset || @shares.empty?
129
+ @shares = {}
130
+ get('shares', {}, :hash).each do |name, share_info|
131
+ share_info = Coral::Util::Data.merge([ {
132
+ :name => name,
133
+ :directory => File.join(@directory, share_info['local_dir']),
134
+ }, symbol_map(share_info) ])
135
+
136
+ @shares[name] = Coral::Cloud::Share.new(share_info)
137
+ end
138
+ end
139
+ return @shares
140
+ end
141
+
142
+ #---
143
+
144
+ def set_shares(values = {})
145
+ return set('shares', values)
146
+ end
147
+
148
+ #---
149
+
150
+ def share(name, key = nil, default = {}, format = false)
151
+ return get_group('shares', name, key, default, format)
152
+ end
153
+
154
+ #---
155
+
156
+ def set_share(name, key = nil, value = {})
157
+ return set_group('shares', name, key, value)
158
+ end
159
+
160
+ #---
161
+
162
+ def delete_share(name, key = nil)
163
+ return delete_group('shares', name, key)
164
+ end
165
+
166
+ #-----------------------------------------------------------------------------
167
+
168
+ def servers(reset = false)
169
+ if reset || @servers.empty?
170
+ @servers = {}
171
+ get('servers', {}, :hash).each do |name, server_info|
172
+ server_info = Coral::Util::Data.merge([ {
173
+ :cloud => self,
174
+ :machine => name,
175
+ }, symbol_map(server_info) ])
176
+
177
+ @servers[name] = Coral::Cloud::Server.new(server_info)
178
+ end
179
+ end
180
+ return @servers
181
+ end
182
+
183
+ #---
184
+
185
+ def set_servers(values = {})
186
+ return set('servers', values)
187
+ end
188
+
189
+ #---
190
+
191
+ def server(name, key = nil, default = {}, format = false)
192
+ return get_group('servers', name, key, default, format)
193
+ end
194
+
195
+ #---
196
+
197
+ def set_server(name, key = nil, value = {})
198
+ return set_group('servers', name, key, value)
199
+ end
200
+
201
+ #---
202
+
203
+ def delete_server(name, key = nil)
204
+ return delete_group('servers', name, key)
205
+ end
206
+
207
+ #-----------------------------------------------------------------------------
208
+
209
+ def search(server, key, default = '', format = false)
210
+ value = default
211
+ server_info = server(server)
212
+
213
+ if server_info[key]
214
+ value = server_info[key]
215
+ else
216
+ settings = {}
217
+ if server_info.has_key?('settings')
218
+ array(server_info['settings']).each do |group|
219
+ settings = settings(group)
220
+
221
+ if settings.has_key?(key)
222
+ if value.is_a?(Array) && settings[key].is_a?(Array)
223
+ value = value | settings[key]
224
+ else
225
+ value = settings[key]
226
+ end
227
+ break
228
+ end
229
+ end
230
+ end
231
+ end
232
+ return filter(value, format)
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,99 @@
1
+
2
+ module Coral
3
+ class PuppetEvent < Event
4
+
5
+ #-----------------------------------------------------------------------------
6
+ # Properties
7
+
8
+ TYPE = :puppet
9
+
10
+ #-----------------------------------------------------------------------------
11
+ # Constructor / Destructor
12
+
13
+ def initialize(options = {})
14
+ options[:type] = TYPE
15
+
16
+ super(options)
17
+
18
+ if options.has_key?(:string)
19
+ items = options[:string].split(':')
20
+ self.element = items[0]
21
+ self.operation = items[1]
22
+ self.message = items[2]
23
+ end
24
+ end
25
+
26
+ #-----------------------------------------------------------------------------
27
+ # Property accessors / modifiers
28
+
29
+ def element
30
+ return property(:element, '', :string)
31
+ end
32
+
33
+ #---
34
+
35
+ def element=element
36
+ set_property(:element, string(element))
37
+ end
38
+
39
+ #---
40
+
41
+ def operation
42
+ return property(:operation, '', :string)
43
+ end
44
+
45
+ #---
46
+
47
+ def operation=operation
48
+ set_property(:operation, string(operation))
49
+ end
50
+
51
+ #--
52
+
53
+ def message
54
+ return property(:message, '', :string)
55
+ end
56
+
57
+ #---
58
+
59
+ def message=message
60
+ set_property(:message, string(message))
61
+ end
62
+
63
+ #-----------------------------------------------------------------------------
64
+ # Import / Export
65
+
66
+ def export
67
+ return "#{type}:#{element}:#{operation}:#{message}"
68
+ end
69
+
70
+ #-----------------------------------------------------------------------------
71
+ # Event handling
72
+
73
+ def check(source)
74
+ if source.match(/notice:\s+(.+?):\s+(.+)\s*/)
75
+ source_element = $1
76
+ source_operation = ''
77
+ source_message = $2
78
+
79
+ source_elements = source_element.split('/')
80
+ source_operation = source_elements.pop.strip unless source_elements.last.match(/[\[\]]/)
81
+
82
+ if source_operation
83
+ source_element = source_elements.join('/').strip
84
+
85
+ logger.debug("#{source_element} includes: #{element} -- " + ( source_element.include?(element) ? 'true' : 'false' ))
86
+ logger.debug("#{source_operation} is: #{operation} -- " + ( source_operation == operation ? 'true' : 'false' ))
87
+ logger.debug("#{source_message} includes: #{message} -- " + ( source_message.include?(message) ? 'true' : 'false' ))
88
+
89
+ if source_element.include?(element) && source_operation == operation && source_message.include?(message)
90
+ logger.debug("MATCH! -> #{element} - #{operation} - #{message}")
91
+ return true
92
+ end
93
+ end
94
+ end
95
+ logger.debug("nothing -> #{element} - #{operation} - #{message}")
96
+ return false
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,254 @@
1
+
2
+ module Coral
3
+ module Cloud
4
+ class Server < Core
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Constructor / Destructor
8
+
9
+ def initialize(options = {})
10
+ super(options)
11
+
12
+ self.machine = ( options.has_key?(:machine) ? options[:machine] : '' )
13
+ @cloud = ( options.has_key?(:cloud) ? options[:cloud] : Coral.create_cloud(@name, options) )
14
+ end
15
+
16
+ #-----------------------------------------------------------------------------
17
+ # Property accessors / modifiers
18
+
19
+ attr_reader :machine, :name, :cloud
20
+
21
+ #---
22
+
23
+ def machine=machine
24
+ @name = ''
25
+ if machine.is_a?(String)
26
+ @machine = nil
27
+ @name = machine
28
+ else
29
+ @machine = machine
30
+ @name = @machine.name if @machine
31
+ end
32
+ end
33
+
34
+ #---
35
+
36
+ def hostname
37
+ return @cloud.server(@name, 'hostname', '', :string)
38
+ end
39
+
40
+ #---
41
+
42
+ def hostname=hostname
43
+ @cloud.set_server(@name, 'hostname', hostname)
44
+ end
45
+
46
+ #---
47
+
48
+ def public_ip
49
+ return @cloud.server(@name, 'public_ip', '', :string)
50
+ end
51
+
52
+ #---
53
+
54
+ def public_ip=public_ip
55
+ @cloud.set_server(@name, 'public_ip', public_ip)
56
+ end
57
+
58
+ #---
59
+
60
+ def internal_ip
61
+ return @cloud.server(@name, 'internal_ip', '', :string)
62
+ end
63
+
64
+ #---
65
+
66
+ def internal_ip=internal_ip
67
+ @cloud.set_server(@name, 'internal_ip', internal_ip)
68
+ end
69
+
70
+ #---
71
+
72
+ def virtual_hostname
73
+ return @cloud.search(@name, 'virtual_hostname', '', :string)
74
+ end
75
+
76
+ #---
77
+
78
+ def virtual_hostname=virtual_hostname
79
+ @cloud.set_server(@name, 'virtual_hostname', virtual_hostname)
80
+ end
81
+
82
+ #---
83
+
84
+ def virtual_ip
85
+ return @cloud.search(@name, 'virtual_ip', '', :string)
86
+ end
87
+
88
+ #---
89
+
90
+ def virtual_ip=virtual_ip
91
+ @cloud.set_server(@name, 'virtual_ip', virtual_ip)
92
+ end
93
+
94
+ #-----------------------------------------------------------------------------
95
+
96
+ def repositories(options = {}, return_objects = true)
97
+ repositories = array(options[:repos], @cloud.repositories.keys, true)
98
+ results = ( return_objects ? {} : [] )
99
+
100
+ return results unless repositories.is_a?(Array)
101
+
102
+ repositories.each do |repo_name|
103
+ if @cloud.repositories.has_key?(repo_name)
104
+ if return_objects
105
+ results[repo_name] = @cloud.repositories[repo_name]
106
+ else
107
+ results << repo_name
108
+ end
109
+ end
110
+ end
111
+ return results
112
+ end
113
+
114
+ #---
115
+
116
+ def shares(options = {}, return_objects = true)
117
+ shares = array(options[:shares], @cloud.shares.keys, true)
118
+ results = ( return_objects ? {} : [] )
119
+
120
+ return results unless shares.is_a?(Array)
121
+
122
+ shares.each do |share_name|
123
+ if @cloud.shares.has_key?(share_name)
124
+ if return_objects
125
+ results[share_name] = @cloud.shares[share_name]
126
+ else
127
+ results << share_name
128
+ end
129
+ end
130
+ end
131
+ return results
132
+ end
133
+
134
+ #-----------------------------------------------------------------------------
135
+ # Management
136
+
137
+ def start(options = {})
138
+ sync_remotes(options)
139
+
140
+ return true if ! @machine
141
+
142
+ options["provision.enabled"] = false
143
+
144
+ if @machine.created?
145
+ @machine.start(options)
146
+ else
147
+ @machine.up(options)
148
+ end
149
+ return true
150
+ end
151
+
152
+ #-----------------------------------------------------------------------------
153
+
154
+ def update(options = {})
155
+ sync_remotes(options)
156
+
157
+ success = true
158
+ return success if ! @machine
159
+
160
+ if @machine.created?
161
+ if @machine.state == :running
162
+ success = Coral::Command.new("vagrant provision #{@name}").exec! do |line|
163
+ process_puppet_message(line)
164
+ end
165
+ end
166
+ end
167
+ return success
168
+ end
169
+
170
+ #-----------------------------------------------------------------------------
171
+
172
+ def destroy(options = {})
173
+ return true if ! @machine
174
+
175
+ if @machine.created?
176
+ do_destroy = false
177
+
178
+ if options[:force]
179
+ do_destroy = true
180
+ else
181
+ choice = nil
182
+ begin
183
+ choice = ui.ask("Are you sure you want to remove: #{@name}?")
184
+ rescue Errors::UIExpectsTTY
185
+ end
186
+ do_destroy = choice.upcase == "Y"
187
+ end
188
+
189
+ if do_destroy
190
+ @machine.destroy
191
+ end
192
+ end
193
+ return true
194
+ end
195
+
196
+ #-----------------------------------------------------------------------------
197
+ # Repository operations
198
+
199
+ def sync_remotes(options = {})
200
+ repositories(options).each do |repo_name, repo|
201
+ if repo.can_persist?
202
+ if @name != 'all'
203
+ repo.set_remote(@name, public_ip, options)
204
+ end
205
+
206
+ server_ips = []
207
+ @cloud.servers.each do |server_name, server|
208
+ server_ips << server.public_ip
209
+ end
210
+
211
+ options[:add] = true
212
+ repo.set_remote('all', server_ips, options)
213
+ end
214
+ end
215
+ return true
216
+ end
217
+
218
+ #-----------------------------------------------------------------------------
219
+
220
+ def commit(options = {})
221
+ repositories(options).each do |name, repo|
222
+ if repo.can_persist?
223
+ repo.commit('.', options)
224
+ end
225
+ end
226
+ return true
227
+ end
228
+
229
+ #-----------------------------------------------------------------------------
230
+
231
+ def push(options = {})
232
+ sync_remotes(options)
233
+
234
+ success = true
235
+ repositories(options).each do |name, repo|
236
+ if repo.can_persist?
237
+ success = repo.push!(@name, options) do |line|
238
+ process_puppet_message(line)
239
+ end
240
+ break unless success
241
+ end
242
+ end
243
+ return success
244
+ end
245
+
246
+ #-----------------------------------------------------------------------------
247
+ # Utilities
248
+
249
+ def process_puppet_message(line)
250
+ return line.match(/err:\s+/) ? { :success => false, :prefix => 'FAIL' } : true
251
+ end
252
+ end
253
+ end
254
+ end