rig 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +1 -0
  2. data/conf/accounts/default.yml +3 -3
  3. data/conf/config.yml +33 -6
  4. data/conf/plugins/chef.rb +105 -0
  5. data/conf/plugins/mongo.rb +135 -0
  6. data/{lib/rig/command/database.rb → conf/plugins/mysql.rb} +12 -8
  7. data/conf/plugins/scout.rb +81 -0
  8. data/conf/userdata/chef/userdata.sh.erb +7 -1
  9. data/lib/rig.rb +11 -1
  10. data/lib/rig/account.rb +43 -0
  11. data/lib/rig/command.rb +92 -41
  12. data/lib/rig/command/abstract.rb +1 -2
  13. data/lib/rig/command/balancer.rb +102 -0
  14. data/lib/rig/command/dns.rb +47 -0
  15. data/lib/rig/command/environment.rb +67 -0
  16. data/lib/rig/command/keypair.rb +15 -0
  17. data/lib/rig/command/server.rb +181 -0
  18. data/lib/rig/config.rb +35 -105
  19. data/lib/rig/log.rb +66 -0
  20. data/lib/rig/model/account.rb +1 -1
  21. data/lib/rig/model/connection.rb +13 -6
  22. data/lib/rig/model/environment.rb +12 -8
  23. data/lib/rig/model/instance.rb +3 -8
  24. data/lib/rig/model/template.rb +1 -1
  25. data/lib/rig/model/userdata.rb +1 -1
  26. data/lib/rig/plugin.rb +61 -0
  27. data/lib/rig/version.rb +2 -2
  28. metadata +15 -35
  29. data/lib/rig/chef.rb +0 -59
  30. data/lib/rig/command/balancer/destroy.rb +0 -16
  31. data/lib/rig/command/balancer/list.rb +0 -30
  32. data/lib/rig/command/balancer/listener.rb +0 -31
  33. data/lib/rig/command/balancer/main.rb +0 -40
  34. data/lib/rig/command/balancer/view.rb +0 -19
  35. data/lib/rig/command/dns/create.rb +0 -19
  36. data/lib/rig/command/dns/destroy.rb +0 -16
  37. data/lib/rig/command/dns/edit.rb +0 -20
  38. data/lib/rig/command/dns/list.rb +0 -21
  39. data/lib/rig/command/dns/main.rb +0 -13
  40. data/lib/rig/command/environment/create.rb +0 -21
  41. data/lib/rig/command/environment/destroy.rb +0 -18
  42. data/lib/rig/command/environment/list.rb +0 -31
  43. data/lib/rig/command/environment/main.rb +0 -15
  44. data/lib/rig/command/environment/protect.rb +0 -22
  45. data/lib/rig/command/instance/create.rb +0 -60
  46. data/lib/rig/command/instance/destroy.rb +0 -19
  47. data/lib/rig/command/instance/list.rb +0 -26
  48. data/lib/rig/command/instance/main.rb +0 -16
  49. data/lib/rig/command/instance/tag/get.rb +0 -20
  50. data/lib/rig/command/instance/tag/main.rb +0 -14
  51. data/lib/rig/command/instance/tag/remove.rb +0 -44
  52. data/lib/rig/command/instance/tag/set.rb +0 -46
  53. data/lib/rig/command/instance/view.rb +0 -20
  54. data/lib/rig/command/keypair/list.rb +0 -14
  55. data/lib/rig/command/keypair/main.rb +0 -11
  56. data/lib/rig/command/main.rb +0 -90
  57. data/lib/rig/model/database.rb +0 -23
  58. data/lib/rig/model/database/base.rb +0 -11
  59. data/lib/rig/model/database/mongodb.rb +0 -70
  60. data/lib/rig/model/database/mysql.rb +0 -12
data/lib/rig.rb CHANGED
@@ -1,2 +1,12 @@
1
+ require 'clamp'
2
+ require 'fog'
3
+
1
4
  require 'rig/version'
2
- require 'rig/config'
5
+ require 'rig/config'
6
+ require 'rig/account'
7
+ require 'rig/log'
8
+ require 'rig/plugin'
9
+ require 'rig/model'
10
+
11
+ Rig::Config.data
12
+ Rig::Account.data
@@ -0,0 +1,43 @@
1
+ module Rig
2
+ module Account
3
+ class << self
4
+ attr_reader :name
5
+ attr_reader :loaded
6
+
7
+ def data(name = get)
8
+ if @name != name
9
+ @data = nil
10
+ @loaded = false
11
+ @name = nil
12
+ end
13
+
14
+ @data ||= begin
15
+ Rig::Log.debug "loading account"
16
+ a = Rig::Model::Account.load(name)
17
+ a[:name] = name
18
+
19
+ Rig::Log.debug "loading plugins from account"
20
+ Rig::Plugin.load(a[:plugins]) if a[:plugins]
21
+
22
+ a
23
+ end
24
+ @name = @data[:name]
25
+ @loaded = true
26
+ @data
27
+ end
28
+
29
+ def get
30
+ return ENV['RIG_ACCOUNT'] if ENV['RIG_ACCOUNT']
31
+ return Rig.config[:account] if Rig.config[:account]
32
+ return Rig.config[:default_account] if Rig.config[:default_account]
33
+ return Rig.config[:accounts].first if Rig.config[:accounts] && Rig.config[:accounts].count > 0
34
+ "default"
35
+ end
36
+
37
+ def save
38
+ name = Rig.account[:name]
39
+ Rig::Model::Account.save(name, Rig.account)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,47 +1,98 @@
1
1
  require 'rig'
2
- require 'clamp'
3
- require 'fog'
4
- require 'yaml'
5
-
6
2
  require 'rig/model'
7
3
 
8
4
  require 'rig/command/abstract'
9
5
  require 'rig/command/options'
10
6
 
11
- require 'rig/command/main'
12
- require 'rig/command/ssh'
13
- require 'rig/command/account'
14
- require 'rig/command/config'
15
- require 'rig/command/database'
16
-
17
- require 'rig/command/environment/main'
18
- require 'rig/command/environment/list'
19
- require 'rig/command/environment/create'
20
- require 'rig/command/environment/destroy'
21
- require 'rig/command/environment/protect'
22
-
23
- require 'rig/command/balancer/main'
24
- require 'rig/command/balancer/list'
25
- require 'rig/command/balancer/view'
26
- require 'rig/command/balancer/destroy'
27
- require 'rig/command/balancer/listener'
28
-
29
- require 'rig/command/dns/main'
30
- require 'rig/command/dns/list'
31
- require 'rig/command/dns/create'
32
- require 'rig/command/dns/edit'
33
- require 'rig/command/dns/destroy'
34
-
35
- require 'rig/command/instance/main'
36
- require 'rig/command/instance/list'
37
- require 'rig/command/instance/create'
38
- require 'rig/command/instance/destroy'
39
- require 'rig/command/instance/view'
40
-
41
- require 'rig/command/instance/tag/main'
42
- require 'rig/command/instance/tag/get'
43
- require 'rig/command/instance/tag/set'
44
- require 'rig/command/instance/tag/remove'
45
-
46
- require 'rig/command/keypair/main'
47
- require 'rig/command/keypair/list'
7
+ module Rig
8
+ module Command
9
+ class Main < Abstract
10
+ def merge_file(src, dest)
11
+ old = YAML.load_file(dest)
12
+ FileUtils.mv(dest, "#{dest}.bak.#{Time.now.to_i}")
13
+ new = YAML.load_file(src)
14
+ new.merge!(old)
15
+ File.open(dest, "w") {|f| f.write(new.to_yaml)}
16
+ end
17
+
18
+ subcommand "userdata", "test userdata" do
19
+ parameter "NAME", "name of server"
20
+ parameter "ROLE", "role of server"
21
+ parameter "ENVIRONMENT", "environment of server"
22
+
23
+ def execute
24
+ userdata = Rig::Model::Userdata.create(name, role, environment)
25
+ puts "USERDATA:\n#{userdata}"
26
+ end
27
+ end
28
+
29
+ subcommand "init", "initialize configuration directory" do
30
+ option %w{-f --force}, :flag, "force creation of files", :default => false
31
+ option %w{-d --directory}, "DIRECTORY", "configuration directory", :default => "~/.rig"
32
+
33
+ def execute
34
+ dir = File.expand_path(ENV['RIG_CONFIG'] ? ENV['RIG_CONFIG'] : directory)
35
+ back = "#{dir}.bak.#{Time.now.to_i}"
36
+ conf = File.expand_path("../../../../conf", __FILE__)
37
+
38
+ files = %w{
39
+ config.yml
40
+
41
+ accounts/default.yml
42
+
43
+ templates/solo.yml
44
+ templates/multi.yml
45
+
46
+ userdata/default/userdata.sh.erb
47
+ userdata/default/userdata.yml
48
+ userdata/chef/userdata.sh.erb
49
+ userdata/chef/userdata.yml
50
+ userdata/chef/validation.pem
51
+
52
+ plugins/chef.rb
53
+ plugins/scout.rb
54
+ }
55
+
56
+ unless dir
57
+ puts "directory could not be found: tried environment variables RIG_CONFIG (#{ENV['RIG_CONFIG']}) and HOME (#{ENV['HOME']}"
58
+ exit(1)
59
+ end
60
+
61
+ if File.exists?(dir)
62
+ unless File.directory?(dir)
63
+ puts "moving old config #{dir} to #{back}"
64
+ FileUtils.mv(dir, new)
65
+ end
66
+ end
67
+
68
+ puts "[create] #{dir}/"
69
+ FileUtils.mkdir_p(dir)
70
+
71
+ files.each do |file|
72
+ src = "#{conf}/#{file}"
73
+ dest = "#{dir}/#{file}"
74
+ FileUtils.mkdir_p(File.dirname(dest))
75
+ if File.exists?(dest) && !force?
76
+ puts "[skip] #{dest}"
77
+ else
78
+ puts "[create] #{dest}"
79
+ FileUtils.copy(src, dest)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class << self
87
+ def load
88
+ Rig::Plugin.run "commands:before"
89
+
90
+ Dir["#{File.dirname(__FILE__)}/model/*.rb"].each {|file| require file.gsub(/\.rb/,'')}
91
+
92
+ Rig::Plugin.run "commands:loaded", Rig::Command::Main
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ Rig::Command.load
@@ -9,8 +9,7 @@ module Rig
9
9
  end
10
10
 
11
11
  option ["-a", "--account"], "ACCOUNT", "set the rig account to use", :default => "default" do |a|
12
- Rig.config[:account] = a
13
- a
12
+ Rig::Account.data(a)
14
13
  end
15
14
 
16
15
  option ["-v", "--version"], :flag, "print version number and exit" do |v|
@@ -0,0 +1,102 @@
1
+ require 'rig'
2
+ require 'rest-client'
3
+
4
+ module Rig
5
+ module Command
6
+ class Balancer < Abstract
7
+ self.default_subcommand = 'list'
8
+
9
+ subcommand "list", "list load balancers" do
10
+ def execute
11
+ list = Rig::Model::Balancer.all
12
+ list.each do |lb|
13
+ puts lb.id
14
+ puts "- listeners"
15
+ lb.listeners.each do |l|
16
+ puts " - #{l.protocol}:#{l.lb_port} => #{l.instance_protocol}:#{l.instance_port}"
17
+ end
18
+ puts "- instances"
19
+ lb.instances.each do |inst|
20
+ puts " - #{inst}"
21
+ end
22
+ puts "- zones"
23
+ lb.availability_zones.each do |z|
24
+ puts " - #{z}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ subcommand "destroy", "destroy load balancer" do
31
+ parameter "NAME", "name of load balancer"
32
+
33
+ def execute
34
+ Rig::Model::Balancer.destroy(name)
35
+ end
36
+ end
37
+
38
+ subcommand "listener", "manage listeners for load balancer" do
39
+ subcommand "add", "add a listener to a balancer" do
40
+ parameter "BALANCER", "balancer name"
41
+ parameter "LISTENER", "listener data PROTO:PORT/PROTO:PORT"
42
+ parameter "[CERT]", "arn for server certificate"
43
+ parameter "[POLICY] ...", "policy names"
44
+
45
+ def execute
46
+ (from, to) = listener.split('/')
47
+ #newlist = elb.listeners.create()
48
+ Rig::Model::Balancer.add_listener(balancer, from, to, cert, policy_list)
49
+ end
50
+ end
51
+
52
+ subcommand "rm", "remove a listener from a balancer" do
53
+ parameter "BALANCER", "balancer name"
54
+ parameter "LISTENER", "listener data PROTO:PORT (just the elb side)"
55
+
56
+ def execute
57
+ Rig::Model::Balancer.destroy_listener(balancer, listener)
58
+ end
59
+ end
60
+ end
61
+
62
+ subcommand 'test', 'test a load balancer' do
63
+ option %w{-c --count}, "COUNT", "number of times to loop", :default => 100
64
+ option %w{-p --poll}, "POLL", "poll time", :default => 10
65
+ parameter "NAME", "name of the load balancer to test"
66
+
67
+ def execute
68
+ elb = Rig::Model::Balancer.find(name)
69
+ raise "elb #{name} not found" unless elb
70
+
71
+ dns = elb.dns_name
72
+ count.times do |i|
73
+ puts "### loop #{i}"
74
+ ips = `host #{dns}`.split(/\n/).collect { |e| e.split.last }
75
+ ips.each do |ip|
76
+ val = begin
77
+ RestClient.get("http://#{ip}")
78
+ rescue => e
79
+ e.message
80
+ end
81
+ print ".. #{ip} - #{val}"
82
+ puts
83
+ end
84
+ sleep poll
85
+ end
86
+ end
87
+ end
88
+
89
+ subcommand "view", "view load balancer information" do
90
+ include Options::ShowAll
91
+
92
+ parameter "NAME", "name of instance"
93
+
94
+ def execute
95
+ item = Rig::Model::Balancer.find(name)
96
+ puts item.attributes.to_yaml
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ Rig::Command::Main.subcommand "balancer", "manage load balancers", Rig::Command::Balancer
@@ -0,0 +1,47 @@
1
+
2
+ module Rig
3
+ module Command
4
+ class Dns < Abstract
5
+ self.default_subcommand = 'list'
6
+
7
+ subcommand "create", "create dns entry" do
8
+ parameter 'NAME', 'the name to create'
9
+ parameter 'VALUE', 'where the name points to'
10
+
11
+ def execute
12
+ Rig::Model::Dns.create(name, value)
13
+ end
14
+ end
15
+
16
+ subcommand "destroy", "destroy dns entry" do
17
+ parameter "NAME", "name of dns entry"
18
+ def execute
19
+ Rig::Model::Dns.destroy(name)
20
+ end
21
+ end
22
+
23
+ subcommand "change", "change dns entry" do
24
+ parameter 'NAME', 'the name to change'
25
+ parameter 'VALUE', 'where the name points to'
26
+
27
+ def execute
28
+ Rig::Model::Dns.destroy(name)
29
+ Rig::Model::Dns.create(name, value)
30
+ end
31
+ end
32
+
33
+ subcommand "list", "list dns entries" do
34
+ def execute
35
+ zone = Rig.get_config(:dns_zone)
36
+ records = Rig::Model::Dns.records("#{zone}")
37
+ rows = []
38
+ records.each do |record|
39
+ rows << [zone, record.type, record.ttl, record.name, [*record.value].join(",")]
40
+ end
41
+ print_table(%w{Domain Type TTL Name Value}, rows)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ Rig::Command::Main.subcommand "dns", "manage dns", Rig::Command::Dns
@@ -0,0 +1,67 @@
1
+ require 'rig'
2
+ require 'fog'
3
+
4
+ module Rig
5
+ module Command
6
+ class Environment < Abstract
7
+ self.default_subcommand = "list"
8
+
9
+ subcommand "create", "create environment" do
10
+ parameter "NAME", "the name of the environment"
11
+ parameter "TEMPLATE", "the template to use (solo, multi, etc)", :default => "solo"
12
+
13
+ def execute
14
+ #connection = Rig::Connection.compute
15
+ env = Rig::Model::Environment.create(name, template.to_sym)
16
+ #ap env.servers
17
+ end
18
+ end
19
+
20
+ subcommand "destroy", "destroy environment" do
21
+ parameter "NAME", "name of the environment to destroy"
22
+
23
+ def execute
24
+ Rig::Model::Environment.destroy(name)
25
+ end
26
+ end
27
+
28
+ subcommand "list", "list environments" do
29
+ option %w{-s --simple}, :flag, "just show list of names"
30
+
31
+ def execute
32
+ envs = Rig::Model::Environment.list
33
+
34
+ if simple?
35
+ print_table(%w{Name}, envs)
36
+ else
37
+ rows = []
38
+ envs.each do |e|
39
+ env = Rig::Model::Environment.find(e)
40
+ zones = env.servers.collect { |e| e.availability_zone }
41
+ rows << [env.name, env.template, env.region, env.servers.count, env.balancers.count, env.protected?, zones]
42
+ end
43
+ print_table(%w{Name Template Region Servers# Balancers# Protected? AZs}, rows)
44
+ end
45
+ end
46
+ end
47
+
48
+ subcommand "protect", "set (or disable) protection on environment" do
49
+ option ["-u", "--undo"], :flag, "disable protection instead of enabling"
50
+ parameter "NAME", "the environment to protect"
51
+
52
+ def execute
53
+ env = Rig::Model::Environment.find(name)
54
+ raise "Environment #{name} not found" if env.nil?
55
+ connection = Rig::Connection.compute
56
+ val = undo? ? "false" : "true"
57
+
58
+ env.servers.each do |s|
59
+ connection.create_tags(s.id, "Protected" => val)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ Rig::Command::Main.subcommand "environment", "manage environments", Rig::Command::Environment
@@ -0,0 +1,15 @@
1
+ module Rig
2
+ module Command
3
+ class Keypair < Abstract
4
+ self.default_subcommand = "list"
5
+ subcommand "list", "list key pairs" do
6
+ def execute
7
+ connection = Rig::Connection.compute
8
+ list = connection.key_pairs.all
9
+ puts list.inspect
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ Rig::Command::Main.subcommand "keypair", "manage key pairs", Rig::Command::Keypair
@@ -0,0 +1,181 @@
1
+ require 'rig'
2
+ require 'awesome_print'
3
+
4
+ module Rig
5
+ module Command
6
+ class Server < Abstract
7
+ self.default_subcommand = "list"
8
+ subcommand "create", "create an server" do
9
+ option %w{-i --image}, "IMAGE", "the image / ami to use, default: your account image"
10
+ option %w{-f --flavor}, "FLAVOR", "the flavor / size to use"
11
+ option %w{-k --keypair}, "KEYPAIR", "the key pair / name to use, default: your account keypair"
12
+ option %w{-g --groups}, "GROUPS", "comma separated list of groups, default: your account groups"
13
+ option %w{-r --region}, "GROUPS", "the region, default: your account region"
14
+ option %w{--add}, :flag, "add this to the existing servers of this role and environment"
15
+ option "--[no-]chef", :flag, "use chef to bootstrap", :default => true
16
+
17
+ parameter "ENVIRONMENT", "the environment this server should belong to"
18
+ parameter "ROLE", "the server role", :default => 'solo'
19
+
20
+ def execute
21
+ name = role
22
+ # figure out if there are others of this role, if there are add a number to name to distinguish.
23
+ n = "#{name}.#{environment}.env"
24
+ chef = Rig.config[:chef] ? true : false
25
+
26
+ unless flavor
27
+ raise "must specify flavor"
28
+ end
29
+
30
+ o = {
31
+ :image_id => image,
32
+ :flavor_id => flavor,
33
+ :key_name => keypair,
34
+ :groups => groups.split(','),
35
+ :user_data => Rig::Model::Userdata.create(n, role, environment, :chef => chef)
36
+ }
37
+ tags = { 'Name' => n, 'Environment' => environment, 'Role' => role }
38
+ server = Rig::Model::Instance.create(o, tags)
39
+ puts "created: #{server.id}"
40
+ rescue => e
41
+ puts "error: #{e.message}"
42
+ raise Clamp::HelpWanted, self
43
+ end
44
+
45
+ def default_image
46
+ Rig.account[:image].is_a?(Hash) ? Rig.account[:image].first.last : Rig.account[:image]
47
+ end
48
+
49
+ def default_keypair
50
+ Rig.account[:keypair]
51
+ end
52
+
53
+ def default_groups
54
+ Rig.account[:groups].join(",")
55
+ end
56
+ end
57
+
58
+ subcommand "destroy", "destroy (terminate) server" do
59
+ parameter "NAME", "the name of the server to connect to"
60
+
61
+ def execute
62
+ list = Rig::Model::Instance.find(name)
63
+ Rig::Model::Instance.destroy(list)
64
+ end
65
+ end
66
+ subcommand "list", "list servers" do
67
+ include Options::ShowAll
68
+
69
+ option %w{-l --long}, :flag, "show more information in table"
70
+ parameter "[ENV]", "environment"
71
+
72
+ def execute
73
+ connection = Rig::Connection.compute
74
+ filters = { }
75
+ filters["instance-state-name"] = "running" unless showall?
76
+ filters["tag:Environment"] = env if env
77
+
78
+ list = connection.servers.all(filters)
79
+ instance_list(list, long?)
80
+ end
81
+ end
82
+ subcommand "tag", "manage server tags" do
83
+ subcommand "get", "get tag(s) for this server" do
84
+ parameter "NAME", "the name of the server to get tags for"
85
+
86
+ def execute
87
+ instance = Rig::Model::Instance.find(name)
88
+ raise "server not found #{name}" unless instance
89
+ puts "#{instance.tags.inspect}"
90
+ end
91
+ end
92
+
93
+ subcommand "rm", "remove tag(s) for this server" do
94
+ include Options::Instance
95
+ include Options::InstanceName
96
+
97
+ parameter "TAG", "tag key to remove", :attribute_name => :raw_tag
98
+
99
+ # TODO: fix this to use Model instead
100
+ def execute
101
+ unless iname || iid
102
+ raise "Must set either server name or server id (-iname or -iid)"
103
+ end
104
+
105
+ connection = Rig::Connection.compute
106
+ if iid
107
+ instance = connection.servers.get(iid)
108
+ else
109
+ list = connection.servers.all({ "tag:Name" => iname })
110
+ if list.count > 1
111
+ puts "there is more than one server that matches:"
112
+ instance_list(list)
113
+ return
114
+ end
115
+ instance = list.first
116
+ end
117
+
118
+ unless instance
119
+ puts "could not find server"
120
+ return
121
+ end
122
+
123
+ ap connection.delete_tags(instance.id, raw_tag => instance.tags[raw_tag])
124
+ end
125
+ end
126
+
127
+ subcommand "set", "set tag(s) for this server" do
128
+ include Options::Instance
129
+ include Options::InstanceName
130
+
131
+ parameter "TAGS ...", "tags of the form key:value", :attribute_name => :raw_tags
132
+
133
+ def execute
134
+ unless iname || iid
135
+ raise "Must set either server name or server id (-iname or -iid)"
136
+ end
137
+
138
+ connection = Rig::Connection.compute
139
+ if iid
140
+ instance = connection.servers.get(iid)
141
+ else
142
+ list = connection.servers.all({ "tag:Name" => iname })
143
+ if list.count > 1
144
+ puts "there is more than one server that matches:"
145
+ instance_list(list)
146
+ return
147
+ end
148
+ instance = list.first
149
+ end
150
+
151
+ unless instance
152
+ puts "could not find server"
153
+ return
154
+ end
155
+
156
+ raw_tags.each do |s|
157
+ (k, v) = s.split(':', 2)
158
+ #tags[k] = v if k && v
159
+ connection.create_tags(instance.id, k => v)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ subcommand "view", "view server information" do
165
+ include Options::ShowAll
166
+
167
+ # TODO: something wrong here, view is showing all servers
168
+ parameter "NAME", "name of server"
169
+
170
+ def execute
171
+ list = Rig::Model::Instance.find(name)
172
+ list.each do |item|
173
+ ap item.attributes
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ Rig::Command::Main.subcommand "server", "manage servers", Rig::Command::Server