kontena-plugin-shell 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,85 @@
1
+ module Kontena::Plugin
2
+ module Shell
3
+ class Command
4
+
5
+ attr_reader :context, :args, :session
6
+
7
+ def self.command(name = nil)
8
+ return @command if instance_variable_defined?(:@command)
9
+ disabled_commands = ENV['KOSH_DISABLED_COMMANDS'].to_s.split(/,/)
10
+ Array(name).each { |name| Shell.commands[name] = self unless disabled_commands.include?(name) }
11
+ @command = name
12
+ end
13
+
14
+ def self.subcommands(subcommands = nil)
15
+ return @subcommands if instance_variable_defined?(:@subcommands)
16
+ @subcommands = {}
17
+ Array(subcommands).each do |sc|
18
+ Array(sc.command).each do |name|
19
+ @subcommands[name] = sc
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.has_subcommands?
25
+ !subcommands.nil? && !subcommands.empty?
26
+ end
27
+
28
+ def has_subcommands?
29
+ self.class.has_subcommands?
30
+ end
31
+
32
+ def self.description(text = nil)
33
+ @description ||= text
34
+ end
35
+
36
+ def self.help(text = nil)
37
+ @help ||= text
38
+ end
39
+
40
+ def self.completions(*completions)
41
+ @completions ||= completions
42
+ end
43
+
44
+ def initialize(context = nil, args = nil, session = nil)
45
+ @args = Array(args)
46
+ @context = context
47
+ @session = session
48
+ end
49
+
50
+ def run
51
+ if has_subcommands?
52
+ if args[1]
53
+ subcommand = self.class.subcommands[args[1]]
54
+ if subcommand
55
+ subcommand.new(context, args[1..-1], session).run
56
+ else
57
+ puts Kontena.pastel.red("Unknown subcommand")
58
+ end
59
+ else
60
+ context << args[0]
61
+ end
62
+ else
63
+ execute
64
+ end
65
+ rescue Kontena::Plugin::Shell::ExitCommand::CleanExit
66
+ puts Kontena.pastel.green("Bye!")
67
+ exit 0
68
+ rescue => ex
69
+ puts Kontena.pastel.red("ERROR: " + ex.message)
70
+ puts Kontena.pastel.green(ex.backtrace.join("\n ")) if ENV["DEBUG"]
71
+ end
72
+ end
73
+
74
+ # SubCommand is just like a command, except it doesn't register the
75
+ # class to Shell.commands.
76
+ class SubCommand < Command
77
+ def self.command(name = nil)
78
+ @command ||= name
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ Dir[File.expand_path('../commands/**/*.rb', __FILE__)].each { |file| require file }
@@ -0,0 +1,29 @@
1
+ require 'kontena/plugin/shell/command'
2
+ require 'kontena/plugin/shell/commands/batch_do'
3
+
4
+ module Kontena::Plugin
5
+ module Shell
6
+ class BatchCommand < Command
7
+
8
+ command 'batch'
9
+ description 'Run/create command batches'
10
+ help -> (context, tokens) {
11
+ if tokens && tokens[2] && subcommands[tokens[2]]
12
+ subcommands[tokens[2]].help
13
+ else
14
+ <<-EOB.gsub(/^\s+/, '')
15
+ To run a series of commands in a batch, use:
16
+
17
+ > batch do
18
+ > command
19
+ > command 2
20
+ > end
21
+ EOB
22
+ end
23
+ }
24
+
25
+ subcommands [Kontena::Plugin::Shell::BatchDoCommand]
26
+ completions *subcommands.keys
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class BatchDoCommand < SubCommand
6
+
7
+ command 'do'
8
+ description 'Run a batch of commands'
9
+ help "Example:\n> batch do\n> master users ls\n> master ls\n> end"
10
+ completions nil
11
+
12
+ def execute
13
+ if args.size > 1
14
+ lines = args[1..-1].join(' ').split(/(?<!\\);/).map(&:strip)
15
+ else
16
+ lines = []
17
+ while buf = Readline.readline("#{Kontena.pastel.green('..')}#{Kontena.pastel.red('>')} ", true)
18
+ buf.strip!
19
+ break if buf == 'end'
20
+ lines << buf unless buf.empty?
21
+ end
22
+ (lines.size + 1).times { Readline::HISTORY.pop }
23
+ Readline::HISTORY.push "batch do #{lines.join('; ')}"
24
+ end
25
+
26
+ lines.each { |line| session.run_command(line) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class ContextTopCommand < Command
6
+ command '/'
7
+ description 'Clear context'
8
+ help "Go to top in context.\n\nYou can also directly call other commands without switching context first by using for example #{Kontena.pastel.yellow('/ master ls')}"
9
+
10
+ def execute
11
+ if args[1] && session
12
+ old_context = context.to_a.clone
13
+ context.top
14
+ session.run_command(args[1..-1].join(' '))
15
+ context.concat(old_context)
16
+ else
17
+ context.top
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class ContextUpCommand < Command
6
+ command '..'
7
+ description 'Go up in context'
8
+ help 'Go up in context'
9
+
10
+ def execute
11
+ context.up
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class DebugCommand < Command
6
+ command 'debug'
7
+ description 'Toggle debug output'
8
+ help 'Use debug on/off to toggle debug output.'
9
+
10
+ completions 'on', 'off', 'api'
11
+
12
+ def execute
13
+ case args[1]
14
+ when 'true', 'on', '1'
15
+ ENV['DEBUG'] = 'true'
16
+ when 'api'
17
+ ENV['DEBUG'] = 'api'
18
+ when 'off', 'false', '0'
19
+ ENV.delete('DEBUG')
20
+ when NilClass
21
+ # do nothing
22
+ else
23
+ puts Kontena.pastel.red("Unknown argument '#{args[1]}'")
24
+ end
25
+ puts "Debug #{Kontena.pastel.send(*ENV['DEBUG'] ? [:green, 'on'] : [:red, 'off'])}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class ExitCommand < Command
6
+ command 'exit'
7
+ description 'Quit shell'
8
+ help 'Enter "exit" to quit.'
9
+
10
+ completions nil
11
+
12
+ CleanExit = Class.new(StandardError)
13
+
14
+ def execute
15
+ raise CleanExit
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,43 @@
1
+ require 'kontena/plugin/shell/command'
2
+
3
+ module Kontena::Plugin
4
+ module Shell
5
+ class HelpCommand < Command
6
+ command 'help'
7
+ description 'Show help'
8
+ help 'Use "help <command>" to see help for a specific command'
9
+ #completions -> (context, tokens, word) { Kontena::Completer.complete(context.to_a + tokens) }
10
+
11
+ def cmd
12
+ full_line = context + args[1..-1]
13
+ cmd = Shell.command(full_line.first) || Shell.command('kontena')
14
+ end
15
+
16
+ def execute
17
+ if cmd.help.respond_to?(:call)
18
+ help_text = cmd.help.call(context, args[1..-1])
19
+ else
20
+ help_text = cmd.help
21
+ end
22
+ puts help_text
23
+
24
+ if cmd.has_subcommands?
25
+ puts
26
+ puts Kontena.pastel.green("Subcommands:")
27
+ cmd.subcommands.each do |name, sc|
28
+ puts sprintf(' %-29s %s', name, sc.description)
29
+ end
30
+ puts
31
+ end
32
+
33
+ if args.empty? || (args.size == 1 && args.first == 'help')
34
+ puts Kontena.pastel.green('KOSH commands:')
35
+ Shell.commands.each do |name, cmd|
36
+ next if cmd == Kontena::Plugin::Shell::KontenaCommand
37
+ puts sprintf(' %-29s %s', name, cmd.description)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+ require 'kontena/main_command' unless Kontena.const_defined?(:MainCommand)
2
+ require 'kontena/plugin/shell/command'
3
+ require 'kontena/plugin/shell/completer'
4
+ require 'kontena/plugin/shell/context'
5
+
6
+ module Kontena::Plugin
7
+ module Shell
8
+ class KontenaCommand < Command
9
+
10
+ command ['kontena'] + Kontena::MainCommand.recognised_subcommands.flat_map(&:names)
11
+ description 'Run Kontena-cli command'
12
+ help -> (context, tokens) { new(context, tokens).subcommand_class.help('').gsub(/^(\s+)\[OPTIONS\] SUB/, "\\1 SUB") }
13
+ completions -> (context, tokens, word) { Kontena::Plugin::Shell::Completer.complete(context.to_a + tokens) }
14
+
15
+ def cmd
16
+ cmd = Kontena::MainCommand.new('')
17
+ cmdline = context.to_a + args
18
+ cmdline.shift if cmdline.first == 'kontena'
19
+ cmd.parse(cmdline)
20
+ cmd
21
+ end
22
+
23
+ def execute
24
+ cmd.run([])
25
+ rescue Clamp::HelpWanted => ex
26
+ unless args.include?('--help') || args.include?('-h')
27
+ context.concat(args)
28
+ end
29
+ rescue SystemExit => ex
30
+ puts Kontena.pastel.red('[Command exited with error]') unless ex.status.zero?
31
+ rescue => ex
32
+ puts Kontena.pastel.red("ERROR: #{ex.message}")
33
+ end
34
+
35
+ def subcommand_class
36
+ (context + args).reject { |t| t.start_with?('-') }.inject(Kontena::MainCommand) do |base, token|
37
+ if base.has_subcommands?
38
+ sc = base.recognised_subcommands.find { |sc| sc.names.include?(token) }
39
+ sc ? sc.subcommand_class : base
40
+ else
41
+ base
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ if ENV["KOSH_DISABLED_COMMANDS"]
50
+ ENV["KOSH_DISABLED_COMMANDS"].split(',').each do |command|
51
+ tokens = command.split(' ')
52
+ subcommand = tokens.pop
53
+ kontena = Kontena::Plugin::Shell::KontenaCommand.new(Kontena::Plugin::Shell::Context.new(tokens))
54
+ found_subcommand = kontena.subcommand_class
55
+ found_subcommand.recognised_subcommands.delete_if { |sc| sc.names.include?(subcommand) }
56
+ end
57
+ end
@@ -0,0 +1,216 @@
1
+ # this is mostly just a wrapped version of the completer in cli
2
+ require 'kontena/cli/common' unless Kontena.const_defined?(:Cli) && Kontena::Cli.const_defined?(:Common)
3
+ require 'yaml'
4
+
5
+ module Kontena::Plugin::Shell
6
+ module Completer
7
+ class Helper
8
+ include Kontena::Cli::Common
9
+
10
+ def grids
11
+ client.get("grids")['grids'].map{|grid| grid['id']}
12
+ rescue
13
+ []
14
+ end
15
+
16
+ def nodes
17
+ client.get("grids/#{current_grid}/nodes")['nodes'].map{|node| node['name']}
18
+ rescue
19
+ []
20
+ end
21
+
22
+ def stacks
23
+ stacks = client.get("grids/#{current_grid}/stacks")['stacks']
24
+ results = []
25
+ results.push stacks.map{|s| s['name']}
26
+ results.delete('null')
27
+ results
28
+ rescue
29
+ []
30
+ end
31
+
32
+ def services
33
+ services = client.get("grids/#{current_grid}/services")['services']
34
+ results = []
35
+ results.push services.map{ |s|
36
+ stack = s['stack']['id'].split('/').last
37
+ if stack != 'null'
38
+ "#{stack}/#{s['name']}"
39
+ else
40
+ s['name']
41
+ end
42
+ }
43
+ results
44
+ rescue
45
+ []
46
+ end
47
+
48
+ def containers
49
+ results = []
50
+ client.get("grids/#{current_grid}/services")['services'].each do |service|
51
+ containers = client.get("services/#{service['id']}/containers")['containers']
52
+ results.push(containers.map{|c| c['name'] })
53
+ results.push(containers.map{|c| c['id'] })
54
+ end
55
+ results
56
+ rescue
57
+ []
58
+ end
59
+
60
+ def yml_services
61
+ if File.exist?('kontena.yml')
62
+ yaml = YAML.safe_load(File.read('kontena.yml'))
63
+ services = yaml['services']
64
+ services.keys
65
+ end
66
+ rescue
67
+ []
68
+ end
69
+
70
+ def yml_files
71
+ Dir["./*.yml"].map{|file| file.sub('./', '')}
72
+ rescue
73
+ []
74
+ end
75
+
76
+ def master_names
77
+ config_file = File.expand_path('~/.kontena_client.json')
78
+ if(File.exist?(config_file))
79
+ config = JSON.parse(File.read(config_file))
80
+ return config['servers'].map{|s| s['name']}
81
+ end
82
+ rescue
83
+ []
84
+ end
85
+ end
86
+
87
+ def self.complete(words)
88
+ while words.first == 'kontena' || words.first == 'complete'
89
+ words.shift
90
+ end
91
+ helper = Helper.new
92
+ completion = []
93
+
94
+ case words[0]
95
+ when NilClass, ''
96
+ completion.concat %w(cloud logout grid app service stack vault certificate node master vpn registry container etcd external-registry whoami plugin version)
97
+ when 'plugin'
98
+ sub_commands = %w(list ls search install uninstall)
99
+ if words[1]
100
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
101
+ else
102
+ completion.push sub_commands
103
+ end
104
+ when 'etcd'
105
+ sub_commands = %w(get set mkdir mk list ls rm)
106
+ if words[1]
107
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
108
+ else
109
+ completion.push sub_commands
110
+ end
111
+ when 'registry'
112
+ sub_commands = %w(create remove rm)
113
+ if words[1]
114
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
115
+ else
116
+ completion.push sub_commands
117
+ end
118
+ when 'grid'
119
+ sub_commands = %w(add-user audit-log create current list user remove show use)
120
+ if words[1]
121
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
122
+ completion.push helper.grids
123
+ else
124
+ completion.push sub_commands
125
+ end
126
+ when 'node'
127
+ sub_commands = %w(list show remove)
128
+ if words[1]
129
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
130
+ completion.push helper.nodes
131
+ else
132
+ completion.push sub_commands
133
+ end
134
+ when 'master'
135
+ sub_commands = %w(list use users current remove rm config cfg login logout token join audit-log init-cloud)
136
+ if words[1] && words[1] == 'use'
137
+ completion.push helper.master_names
138
+ elsif words[1] && words[1] == 'users'
139
+ users_sub_commands = %(invite list role)
140
+ completion.push users_sub_commands
141
+ elsif words[1] && ['config', 'cfg'].include?(words[1])
142
+ config_sub_commands = %(set get dump load import export unset)
143
+ completion.push config_sub_commands
144
+ elsif words[1] && words[1] == 'token'
145
+ token_sub_commands = %(list ls rm remove show current create)
146
+ completion.push token_sub_commands
147
+ elsif words[1]
148
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
149
+ else
150
+ completion.push sub_commands
151
+ end
152
+ when 'cloud'
153
+ sub_commands = %w(login logout master)
154
+ if words[1] && words[1] == 'master'
155
+ cloud_master_sub_commands = %(list ls remove rm add show update)
156
+ completion.push cloud_master_sub_commands
157
+ elsif words[1]
158
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
159
+ else
160
+ completion.push sub_commands
161
+ end
162
+ when 'service'
163
+ sub_commands = %w(containers create delete deploy list logs restart
164
+ scale show start stats stop update monitor env
165
+ secret link unlink)
166
+ if words[1]
167
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
168
+ completion.push helper.services
169
+ else
170
+ completion.push sub_commands
171
+ end
172
+ when 'container'
173
+ sub_commands = %w(exec inspect logs)
174
+ if words[1]
175
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
176
+ completion.push helper.containers
177
+ else
178
+ completion.push sub_commands
179
+ end
180
+ when 'vpn'
181
+ completion.push %w(config create delete)
182
+ when 'external-registry'
183
+ completion.push %w(add list delete)
184
+ when 'app'
185
+ sub_commands = %w(init build config deploy start stop remove rm ps list
186
+ logs monitor show)
187
+ if words[1]
188
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
189
+ completion.push helper.yml_services
190
+ else
191
+ completion.push sub_commands
192
+ end
193
+ when 'stack'
194
+ sub_commands = %w(build install upgrade deploy start stop remove rm ls list
195
+ logs monitor show registry)
196
+ if words[1]
197
+ if words[1] == 'registry'
198
+ registry_sub_commands = %(push pull search show rm)
199
+ completion.push registry_sub_commands
200
+ elsif %w(install).include?(words[1])
201
+ completion.push helper.yml_files
202
+ elsif words[1] == 'upgrade' && words[3]
203
+ completion.push helper.yml_files
204
+ else
205
+ completion.push(sub_commands) unless sub_commands.include?(words[1])
206
+ completion.push helper.stacks
207
+ end
208
+ else
209
+ completion.push sub_commands
210
+ end
211
+ else
212
+ end
213
+ completion.flatten.flat_map { |item| item.split(/\s+/) }
214
+ end
215
+ end
216
+ end