shu-san-scripts 0.2.0 → 0.2.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 CHANGED
@@ -1,9 +1,9 @@
1
1
  = SHU SAN Scripts
2
2
 
3
- Together these scripts define a +store+ command, used to simplify the
4
- task of creating iSCSI targets on OpenSolaris (Solaris > 11) based
5
- hosts. It has been tested on Nexenta NCP and Open Indiana, but with some
6
- modification it could be made to work on FreeBSD as well.
3
+ Together these scripts define a +store+ command, used to simplify the task of
4
+ creating, deleting and managing iSCSI targets on OpenSolaris (Solaris > 11)
5
+ based hosts. It has been tested on Nexenta NCP and Open Indiana, but with
6
+ some modification it could be made to work on FreeBSD as well.
7
7
 
8
8
  The user interface is deliberately as simple as possible: we assume the
9
9
  user has no underlying knowledge of (Open)Solaris, ZFS and not all that
@@ -47,7 +47,9 @@ arguments
47
47
 
48
48
  Available commands:
49
49
 
50
+ delete_vol Remove the specified target from the iSCSI volume store.
50
51
  help Show help for a command
52
+ list_vols Show the currently defined iSCSI targets on this host.
51
53
  new_vol Create a new iSCSI volume in the SAN volume store.
52
54
 
53
55
  Global options:
data/lib/SANStore.rb ADDED
@@ -0,0 +1,24 @@
1
+ ### Copyright (c) 2010, David Love
2
+ ###
3
+ ### Permission to use, copy, modify, and/or distribute this software for
4
+ ### any purpose with or without fee is hereby granted, provided that the
5
+ ### above copyright notice and this permission notice appear in all copies.
6
+ ###
7
+ ### THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ ### WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ ### MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ### ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ ### WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ### ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ ### OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+ ###
15
+
16
+ module SANStore
17
+
18
+ ## Define Application Global constants
19
+
20
+ # The current SANStore client version.
21
+ VERSION = '0.1'
22
+
23
+ end
24
+
@@ -0,0 +1,29 @@
1
+ ### Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ ###
3
+ ### Permission to use, copy, modify, and/or distribute this software for
4
+ ### any purpose with or without fee is hereby granted, provided that the
5
+ ### above copyright notice and this permission notice appear in all copies.
6
+ ###
7
+ ### THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ ### WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ ### MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ### ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ ### WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ### ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ ### OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+ ###
15
+
16
+ require 'rubygems'
17
+
18
+ # Load command line handling library
19
+ require 'cri'
20
+
21
+ # Install module for command line
22
+ # interface
23
+ module SANStore:CLI
24
+ end
25
+
26
+ # Load the command line handling modules
27
+ require 'SANStore/cli/logger'
28
+ require 'SANStore/cli/commands'
29
+ require 'SANStore/cli/base'
@@ -0,0 +1,103 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+ #
15
+
16
+ # @author Denis Defreyne
17
+ # @author David Love
18
+ #
19
+ # The +CLI+ module acts as the gathering place for all the gloabl options
20
+ # and the sub-commands which the +SANStore+ command can deal with.
21
+ # Sub-commands are themselves defined in the {SANStore::CLI::Commands}
22
+ # module
23
+ module SANStore::CLI
24
+
25
+ # @author Denis Defreyne
26
+ # @author David Love
27
+ #
28
+ # Details the global options applicable to all commands, and the code necessary
29
+ # to process those options. Each sub-command also needs to be registered with
30
+ # this class for it to work: strange things will happen if the sub-command is
31
+ # not set-up here!
32
+ #
33
+ # When creating a new sub-command, the constructor for that sub-command needs
34
+ # to be added to the {Base#initialize} function of *this* class as well. Be sure to
35
+ # also add the path to the file where the sub-command is defined to the file
36
+ # +lib/SANStore/cli/commands.rb+, otherwise you will get warnings of unknown
37
+ # classes.
38
+ class Base < Cri::Base
39
+
40
+ # Instantiates the sub-commands by creating a single reference to each
41
+ # known sub-command.
42
+ #
43
+ # @note This means that if your sub-command is not in this constructor
44
+ # it *will not* be found, and *will not* appear as a valid sub-command.
45
+ # If something is missing from the 'help' command, check this method!
46
+ def initialize
47
+ super('SANStore')
48
+
49
+ # Add help command
50
+ self.help_command = SANStore::CLI::Commands::Help.new
51
+ add_command(self.help_command)
52
+
53
+ # Add other commands
54
+ add_command(SANStore::CLI::Commands::DeleteVol.new)
55
+ add_command(SANStore::CLI::Commands::ListVols.new)
56
+ add_command(SANStore::CLI::Commands::NewVol.new)
57
+ end
58
+
59
+ # Returns the list of global option definitionss.
60
+ def global_option_definitions
61
+ [
62
+ {
63
+ :long => 'help', :short => 'h', :argument => :forbidden,
64
+ :desc => 'show this help message and quit'
65
+ },
66
+ {
67
+ :long => 'no-color', :short => 'C', :argument => :forbidden,
68
+ :desc => 'disable color'
69
+ },
70
+ {
71
+ :long => 'version', :short => 'v', :argument => :forbidden,
72
+ :desc => 'show version information and quit'
73
+ },
74
+ {
75
+ :long => 'verbose', :short => 'V', :argument => :forbidden,
76
+ :desc => 'make store command output more detailed'
77
+ }
78
+ ]
79
+ end
80
+
81
+ # Process the global options, and set/change the application state from them
82
+ def handle_option(option)
83
+ # Handle version option
84
+ if option == :version
85
+ puts "SANStore Bootstrap Client #{SANStore::VERSION} (c) 2011 David Love."
86
+ puts "Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) running on #{RUBY_PLATFORM}"
87
+ exit 0
88
+ # Handle verbose option
89
+ elsif option == :verbose
90
+ SANStore::CLI::Logger.instance.level = :low
91
+ # Handle no-color option
92
+ elsif option == :'no-color'
93
+ SANStore::CLI::Logger.instance.color = false
94
+ # Handle help option
95
+ elsif option == :help
96
+ show_help
97
+ exit 0
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ # @author David Love
16
+ #
17
+ # The +Commands+ module acts as the gathering place for all the sub-commands
18
+ # which the +store+ command can deal with. Each sub-command defines both
19
+ # the command action, options, and associated help text.
20
+ #
21
+ # All commands should live under +lib/SANStore/cli/commands+, with the
22
+ # file-name named after the sub-command defined within it.
23
+
24
+ module SANStore::CLI::Commands
25
+ end
26
+
27
+ require 'SANStore/cli/commands/help'
28
+
29
+ require 'SANStore/cli/commands/delete_vol'
30
+ require 'SANStore/cli/commands/list_vols'
31
+ require 'SANStore/cli/commands/new_vol'
@@ -0,0 +1,113 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ # Volume names are based on RFC 4122 UUIDs
16
+ require "uuidtools"
17
+
18
+ # Use the ANSI library to report the status to the user
19
+ require "ansi/code"
20
+
21
+ # Use the ZFS library
22
+ require "SANStore/zfs/zfs"
23
+
24
+ # Use the COMStar iSCSI library
25
+ require "SANStore/iSCSI/comstar.rb"
26
+
27
+ module SANStore::CLI::Commands
28
+
29
+ # @author David Love
30
+ #
31
+ # The +new_vol+ command adds a new ZFS volume to the specified volume
32
+ # store, and sets it up as an iSCSI target. Defaults are supplied to
33
+ # all arguments, but can be overridden by the user for slightly
34
+ # customised set-up of the volume store. However, the user interface
35
+ # should be kept as simple as possible.
36
+ class DeleteVol < Cri::Command
37
+
38
+ # The name of the sub-command (as it appears in the command line app)
39
+ def name
40
+ 'delete_vol'
41
+ end
42
+
43
+ # The aliases this sub-command is known by
44
+ def aliases
45
+ [
46
+ "rm", "delete", "rm_vol"
47
+ ]
48
+ end
49
+
50
+ # A short help text describing the purpose of this command
51
+ def short_desc
52
+ 'Remove the specified target from the iSCSI volume store.'
53
+ end
54
+
55
+ # A longer description, detailing both the purpose and the
56
+ # use of this command
57
+ def long_desc
58
+ 'This command deletes the specified target from the pool, and ' +
59
+ 'unlinks the volume store backing the target. Since the underlying ' +
60
+ 'volume store is destroyed, this action is ' + ANSI.bold{ "irreversible" } +
61
+ 'and so this command should be used with ' + ANSI.bold{ "great" } + 'care.' +
62
+ "\n\n" +
63
+ 'NOTE: Any clients attached to the volume will have their ' +
64
+ 'iSCSI session forcibly closed. This may result in the premature ' +
65
+ 'death of the client if care is not taken.'
66
+ end
67
+
68
+ # Show the user the basic syntax of this command
69
+ def usage
70
+ "store delete_vol SHARE_NAME"
71
+ end
72
+
73
+ # Define the options for this command
74
+ def option_definitions
75
+ [
76
+ { :short => 'v', :long => 'volume_store', :argument => :optional,
77
+ :desc => 'specifify the ZFS root of the new iSCSI volume. Defaults to "store/volumes".'
78
+ },
79
+ { :short => 'n', :long => 'name', :argument => :optional,
80
+ :desc => 'the name of the new volume. This must be a valid ZFS volume name, and defaults to ' +
81
+ 'an RFC 4122 GUID.'
82
+ },
83
+ { :short => 's', :long => 'size', :argument => :optional,
84
+ :desc => 'the size of the new iSCSI volume. Note that while ZFS allows you to change the size ' +
85
+ 'of the new volume relatively easily, because the iSCSI initiator sees this volume as a raw ' +
86
+ 'device changing the size later may be very easy or very difficult depending on the initiators ' +
87
+ 'operating system (and the specific file system being used). In other words, choose with care: ' +
88
+ 'by default this command uses a size of 20G, which should be enough for most tasks in the labs.'
89
+ },
90
+ ]
91
+ end
92
+
93
+ # Execute the command
94
+ def run(options, arguments)
95
+
96
+ # Check that we have been given a name
97
+ if arguments.size > 1
98
+ SANStore::CLI::Logger.instance.log_level(:high, :error, "You must specify the name of the target to remove")
99
+ exit 1
100
+ end
101
+
102
+ # Delete the iSCSI target
103
+ SANStore::CLI::Logger.instance.log_level(:high, :delete, "Removing target #{arguments[0]}")
104
+ zfs_volume = COMStar.delete_target(arguments[0])
105
+
106
+ # Remove the underlying ZFS store
107
+ SANStore::CLI::Logger.instance.log_level(:low, :delete, "Removing ZFS store for #{arguments[0]}")
108
+ ZFS.delete_volume(zfs_volume)
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,106 @@
1
+ ### Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ ###
3
+ ### Permission to use, copy, modify, and/or distribute this software for
4
+ ### any purpose with or without fee is hereby granted, provided that the
5
+ ### above copyright notice and this permission notice appear in all copies.
6
+ ###
7
+ ### THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ ### WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ ### MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ### ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ ### WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ### ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ ### OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+ ###
15
+
16
+ module SANStore::CLI::Commands
17
+
18
+ # @author Denis Defreyne
19
+ # @author David Love
20
+ #
21
+ # The +help+ command show the user a brief summary of the available
22
+ # sub-commands, and the short description of each of those commands.
23
+ #
24
+ # Further help is available to the user, if one of the sub-commands
25
+ # is named as an argument to this command. In that case, the longer
26
+ # help for the command is displayed.
27
+ #
28
+ # @note This class is merely a helper class: the actual text, options
29
+ # and other details are drawn directly from the source code of those
30
+ # commands. In the execution of this command, we rely on the +cri+
31
+ # and +cli+ libraries to do the hard work of actually processing the
32
+ # sub-commands.
33
+ class Help < Cri::Command
34
+
35
+ # The name of the sub-command (as it appears in the command line app)
36
+ def name
37
+ 'help'
38
+ end
39
+
40
+ # The aliases this sub-command is known by
41
+ def aliases
42
+ []
43
+ end
44
+
45
+ # A short help text describing the purpose of this command
46
+ def short_desc
47
+ 'Show help for a command'
48
+ end
49
+
50
+ # A longer description, detailing both the purpose and the
51
+ # use of this command
52
+ def long_desc
53
+ 'Show help for the given command, or show general help. When no ' +
54
+ 'command is given, a list of available commands is displayed, as ' +
55
+ 'well as a list of global command-line options. When a command is ' +
56
+ 'given, a command description as well as command-specific ' +
57
+ 'command-line options are shown.'
58
+ end
59
+
60
+ # Show the user the basic syntax of this command
61
+ def usage
62
+ "store help [command]"
63
+ end
64
+
65
+ # Execute the command
66
+ def run(options, arguments)
67
+ # Check arguments
68
+ if arguments.size > 1
69
+ $stderr.puts "usage: #{usage}"
70
+ exit 1
71
+ end
72
+
73
+ if arguments.length == 0
74
+ # Build help text
75
+ text = ''
76
+
77
+ # Add title
78
+ text << "A command-line tool for managing iSCSI targets on OpenSolaris.\n"
79
+
80
+ # Add available commands
81
+ text << "\n"
82
+ text << "Available commands:\n"
83
+ text << "\n"
84
+ @base.commands.sort.each do |command|
85
+ text << sprintf(" %-20s %s\n", command.name, command.short_desc)
86
+ end
87
+
88
+ # Add global options
89
+ text << "\n"
90
+ text << "Global options:\n"
91
+ text << "\n"
92
+ @base.global_option_definitions.sort { |x,y| x[:long] <=> y[:long] }.each do |opt_def|
93
+ text << sprintf(" -%1s --%-15s %s\n", opt_def[:short], opt_def[:long], opt_def[:desc])
94
+ end
95
+
96
+ # Display text
97
+ puts text
98
+ elsif arguments.length == 1
99
+ command = @base.command_named(arguments[0])
100
+ puts command.help
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,103 @@
1
+ # Copyright (c) 2009 Denis Defreyne, 2010-2011 David Love
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ # Volume names are based on RFC 4122 UUIDs
16
+ require "uuidtools"
17
+
18
+ # Use the ZFS library
19
+ require "SANStore/zfs/zfs"
20
+
21
+ # Use the COMStar iSCSI library
22
+ require "SANStore/iSCSI/comstar.rb"
23
+
24
+ module SANStore::CLI::Commands
25
+
26
+ # @author David Love
27
+ #
28
+ # The +list_vols+ command show the current iSCSI targets available
29
+ # on this host
30
+ class ListVols < Cri::Command
31
+
32
+ # The name of the sub-command (as it appears in the command line app)
33
+ def name
34
+ 'list_vols'
35
+ end
36
+
37
+ # The aliases this sub-command is known by
38
+ def aliases
39
+ [
40
+ "list_vol", "list", "ls"
41
+ ]
42
+ end
43
+
44
+ # A short help text describing the purpose of this command
45
+ def short_desc
46
+ 'Show the currently defined iSCSI targets on this host.'
47
+ end
48
+
49
+ # A longer description, detailing both the purpose and the
50
+ # use of this command
51
+ def long_desc
52
+ 'Displays a list of valid iSCSI targets, all of which ' +
53
+ 'should be available on the local network' + "\n\n"
54
+ 'NOTE: Because of the way iSCSI works, this host has no ' +
55
+ 'way of knowing what is actually '+ ANSI.bold{ "in" } + ' the volume. So even ' +
56
+ 'if a target is defined, this host has no way of knowing if ' +
57
+ 'a given initiator can actually ' + ANSI.bold{ "use" } +
58
+ 'the contents of this volume. If something appears to be '
59
+ 'wrong, check the set-up of the host to make sure it can '
60
+ 'actually connect to the targets defined here.'
61
+ end
62
+
63
+ # Show the user the basic syntax of this command
64
+ def usage
65
+ "store list_vols"
66
+ end
67
+
68
+ # Define the options for this command
69
+ def option_definitions
70
+ [
71
+ ]
72
+ end
73
+
74
+ # Execute the command
75
+ def run(options, arguments)
76
+
77
+ # Get the list of defined volumes
78
+ volumes = COMStar.list_vols
79
+
80
+ # Show the list to the caller
81
+ text = String.new
82
+ volumes.each{|target|
83
+ text << sprintf("%-60s ", target[:name])
84
+
85
+ if target[:state] == "online" then
86
+ text << sprintf("%-20s ", ANSI.green{ target[:state] })
87
+ else
88
+ text << sprintf("%-20s ", ANSI.black{ target[:state] })
89
+ end
90
+
91
+ if target[:sessions].to_i > 0 then
92
+ text << sprintf("%-10s\n", ANSI.white{ target[:sessions] })
93
+ else
94
+ text << sprintf("%-10s\n", ANSI.black{ target[:sessions] })
95
+ end
96
+ }
97
+ puts sprintf("%-68s %-19s %-10s\n", ANSI.bold{ "Target Name"}, ANSI.bold{ "Status" }, ANSI.bold{ "Open Sessions" })
98
+ puts text
99
+ end
100
+
101
+ end
102
+
103
+ end