coral_cloud 0.1.1

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