vines-services 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.
Files changed (69) hide show
  1. data/LICENSE +19 -0
  2. data/README +40 -0
  3. data/Rakefile +130 -0
  4. data/bin/vines-services +95 -0
  5. data/conf/config.rb +25 -0
  6. data/lib/vines/services/command/init.rb +209 -0
  7. data/lib/vines/services/command/restart.rb +14 -0
  8. data/lib/vines/services/command/start.rb +30 -0
  9. data/lib/vines/services/command/stop.rb +20 -0
  10. data/lib/vines/services/command/views.rb +26 -0
  11. data/lib/vines/services/component.rb +26 -0
  12. data/lib/vines/services/config.rb +105 -0
  13. data/lib/vines/services/connection.rb +120 -0
  14. data/lib/vines/services/controller/attributes_controller.rb +19 -0
  15. data/lib/vines/services/controller/base_controller.rb +99 -0
  16. data/lib/vines/services/controller/disco_info_controller.rb +61 -0
  17. data/lib/vines/services/controller/labels_controller.rb +17 -0
  18. data/lib/vines/services/controller/members_controller.rb +44 -0
  19. data/lib/vines/services/controller/messages_controller.rb +66 -0
  20. data/lib/vines/services/controller/probes_controller.rb +45 -0
  21. data/lib/vines/services/controller/services_controller.rb +81 -0
  22. data/lib/vines/services/controller/subscriptions_controller.rb +39 -0
  23. data/lib/vines/services/controller/systems_controller.rb +45 -0
  24. data/lib/vines/services/controller/transfers_controller.rb +58 -0
  25. data/lib/vines/services/controller/uploads_controller.rb +62 -0
  26. data/lib/vines/services/controller/users_controller.rb +127 -0
  27. data/lib/vines/services/core_ext/blather.rb +46 -0
  28. data/lib/vines/services/core_ext/couchrest.rb +33 -0
  29. data/lib/vines/services/indexer.rb +195 -0
  30. data/lib/vines/services/priority_queue.rb +94 -0
  31. data/lib/vines/services/roster.rb +70 -0
  32. data/lib/vines/services/storage/couchdb/fragment.rb +23 -0
  33. data/lib/vines/services/storage/couchdb/service.rb +170 -0
  34. data/lib/vines/services/storage/couchdb/system.rb +141 -0
  35. data/lib/vines/services/storage/couchdb/upload.rb +66 -0
  36. data/lib/vines/services/storage/couchdb/user.rb +137 -0
  37. data/lib/vines/services/storage/couchdb/vcard.rb +13 -0
  38. data/lib/vines/services/storage/couchdb.rb +157 -0
  39. data/lib/vines/services/storage.rb +33 -0
  40. data/lib/vines/services/throttle.rb +26 -0
  41. data/lib/vines/services/version.rb +7 -0
  42. data/lib/vines/services/vql/compiler.rb +94 -0
  43. data/lib/vines/services/vql/vql.citrus +115 -0
  44. data/lib/vines/services/vql/vql.rb +186 -0
  45. data/lib/vines/services.rb +71 -0
  46. data/test/config_test.rb +242 -0
  47. data/test/priority_queue_test.rb +23 -0
  48. data/test/storage/couchdb_test.rb +30 -0
  49. data/test/vql/compiler_test.rb +96 -0
  50. data/test/vql/vql_test.rb +233 -0
  51. data/web/coffeescripts/api.coffee +51 -0
  52. data/web/coffeescripts/commands.coffee +18 -0
  53. data/web/coffeescripts/files.coffee +315 -0
  54. data/web/coffeescripts/init.coffee +21 -0
  55. data/web/coffeescripts/services.coffee +356 -0
  56. data/web/coffeescripts/setup.coffee +503 -0
  57. data/web/coffeescripts/systems.coffee +371 -0
  58. data/web/images/default-service.png +0 -0
  59. data/web/images/linux.png +0 -0
  60. data/web/images/mac.png +0 -0
  61. data/web/images/run.png +0 -0
  62. data/web/images/windows.png +0 -0
  63. data/web/index.html +17 -0
  64. data/web/stylesheets/common.css +52 -0
  65. data/web/stylesheets/files.css +218 -0
  66. data/web/stylesheets/services.css +181 -0
  67. data/web/stylesheets/setup.css +117 -0
  68. data/web/stylesheets/systems.css +142 -0
  69. metadata +230 -0
@@ -0,0 +1,157 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ class Storage
6
+ class CouchDB < Storage
7
+ register :couchdb
8
+
9
+ module ClassMethods
10
+ # Override CouchRest::Model::Persistence::ClassMethods#build_from_database
11
+ # to instantiate the fully qualified model class name. We store the
12
+ # bare class name in doc['type'] (e.g. Service rather than
13
+ # Vines::Services::CouchModels::Service), so outside query processes
14
+ # don't need to know our class hierarchy.
15
+ def build_from_database(doc = {}, options = {}, &block)
16
+ src = doc[model_type_key]
17
+ base = (src.blank? || src == self.to_s) ? self : "Vines::Services::CouchModels::#{src}".constantize
18
+ base.new(doc, options.merge(:directly_set_attributes => true), &block)
19
+ end
20
+
21
+ # CouchRest::Model uses Class#to_s to determine design document names
22
+ # as well as the value of doc['type']. Strip off all module names to
23
+ # get clean design document URLs.
24
+ def to_s
25
+ self.name.split('::').last
26
+ end
27
+ end
28
+
29
+ %w[host port database tls username password index_dir].each do |name|
30
+ define_method(name) do |*args|
31
+ if args.first
32
+ @config[name.to_sym] = args.first
33
+ else
34
+ @config[name.to_sym]
35
+ end
36
+ end
37
+ end
38
+
39
+ def initialize(&block)
40
+ @config = {}
41
+ instance_eval(&block)
42
+ [:host, :port, :database, :index_dir].each do |key|
43
+ raise "Must provide #{key}" unless @config[key]
44
+ end
45
+
46
+ @config[:index_dir] = File.expand_path(@config[:index_dir])
47
+ unless File.directory?(@config[:index_dir]) && File.writable?(@config[:index_dir])
48
+ raise 'Must provide a writable index directory'
49
+ end
50
+
51
+ @url = url(@config)
52
+ init_couch_rest
53
+
54
+ db = "%s-%s-%s.db" % [host, port, database]
55
+ @index = Indexer[File.join(@config[:index_dir], db)]
56
+ end
57
+
58
+ def get(path, &callback)
59
+ http(path, :get, &callback)
60
+ end
61
+
62
+ def post(path, body, &callback)
63
+ http(path, :post, body, &callback)
64
+ end
65
+
66
+ def delete(path, &callback)
67
+ http(path, :get) do |doc|
68
+ if doc
69
+ http("#{path}?rev=#{doc['_rev']}", :delete, &callback)
70
+ else
71
+ yield
72
+ end
73
+ end
74
+ end
75
+
76
+ def save(doc, &callback)
77
+ http('', :post, doc.to_json, &callback)
78
+ end
79
+
80
+ def index(system)
81
+ @index << system.ohai
82
+ end
83
+
84
+ def query(sql, *params, &callback)
85
+ @index.find(sql, params, &callback)
86
+ end
87
+
88
+ def store_file(path)
89
+ file = CouchModels::Upload.find_by_name(File.basename(path))
90
+ unless file
91
+ File.delete(path)
92
+ return
93
+ end
94
+
95
+ http = EM::HttpRequest.new("#{@url}/#{file.id}/data?rev=#{file.rev}").put(
96
+ head: {'Content-Type' => 'application/octet-stream'},
97
+ file: path
98
+ )
99
+ http.callback {|chunk| yield }
100
+ end
101
+
102
+ def create_views
103
+ # FIXME Use views in CouchRest::Model classes to populate db
104
+ designs = {}
105
+
106
+ EM.run do
107
+ http('', :put) do # create db
108
+ designs.each do |name, views|
109
+ get("/_design/#{name}") do |doc|
110
+ doc ||= {"_id" => "_design/#{name}"}
111
+ doc['language'] = 'javascript'
112
+ doc['views'] = views
113
+ save(doc) { EM.stop }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def http(path, method, body=nil)
123
+ args = {}.tap do |opts|
124
+ opts[:head] = {'Content-Type' => 'application/json'}
125
+ opts[:body] = body if body
126
+ end
127
+
128
+ http = EM::HttpRequest.new("#{@url}#{path}").send(method, args)
129
+ http.errback { yield }
130
+ http.callback do
131
+ doc = if (200...300).include?(http.response_header.status)
132
+ JSON.parse(http.response) rescue nil
133
+ end
134
+ yield doc
135
+ end
136
+ end
137
+
138
+ def url(config)
139
+ scheme = config[:tls] ? 'https' : 'http'
140
+ user, password = config.values_at(:username, :password)
141
+ credentials = empty?(user, password) ? '' : "%s:%s@" % [user, password]
142
+ "%s://%s%s:%s/%s" % [scheme, credentials, *config.values_at(:host, :port, :database)]
143
+ end
144
+
145
+ def init_couch_rest
146
+ *url, _ = @url.split('/')
147
+ server = CouchRest::Server.new(url.join('/'))
148
+ CouchRest::Model::Base.database = server.database(database)
149
+ end
150
+
151
+ def escape(jid)
152
+ URI.escape(jid, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ class Storage
6
+ include Vines::Log
7
+
8
+ @@nicks = {}
9
+
10
+ # Register a nickname that can be used in the config file to specify this
11
+ # storage implementation.
12
+ def self.register(name)
13
+ @@nicks[name.to_sym] = self
14
+ end
15
+
16
+ def self.from_name(name, &block)
17
+ klass = @@nicks[name.to_sym]
18
+ raise "#{name} storage class not found" unless klass
19
+ klass.new(&block)
20
+ end
21
+
22
+ private
23
+
24
+ # Return true if any of the arguments are nil or empty strings.
25
+ # For example:
26
+ # username, password = 'alice@wonderland.lit', ''
27
+ # empty?(username, password) #=> true
28
+ def empty?(*args)
29
+ args.flatten.any? {|arg| (arg || '').strip.empty? }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ # Send many outgoing stanzas to the server at a sustained rate that won't
6
+ # cause the server to shutdown the component's stream with a policy_violation
7
+ # error.
8
+ class Throttle
9
+ def initialize(stream, delay=0.1)
10
+ @stream, @delay = stream, delay
11
+ end
12
+
13
+ # Send the nodes to the server at a constant rate. The nodes are sent
14
+ # asynchronously, so this method returns immediately.
15
+ def async_send(nodes)
16
+ timer = EM::PeriodicTimer.new(@delay) do
17
+ if node = nodes.shift
18
+ @stream.write(node)
19
+ else
20
+ timer.cancel
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+
3
+ module Vines
4
+ module Services
5
+ module VQL
6
+ # Compiles Vines Query Language queries into JavaScript for CouchDB views
7
+ # and SQL for sqlite queries. The output of the compiler is a fully formed
8
+ # query that can be sent directly to the database.
9
+ class Compiler
10
+ Citrus.load(File.expand_path('../vql.citrus', __FILE__))
11
+
12
+ # Return the compiled CouchDB map function. Raise an exception on
13
+ # compilation failure. The exception's error message can be shown to
14
+ # the user to debug the syntax.
15
+ def to_js(code)
16
+ raise ArgumentError, 'code required' if code.nil? || code.strip.empty?
17
+ expr = VinesQL.parse(code.strip)
18
+ %Q{
19
+ function(doc) {
20
+ if (doc.type != 'System') return;
21
+ try {
22
+ var match = #{expr.js};
23
+ if (match) {
24
+ var name = doc['_id'].replace('system:', '');
25
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
26
+ emit(name, os);
27
+ }
28
+ } catch(e) {
29
+ log(e.message);
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ # Return the compiled CouchDB map function for all services. Raise an
36
+ # exception on compilation failure. The exception's error message can
37
+ # be shown to the user to debug the syntax.
38
+ def to_full_js(services)
39
+ maps = services.map do |service|
40
+ expr = VinesQL.parse(service.code.strip)
41
+ %Q{
42
+ try {
43
+ var match = #{expr.js};
44
+ if (match) {
45
+ emit([0, '#{service.id}'], {name: name, os: os});
46
+ emit([1, name], '#{service.id}');
47
+ }
48
+ } catch(e) {
49
+ log(e.message);
50
+ }
51
+ }
52
+ end
53
+
54
+ %Q{
55
+ function(doc) {
56
+ if (doc.type != 'System') return;
57
+ var name = doc['_id'].replace('system:', '');
58
+ var os = doc.ohai.kernel.os.toLowerCase().replace('gnu/', '');
59
+ #{maps.join}
60
+ }
61
+ }
62
+ end
63
+
64
+ # Return the compiled sqlite SQL query along with an array of parameter
65
+ # replacement values. Raise an exception on compilation failure. The
66
+ # exception's error message can be shown to the user to debug the syntax.
67
+ def to_sql(code)
68
+ raise ArgumentError, 'code required' if code.nil? || code.strip.empty?
69
+ expr = VinesQL.parse(code.strip)
70
+
71
+ keys, values = expr.params.partition.each_with_index {|p, ix| ix % 2 == 0 }
72
+ joins = keys.each_with_index.map do |k, ix|
73
+ "inner join attributes a%s on id=a%s.system_id and a%s.key=?" % [ix, ix, ix]
74
+ end
75
+
76
+ where = expr.sql.tap do |sql|
77
+ values.size.times do |ix|
78
+ sql.sub!(/(^|[^\.])value/, "\\1a#{ix}.value")
79
+ end
80
+ end
81
+
82
+ sql = %Q{
83
+ select name, os from systems
84
+ #{joins.join("\n")}
85
+ where #{where}
86
+ order by name
87
+ }
88
+
89
+ [sql, [keys, values].flatten]
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,115 @@
1
+ grammar VinesQL
2
+ rule expr
3
+ expr:(disjunctive | stmt)
4
+ end
5
+
6
+ rule disjunctive
7
+ (lhs:stmt 'or' ws rhs:expr) <Vines::Services::VQL::Or>
8
+ end
9
+
10
+ rule stmt
11
+ stmt:(conjunctive | term)
12
+ end
13
+
14
+ rule conjunctive
15
+ (lhs:term 'and' ws rhs:stmt) <Vines::Services::VQL::And>
16
+ end
17
+
18
+ rule term
19
+ is_not | is | ltgt | starts_with | ends_with | not_like | like | group
20
+ end
21
+
22
+ rule is
23
+ (lhs:member 'is' ws rhs:value) <Vines::Services::VQL::Is>
24
+ end
25
+
26
+ rule is_not
27
+ (lhs:member 'is' ws 'not' ws rhs:value) <Vines::Services::VQL::IsNot>
28
+ end
29
+
30
+ rule like
31
+ (lhs:member 'like' ws rhs:string) <Vines::Services::VQL::Like>
32
+ end
33
+
34
+ rule not_like
35
+ (lhs:member 'not' ws 'like' ws rhs:string) <Vines::Services::VQL::NotLike>
36
+ end
37
+
38
+ rule starts_with
39
+ (lhs:member 'starts' ws 'with' ws rhs:string) <Vines::Services::VQL::StartsWith>
40
+ end
41
+
42
+ rule ends_with
43
+ (lhs:member 'ends' ws 'with' ws rhs:string) <Vines::Services::VQL::EndsWith>
44
+ end
45
+
46
+ rule ltgt
47
+ (lhs:member op:([<>] '='?) ws rhs:number) <Vines::Services::VQL::LtGt>
48
+ end
49
+
50
+ rule value
51
+ string | number | keyword
52
+ end
53
+
54
+ rule group
55
+ (lparen expr rparen) <Vines::Services::VQL::Group>
56
+ end
57
+
58
+ rule member
59
+ (str:(ident ('.' ident)*) ws) <Vines::Services::VQL::Member>
60
+ end
61
+
62
+ rule ident
63
+ (alpha | '_') (alpha | '_' | digits)*
64
+ end
65
+
66
+ rule string
67
+ single_quoted | double_quoted
68
+ end
69
+
70
+ rule single_quoted
71
+ ("'" str:(~"'") "'" ws) <Vines::Services::VQL::SingleQuoted>
72
+ end
73
+
74
+ rule double_quoted
75
+ ('"' str:(~'"') '"' ws) <Vines::Services::VQL::DoubleQuoted>
76
+ end
77
+
78
+ rule keyword
79
+ (str:'null' ws) <Vines::Services::VQL::Null>
80
+ | (str:'true' ws) <Vines::Services::VQL::Terminal>
81
+ | (str:'false' ws) <Vines::Services::VQL::Terminal>
82
+ end
83
+
84
+ rule number
85
+ float | int
86
+ end
87
+
88
+ rule float
89
+ (str:('-'? digits '.' digits) ws) <Vines::Services::VQL::Terminal>
90
+ end
91
+
92
+ rule int
93
+ (str:('-'? digits) ws) <Vines::Services::VQL::Terminal>
94
+ end
95
+
96
+ rule digits
97
+ [0-9]+
98
+ end
99
+
100
+ rule alpha
101
+ [a-zA-Z]+
102
+ end
103
+
104
+ rule lparen
105
+ '(' ws
106
+ end
107
+
108
+ rule rparen
109
+ ')' ws
110
+ end
111
+
112
+ rule ws
113
+ [ \t\n\r]*
114
+ end
115
+ end
@@ -0,0 +1,186 @@
1
+ module Vines
2
+ module Services
3
+ # These modules give semantic meaning to the Vines Query Language Citrus
4
+ # parser. The query language is translated into JavaScript and SQL fragments
5
+ # for use in CouchDB views or sqlite queries. This file must be loaded before
6
+ # vql.citrus.
7
+ module VQL
8
+ module Or
9
+ def js
10
+ "%s || %s" % [lhs.js, rhs.js]
11
+ end
12
+ def sql
13
+ "%s or %s" % [lhs.sql, rhs.sql]
14
+ end
15
+ def params
16
+ [lhs.params, rhs.params].flatten
17
+ end
18
+ end
19
+
20
+ module And
21
+ def js
22
+ "%s && %s" % [lhs.js, rhs.js]
23
+ end
24
+ def sql
25
+ "%s and %s" % [lhs.sql, rhs.sql]
26
+ end
27
+ def params
28
+ [lhs.params, rhs.params].flatten
29
+ end
30
+ end
31
+
32
+ module Is
33
+ def js
34
+ "(%s === %s)" % [lhs.js, rhs.js]
35
+ end
36
+ def sql
37
+ case rhs
38
+ when Vines::Services::VQL::Null
39
+ "value is null"
40
+ else
41
+ "value=?"
42
+ end
43
+ end
44
+ def params
45
+ case rhs
46
+ when Vines::Services::VQL::Null
47
+ [lhs.sql]
48
+ else
49
+ [lhs.sql, rhs.sql]
50
+ end
51
+ end
52
+ end
53
+
54
+ module IsNot
55
+ def js
56
+ "(%s !== %s)" % [lhs.js, rhs.js]
57
+ end
58
+ def sql
59
+ case rhs
60
+ when Vines::Services::VQL::Null
61
+ "value is not null"
62
+ else
63
+ "value <> ?"
64
+ end
65
+ end
66
+ def params
67
+ case rhs
68
+ when Vines::Services::VQL::Null
69
+ [lhs.sql]
70
+ else
71
+ [lhs.sql, rhs.sql]
72
+ end
73
+ end
74
+ end
75
+
76
+ module Like
77
+ def js
78
+ "(%s.indexOf(%s) !== -1)" % [lhs.js, rhs.js]
79
+ end
80
+ def sql
81
+ "value like ?"
82
+ end
83
+ def params
84
+ [lhs.sql, "%#{rhs.sql}%"]
85
+ end
86
+ end
87
+
88
+ module NotLike
89
+ def js
90
+ "(%s.indexOf(%s) === -1)" % [lhs.js, rhs.js]
91
+ end
92
+ def sql
93
+ "value not like ?"
94
+ end
95
+ def params
96
+ [lhs.sql, "%#{rhs.sql}%"]
97
+ end
98
+ end
99
+
100
+ module StartsWith
101
+ def js
102
+ "(%s.lastIndexOf(%s, 0) === 0)" % [lhs.js, rhs.js]
103
+ end
104
+ def sql
105
+ "value like ?"
106
+ end
107
+ def params
108
+ [lhs.sql, "#{rhs.sql}%"]
109
+ end
110
+ end
111
+
112
+ module EndsWith
113
+ def js
114
+ "(%s.match(%s + '$'))" % [lhs.js, rhs.js]
115
+ end
116
+ def sql
117
+ "value like ?"
118
+ end
119
+ def params
120
+ [lhs.sql, "%#{rhs.sql}"]
121
+ end
122
+ end
123
+
124
+ module LtGt
125
+ def js
126
+ "(%s %s %s)" % [lhs.js, op, rhs.js]
127
+ end
128
+ def sql
129
+ "cast(value as number) %s ?" % op
130
+ end
131
+ def params
132
+ [lhs.sql, rhs.sql]
133
+ end
134
+ end
135
+
136
+ module Group
137
+ def js
138
+ "(%s)" % expr.js
139
+ end
140
+ def sql
141
+ "(%s)" % expr.sql
142
+ end
143
+ def params
144
+ expr.params
145
+ end
146
+ end
147
+
148
+ module Terminal
149
+ def js
150
+ str.to_s
151
+ end
152
+ def sql
153
+ str.to_s
154
+ end
155
+ def params
156
+ []
157
+ end
158
+ end
159
+
160
+ module Member
161
+ include Terminal
162
+ def js
163
+ "doc.ohai.#{str}"
164
+ end
165
+ end
166
+
167
+ module SingleQuoted
168
+ include Terminal
169
+ def js
170
+ "'%s'" % str
171
+ end
172
+ end
173
+
174
+ module DoubleQuoted
175
+ include Terminal
176
+ def js
177
+ '"%s"' % str
178
+ end
179
+ end
180
+
181
+ module Null
182
+ include Terminal
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+
3
+ %w[
4
+ couchrest_model
5
+ logger
6
+ bcrypt
7
+ blather/client/client
8
+ cgi
9
+ citrus
10
+ em-http
11
+ fiber
12
+ fileutils
13
+ json
14
+ nokogiri
15
+ sqlite3
16
+ uri
17
+
18
+ vines/log
19
+ vines/daemon
20
+ vines/jid
21
+ vines/kit
22
+
23
+ vines/services/core_ext/blather
24
+ vines/services/core_ext/couchrest
25
+
26
+ vines/services/version
27
+ vines/services/connection
28
+ vines/services/config
29
+ vines/services/component
30
+ vines/services/priority_queue
31
+ vines/services/indexer
32
+ vines/services/throttle
33
+
34
+ vines/services/vql/vql
35
+ vines/services/vql/compiler
36
+
37
+ vines/services/command/init
38
+ vines/services/command/restart
39
+ vines/services/command/start
40
+ vines/services/command/stop
41
+ vines/services/command/views
42
+
43
+ vines/services/storage
44
+ vines/services/storage/couchdb
45
+ vines/services/storage/couchdb/fragment
46
+ vines/services/storage/couchdb/service
47
+ vines/services/storage/couchdb/system
48
+ vines/services/storage/couchdb/upload
49
+ vines/services/storage/couchdb/user
50
+ vines/services/storage/couchdb/vcard
51
+
52
+ vines/services/controller/base_controller
53
+ vines/services/controller/attributes_controller
54
+ vines/services/controller/disco_info_controller
55
+ vines/services/controller/uploads_controller
56
+ vines/services/controller/labels_controller
57
+ vines/services/controller/members_controller
58
+ vines/services/controller/messages_controller
59
+ vines/services/controller/probes_controller
60
+ vines/services/controller/services_controller
61
+ vines/services/controller/subscriptions_controller
62
+ vines/services/controller/systems_controller
63
+ vines/services/controller/transfers_controller
64
+ vines/services/controller/users_controller
65
+ ].each {|f| require f }
66
+
67
+ module Vines
68
+ module Services
69
+ class Forbidden < StandardError; end
70
+ end
71
+ end