mystro-common 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ module Mystro
2
+ module Connect
3
+ class Base
4
+ class << self
5
+ attr_accessor :model
6
+ attr_accessor :collection
7
+
8
+ def cname
9
+ self.name.split("::").last.downcase
10
+ end
11
+
12
+ def fog
13
+ Mystro.send(cname).fog || nil rescue nil
14
+ end
15
+
16
+ def after(event, &block)
17
+ @hooks ||= {}
18
+ @hooks[event] ||= []
19
+ @hooks[event] << block
20
+ end
21
+
22
+ def hooks(event, *args)
23
+ Mystro::Log.debug "#{cname}:hooks:#{event} #{@hooks[event].count}"
24
+ @hooks[event].each do |e|
25
+ Mystro::Log.debug "#{cname}:hooks:#{event} #{e}"
26
+ e.call(*args) if e
27
+ end
28
+ end
29
+ end
30
+
31
+ attr_reader :cfg
32
+ attr_reader :opt
33
+ attr_reader :fog
34
+
35
+ def initialize(account)
36
+ a = account.data
37
+ c = cname
38
+
39
+ # opt is used to initialize fog in connect classes
40
+ defaults = a.connect!.fog? ? a.connect.fog : {}
41
+ overrides = a[c] && a[c].fog? ? a[c].fog : {}
42
+ raw = defaults.deep_merge(overrides)
43
+ @opt = Hashie::Mash.new(raw)
44
+
45
+ # cfg is the additional configuration parms (aside from the fog block)
46
+ raw = {}
47
+ if a[c]
48
+ raw = a[c].to_hash
49
+ raw.delete(:fog) if raw[:fog]
50
+ end
51
+ @cfg = Hashie::Mash.new(raw)
52
+
53
+ self.class.after :create do |e, model|
54
+ Mystro::Plugin.run "#{cname}:create", e, model
55
+ end
56
+
57
+ self.class.after :destroy do |e, tags|
58
+ Mystro::Plugin.run "#{cname}:destroy", e, tags
59
+ end
60
+
61
+ connect
62
+ end
63
+
64
+ def connect
65
+ @fog ||= model.new(opt) if opt
66
+ rescue => e
67
+ Mystro::Log.error "#{cname} connect failed: #{e.message} at #{e.backtrace.first}"
68
+ end
69
+
70
+ def connected?
71
+ fog.nil? ? false : true
72
+ end
73
+
74
+ def find(id)
75
+ fog.send(collection).get(id)
76
+ end
77
+
78
+ def all
79
+ fog.send(collection).all
80
+ end
81
+
82
+ def create(model)
83
+ Mystro::Log.debug "#{cname}#create #{model.inspect}"
84
+ Mystro::Log.debug "#{cname}#create #{fog.inspect}"
85
+ e = fog.send(collection).create(model.fog_options)
86
+ e
87
+ end
88
+
89
+ def destroy(models)
90
+ list = [*models].flatten
91
+ list.each do |m|
92
+ Mystro::Log.debug "#{cname}#destroy #{m.rid}"
93
+ e = find(m.rid)
94
+ e.destroy if e
95
+ end
96
+ end
97
+
98
+ def model
99
+ raise "model not set for '#{cname}'" unless self.class.model
100
+ @model ||= self.class.model.constantize
101
+ end
102
+
103
+ def collection
104
+ raise "collection not set for '#{cname}'" unless self.class.collection
105
+ @collection ||= self.class.collection
106
+ end
107
+
108
+ def cname
109
+ self.class.cname
110
+ end
111
+
112
+ def after(*args)
113
+ self.class.after(*args)
114
+ end
115
+
116
+ def hooks(*args)
117
+ self.class.hooks(*args)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ Dir["#{File.dirname(__FILE__)}/connect/*.rb"].each {|file| require "#{file.gsub(/\.rb/,'')}" }
@@ -0,0 +1,281 @@
1
+ module Mystro
2
+ module DSL
3
+ module Template
4
+ class << self
5
+ def load(name_or_file)
6
+ @templates ||= {}
7
+ template_name = nil
8
+ template_file = nil
9
+ if File.exists?(name_or_file)
10
+ template_name = File.basename(name_or_file).gsub(/\.rb$/, "").to_sym
11
+ template_file = name_or_file
12
+ elsif File.exists?("#{dir}/#{name_or_file}.rb")
13
+ template_name = name.to_sym
14
+ template_file = "#{dir}/#{name}.rb"
15
+ end
16
+ raise "could not load template #{template_name} (#{template_file})" unless template_file && File.file?(template_file)
17
+ #raise "template already loaded #{template_name}" if @templates[template_name]
18
+ @templates[template_name] ||= begin
19
+ t = Mystro::DSL::Template::DSL.new(template_name)
20
+ t.instance_eval(File.read(template_file), "Template(#{template_file})")
21
+ @templates[template_name] = t
22
+ end
23
+ end
24
+
25
+ def load_yaml_file(path)
26
+ file = File.expand_path(path)
27
+ raise "Configuration not found: #{path} (#{file})" unless File.exists?(file)
28
+ yaml = YAML.load_file(file)
29
+ yaml = yaml[yaml.keys.first] if yaml.keys.count == 1
30
+
31
+ yaml
32
+ end
33
+
34
+ def list
35
+ Dir["#{dir}/*"].inject({}) do |h, e|
36
+ f = e.gsub("#{dir}/", "")
37
+ f = File.basename(f, ".yml")
38
+ h[f.to_sym] = e
39
+ h
40
+ end
41
+ end
42
+
43
+ private
44
+ def dir
45
+ "#{Mystro.directory}/templates"
46
+ end
47
+ end
48
+
49
+ class DSL
50
+ attr_reader :balancers, :servers
51
+
52
+ def initialize(name)
53
+ @name = name.to_sym
54
+ @balancers = []
55
+ @servers = []
56
+ end
57
+
58
+ def template(&block)
59
+ instance_eval &block
60
+ end
61
+
62
+ def balancer(name, &block)
63
+ balancer = Balancer.new(name)
64
+ balancer.instance_eval &block
65
+ @balancers << balancer
66
+ end
67
+
68
+ def server(name, &block)
69
+ server = Server.new(name)
70
+ server.instance_eval &block
71
+ @servers << server
72
+ end
73
+ end
74
+
75
+ class Base
76
+ def attr(name, value=nil)
77
+ @attrs[name] = value unless value.nil?
78
+ @attrs[name]
79
+ end
80
+
81
+ def list_attr(name, value=nil)
82
+ @attrs[name] ||= []
83
+ unless value.nil?
84
+ if value.kind_of?(Array)
85
+ @attrs[name] += value
86
+ else
87
+ @attrs[name] << value
88
+ end
89
+ end
90
+ @attrs[name]
91
+ end
92
+ end
93
+
94
+ class Server < Base
95
+ def initialize(name)
96
+ @attrs = {
97
+ :name => name.to_sym,
98
+ :roles => [],
99
+ :groups => [],
100
+ :count => 1,
101
+ :image => nil,
102
+ :flavor => nil,
103
+ :keypair => nil,
104
+ :userdata => "default",
105
+ :dnsnames => [],
106
+ }
107
+ end
108
+
109
+ def name
110
+ @attrs[:name]
111
+ end
112
+
113
+ def roles
114
+ @attrs[:roles]
115
+ end
116
+
117
+ def groups
118
+ @attrs[:groups]
119
+ end
120
+
121
+ def dnsnames
122
+ @attrs[:dnsnames]
123
+ end
124
+
125
+ def role(r)
126
+ list_attr(:roles, r)
127
+ end
128
+
129
+ def count(c=nil)
130
+ attr(:count, c)
131
+ end
132
+
133
+ def image(i=nil)
134
+ attr(:image, i)
135
+ end
136
+
137
+ def flavor(f=nil)
138
+ attr(:flavor, f)
139
+ end
140
+
141
+ def group(g)
142
+ list_attr(:groups, g)
143
+ end
144
+
145
+ def keypair(k=nil)
146
+ attr(:keypair, k)
147
+ end
148
+
149
+ def userdata(u=nil)
150
+ attr(:userdata, u)
151
+ end
152
+
153
+ def balancer(b=nil, &block)
154
+ if block_given?
155
+ raise "balancer block must specify name" unless b
156
+ Mystro::DSL::Template::DSL.balancer(b, &block)
157
+ end
158
+ attr(:balancer, b)
159
+ end
160
+
161
+ def dns(name)
162
+ n = name.gsub(Mystro.get_config(:dns_zone), "")
163
+ list_attr(:dnsnames, n)
164
+ end
165
+ end
166
+
167
+ class Balancer
168
+ attr_reader :name, :sticky, :primary, :sticky_type, :sticky_arg
169
+
170
+ def initialize(name)
171
+ @listeners = []
172
+ @name = name.to_sym
173
+ @primary = false
174
+ @sticky = false
175
+ end
176
+
177
+ def primary(enable = nil)
178
+ enable.nil? ? @primary : @primary = enable
179
+ end
180
+
181
+ def listener(&block)
182
+ listener = Listener.new
183
+ listener.instance_eval &block
184
+ @listeners << listener
185
+ end
186
+
187
+ def health(&block)
188
+ healthcheck = HealthCheck.new
189
+ healthcheck.instance_eval &block
190
+ @healthcheck = healthcheck
191
+ end
192
+
193
+ def listeners
194
+ @listeners.map { |e| e.spec }
195
+ end
196
+
197
+ def sticky(type=nil, expires_or_cookie=nil)
198
+ if type && expires_or_cookie
199
+ @sticky = true
200
+ @sticky_type = type
201
+ @sticky_arg = expires_or_cookie
202
+ end
203
+ @sticky
204
+ end
205
+ end
206
+
207
+ class Listener
208
+ def initialize
209
+ @from_proto = nil
210
+ @from_port = nil
211
+ @to_proto = nil
212
+ @to_port = nil
213
+ @cert = nil
214
+ end
215
+
216
+ def from(proto, port)
217
+ @from_proto = proto
218
+ @from_port = port
219
+ end
220
+
221
+ def to(proto, port)
222
+ @to_proto = proto
223
+ @to_port = port
224
+ end
225
+
226
+ def cert(cert)
227
+ @cert = cert
228
+ end
229
+
230
+ def spec
231
+ {
232
+ :from => "#@from_proto:#@from_port",
233
+ :to => "#@to_proto:#@to_port",
234
+ :cert => @cert,
235
+ }
236
+ end
237
+ end
238
+
239
+ class HealthCheck
240
+ def initialize
241
+ @healthy = 10
242
+ @unhealthy = 2
243
+ @interval = 30
244
+ @target = nil
245
+ @timeout = 5
246
+ end
247
+
248
+ def healthy(v)
249
+ @healthy = v
250
+ end
251
+
252
+ def unhealthy(v)
253
+ @unhealthy = v
254
+ end
255
+
256
+ def interval(v)
257
+ @interval = v
258
+ end
259
+
260
+ def target(v)
261
+ @target = v
262
+ end
263
+
264
+ def timeout(v)
265
+ @timeout = v
266
+ end
267
+
268
+ def spec
269
+ raise "target not specified for health check" unless @target
270
+ {
271
+ healthy: @healthy,
272
+ unhealthy: @unhealthy,
273
+ interval: @interval,
274
+ target: @target,
275
+ timeout: @timeout,
276
+ }
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,15 @@
1
+ # make load balancers act more like other models in Fog
2
+ # ultimately a hack, but makes things work for now.
3
+ module Fog
4
+ class Balancer
5
+ def self.new(attributes)
6
+ attributes = attributes.dup
7
+ case provider = attributes.delete(:provider).to_s.downcase.to_sym
8
+ when :aws
9
+ Fog::AWS::ELB.new(attributes)
10
+ else
11
+ raise ArgumentError.new("#{provider} is not a recognized balancer provider")
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/mystro/job.rb ADDED
File without changes
data/lib/mystro/log.rb ADDED
@@ -0,0 +1,58 @@
1
+ require "yell"
2
+
3
+ module Mystro
4
+ class Log
5
+ class << self
6
+ attr_reader :loggers
7
+
8
+ def init
9
+ @loggers = { }
10
+ @loggers["STDOUT"] = Yell.new(STDOUT, :level => [:info, :warn], :format => Yell::NoFormat)
11
+ @loggers["STDERR"] = Yell.new(STDERR, :level => [:error, :fatal], :format => Yell::NoFormat)
12
+ #@loggers["/tmp/mystro.log"] = Yell.new(:datefile, "/tmp/mystro.log", :level => :debug, :format => Yell::DefaultFormat, :keep => 7, :symlink => true)
13
+ end
14
+
15
+ def add(level, dest, format=Yell::DefaultFormat)
16
+ if [STDOUT, STDERR].include?(dest)
17
+ console_disable
18
+ @loggers["STDOUT"] = Yell.new(dest, :level => level, :format => format)
19
+ else
20
+ @loggers.delete(dest) if @loggers[dest]
21
+ FileUtils.mkdir_p(File.dirname(dest))
22
+ @loggers[dest] = Yell.new(:datefile, dest,
23
+ :level => level, :format => format,
24
+ :keep => 7, :symlink => true)
25
+ end
26
+ end
27
+
28
+ def console_disable
29
+ %w{STDOUT STDERR}.each { |s| @loggers.delete(s) }
30
+ end
31
+
32
+ def console_debug
33
+ console_disable
34
+ @loggers["STDOUT"] = Yell.new(STDOUT, :level => [:debug, :info, :warn], :format => Yell::NoFormat)
35
+ @loggers["STDERR"] = Yell.new(STDERR, :level => [:error, :fatal], :format => Yell::NoFormat)
36
+ end
37
+
38
+ def console_quiet
39
+ console_disable
40
+ @loggers["STDOUT"] = Yell.new(STDOUT, :level => [:warn], :format => Yell::NoFormat)
41
+ @loggers["STDERR"] = Yell.new(STDERR, :level => [:error, :fatal], :format => Yell::NoFormat)
42
+ end
43
+
44
+ %w{debug info warn error fatal}.each do |method_name|
45
+ class_eval(<<-METHOD_DEFN, __FILE__, __LINE__)
46
+ def #{method_name}(msg=nil, &block)
47
+ @loggers.each {|k, l| l.#{method_name}(msg, &block) }
48
+ end
49
+ METHOD_DEFN
50
+ end
51
+
52
+ def reset
53
+ @loggers = {}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ Mystro::Log.init
@@ -0,0 +1,71 @@
1
+ require 'mystro-common'
2
+
3
+ module Mystro
4
+ module Model
5
+ class Base
6
+ attr_reader :attributes
7
+
8
+ def initialize(attributes={ })
9
+ # all the stringify keys calls are to make sure that we normalize EVERYTHING before merges
10
+ d = defaults.stringify_keys!
11
+ c = load_from_config.stringify_keys!
12
+ a = attributes.stringify_keys!
13
+ # attributes should ALWAYS be stringified keys
14
+ @attributes = d.deep_merge(c).deep_merge(a).stringify_keys!
15
+ end
16
+
17
+ def defaults
18
+ self.class.defaults || {}
19
+ end
20
+
21
+ def fog_tags(hash=self.class.tagnames.each {|t| hash[t] = send(t) })
22
+ hash.inject({ }) do |h, e|
23
+ (k, v) = e
24
+ h["#{k.to_s.capitalize}"] = v
25
+ h
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def load_from_config
32
+ cname = self.class.name.split("::").last.downcase
33
+ config = { }
34
+ if Mystro.account && Mystro.account[cname]
35
+ config = Mystro.account[cname].to_hash
36
+ end
37
+ config["account"] = Mystro.selected
38
+ config
39
+ end
40
+
41
+ class << self
42
+ attr_reader :tagnames
43
+ attr_reader :attrnames
44
+ attr_reader :defaults
45
+
46
+ def attr(*list)
47
+ @attrnames = list
48
+ list.each do |a|
49
+ define_method(a) do
50
+ @attributes[a.to_s]
51
+ end
52
+ define_method("#{a}=") do |val|
53
+ @attributes[a.to_s] = val
54
+ end
55
+ end
56
+ end
57
+
58
+ def tag(*list)
59
+ @tagnames = list
60
+ attr(*list)
61
+ end
62
+
63
+ def default(hash)
64
+ @defaults = hash
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ Dir["#{File.dirname(__FILE__)}/model/*.rb"].each { |file| require file.gsub(/\.rb/, '') }
@@ -0,0 +1,73 @@
1
+ module Mystro
2
+ module Plugin
3
+ class << self
4
+ def run(event, *args)
5
+ return if Mystro.config.mock
6
+
7
+ @hooks ||= []
8
+ @hooks.select { |e| e[:event] == event }.each do |plugin|
9
+ klass = plugin[:class]
10
+ block = plugin[:block]
11
+ begin
12
+ Mystro::Log.debug "calling #{klass} :: #{event}"
13
+ block.call(args.dup)
14
+ rescue => e
15
+ Mystro::Log.error "failed to run event #{event} for #{klass}: #{e.message}"
16
+ Mystro::Log.debug e
17
+ end
18
+ end
19
+ end
20
+
21
+ def on(klass, event, &block)
22
+ @hooks ||= []
23
+ @hooks << { :event => event, :class => klass, :block => block }
24
+ end
25
+
26
+ def load(plugins={ })
27
+ plugins.each do |plugin, data|
28
+ begin
29
+ f = "#{Mystro.directory}/plugins/#{plugin}"
30
+ Mystro::Log.debug "loading plugin: #{plugin} #{f}"
31
+ require f
32
+ rescue LoadError, StandardError => e
33
+ Mystro::Log.error "error while loading plugin: #{plugin}: #{e.message} at #{e.backtrace.first}"
34
+ end
35
+ end
36
+ end
37
+
38
+ def register(plugin, type, klass)
39
+ @plugins ||= {}
40
+ @plugins[type] ||= {}
41
+ @plugins[type][plugin] = klass
42
+ end
43
+ end
44
+
45
+ module Base
46
+ def self.included(base)
47
+ base.extend self
48
+ end
49
+
50
+ def config_for(klass)
51
+ name = klass.name.split('::').last.downcase.to_sym
52
+ return Mystro.account.plugins[name] if Mystro.account.plugins && Mystro.account.plugins[name]
53
+ { }
54
+ end
55
+
56
+ def on(event, &block)
57
+ Mystro::Plugin.on(self, event, &block)
58
+ end
59
+
60
+ def command(name, desc, klass=nil, &block)
61
+ on "commands:loaded" do |args|
62
+ Mystro::Log.debug "loading commands for #{name}"
63
+ command = args.shift
64
+ if klass
65
+ command.subcommand name, "#{desc} (#{self})", klass
66
+ else
67
+ command.subcommand name, "#{desc} (#{self})", &block
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ require 'erubis'
2
+
3
+ module Mystro
4
+ class Userdata
5
+ class << self
6
+ def create(name, roles, environment, opts={ })
7
+ package = opts.delete(:package)
8
+ dependencies = opts.delete(:dependencies)
9
+ gems = opts.delete(:gems)
10
+ files = opts.delete(:files)
11
+ directory = "#{Mystro.directory}/userdata/#{package}"
12
+ file = "#{directory}/userdata.yml"
13
+ raise "userdata error: package not specified" unless package
14
+ raise "userdata error: pacakge #{package} directory does not exist (#{directory})" unless File.directory?(directory)
15
+ raise "userdata error: configuration file does not exist (#{file})" unless File.exists?(file)
16
+ config = YAML.load_file(file)
17
+
18
+ data = {
19
+ :name => name,
20
+ :roles => roles,
21
+ :environment => environment,
22
+ :nickname => name,
23
+ :account => "unknown",
24
+ :zone => "unknown.local",
25
+ :dependencies => [],
26
+ :gems => [],
27
+ :files => [],
28
+ :directory => directory,
29
+ :template => "userdata.sh.erb",
30
+ }.deep_merge(config.symbolize_keys!).deep_merge(opts.symbolize_keys!)
31
+
32
+ data[:gems] += gems if gems
33
+ data[:dependencies] += dependencies if dependencies
34
+ data[:files] += files if files
35
+
36
+ template = File.open("#{directory}/#{data[:template]}").read
37
+ erb = Erubis::Eruby.new(template)
38
+ out = erb.evaluate(data)
39
+ end
40
+ end
41
+ end
42
+ end