kuberun 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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +4 -0
- data/.gitignore +68 -0
- data/.rspec +3 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +24 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +149 -0
- data/LICENSE.txt +20 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/kuberun +19 -0
- data/kuberun.gemspec +49 -0
- data/lib/kuberun.rb +16 -0
- data/lib/kuberun/cli.rb +50 -0
- data/lib/kuberun/command.rb +125 -0
- data/lib/kuberun/commands/.gitkeep +1 -0
- data/lib/kuberun/commands/run_pod.rb +109 -0
- data/lib/kuberun/kubectl.rb +106 -0
- data/lib/kuberun/templates/.gitkeep +1 -0
- data/lib/kuberun/templates/run_pod/.gitkeep +1 -0
- data/lib/kuberun/version.rb +5 -0
- metadata +393 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'kuberun'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/kuberun
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib_path = File.expand_path('../lib', __dir__)
|
5
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
6
|
+
require 'kuberun/cli'
|
7
|
+
require 'kuberun'
|
8
|
+
|
9
|
+
Signal.trap('INT') do
|
10
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
11
|
+
exit(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
Kuberun::CLI.start
|
16
|
+
rescue Kuberun::CLI::Error => err
|
17
|
+
puts "ERROR: #{err.message}"
|
18
|
+
exit 1
|
19
|
+
end
|
data/kuberun.gemspec
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'kuberun/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'kuberun'
|
9
|
+
spec.license = 'MIT'
|
10
|
+
spec.version = Kuberun::VERSION
|
11
|
+
spec.authors = ['kruczjak']
|
12
|
+
spec.email = ['jakub.kruczek@boostcom.no']
|
13
|
+
|
14
|
+
spec.summary = 'CLI to run pods based on deployments'
|
15
|
+
spec.description = 'CLI to run pods based on deployments'
|
16
|
+
spec.homepage = 'https://boostcom.com'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'pastel', '~> 0.7.2'
|
26
|
+
spec.add_dependency 'thor', '~> 0.20.0'
|
27
|
+
spec.add_dependency 'tty-color', '~> 0.4.2'
|
28
|
+
spec.add_dependency 'tty-command', '~> 0.8.0'
|
29
|
+
spec.add_dependency 'tty-config', '~> 0.2.0'
|
30
|
+
spec.add_dependency 'tty-cursor', '~> 0.5.0'
|
31
|
+
spec.add_dependency 'tty-editor', '~> 0.4.0'
|
32
|
+
spec.add_dependency 'tty-file', '~> 0.6.0'
|
33
|
+
spec.add_dependency 'tty-font', '~> 0.2.0'
|
34
|
+
spec.add_dependency 'tty-markdown', '~> 0.4.0'
|
35
|
+
spec.add_dependency 'tty-pager', '~> 0.11.0'
|
36
|
+
spec.add_dependency 'tty-platform', '~> 0.1.0'
|
37
|
+
spec.add_dependency 'tty-progressbar', '~> 0.15.0'
|
38
|
+
spec.add_dependency 'tty-prompt', '~> 0.16.1'
|
39
|
+
spec.add_dependency 'tty-screen', '~> 0.6.4'
|
40
|
+
spec.add_dependency 'tty-spinner', '~> 0.8.0'
|
41
|
+
spec.add_dependency 'tty-table', '~> 0.10.0'
|
42
|
+
spec.add_dependency 'tty-tree', '~> 0.1.0'
|
43
|
+
spec.add_dependency 'tty-which', '~> 0.3.0'
|
44
|
+
|
45
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
46
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
47
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
48
|
+
spec.add_development_dependency 'rubocop', '~> 0.58.0'
|
49
|
+
end
|
data/lib/kuberun.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kuberun/version'
|
4
|
+
|
5
|
+
require 'pastel'
|
6
|
+
require 'tty-command'
|
7
|
+
require 'tty-prompt'
|
8
|
+
|
9
|
+
require 'kuberun/kubectl'
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
module Kuberun
|
13
|
+
Kubectl = ::Kubectl.new
|
14
|
+
Pastel = ::Pastel.new
|
15
|
+
# Your code goes here...
|
16
|
+
end
|
data/lib/kuberun/cli.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Kuberun
|
6
|
+
# Handle the application command line parsing
|
7
|
+
# and the dispatch to various command objects
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class CLI < Thor
|
11
|
+
DEFAULT_OPTIONS_FOR_KUBECTL_OPTIONS = { type: :string, default: '', desc: 'See kubectl options' }
|
12
|
+
BASE_KUBECTL_OPTIONS = {
|
13
|
+
'certificate-authority': {},
|
14
|
+
'client-certificate': {},
|
15
|
+
'client-key': {},
|
16
|
+
'cluster': {},
|
17
|
+
'context': {},
|
18
|
+
'insecure-skip-tls-verify': {},
|
19
|
+
'kubeconfig': {},
|
20
|
+
'namespace': { aliases: :'-n' },
|
21
|
+
'token': {},
|
22
|
+
'v': { type: :numeric, default: 0, desc: 'Log level, also passed to kubectl' },
|
23
|
+
}
|
24
|
+
BASE_KUBECTL_OPTIONS.each do |option_name, hash|
|
25
|
+
class_option option_name, DEFAULT_OPTIONS_FOR_KUBECTL_OPTIONS.merge(hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Error raised by this runner
|
29
|
+
Error = Class.new(StandardError)
|
30
|
+
|
31
|
+
desc 'version', 'kuberun version'
|
32
|
+
def version
|
33
|
+
require_relative 'version'
|
34
|
+
puts "v#{Kuberun::VERSION}"
|
35
|
+
end
|
36
|
+
map %w[--version -v] => :version
|
37
|
+
|
38
|
+
desc 'run_pod DEPLOYMENT_NAME', 'Starts pod for command'
|
39
|
+
method_option :help, aliases: '-h', type: :boolean,
|
40
|
+
desc: 'Display usage information'
|
41
|
+
def run_pod(deployment_name)
|
42
|
+
if options[:help]
|
43
|
+
invoke :help, ['run_pod']
|
44
|
+
else
|
45
|
+
require_relative 'commands/run_pod'
|
46
|
+
Kuberun::Commands::RunPod.new(deployment_name, options).execute
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Kuberun
|
6
|
+
class Command
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :command, :run
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
Kuberun::Kubectl.load_options(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Execute this command
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def execute(*)
|
19
|
+
raise(
|
20
|
+
NotImplementedError,
|
21
|
+
"#{self.class}##{__method__} must be implemented"
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# The external commands runner
|
26
|
+
#
|
27
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def command(**options)
|
31
|
+
require 'tty-command'
|
32
|
+
TTY::Command.new(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# The cursor movement
|
36
|
+
#
|
37
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def cursor
|
41
|
+
require 'tty-cursor'
|
42
|
+
TTY::Cursor
|
43
|
+
end
|
44
|
+
|
45
|
+
# Open a file or text in the user's preferred editor
|
46
|
+
#
|
47
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def editor
|
51
|
+
require 'tty-editor'
|
52
|
+
TTY::Editor
|
53
|
+
end
|
54
|
+
|
55
|
+
# File manipulation utility methods
|
56
|
+
#
|
57
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def generator
|
61
|
+
require 'tty-file'
|
62
|
+
TTY::File
|
63
|
+
end
|
64
|
+
|
65
|
+
# Terminal output paging
|
66
|
+
#
|
67
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def pager(**options)
|
71
|
+
require 'tty-pager'
|
72
|
+
TTY::Pager.new(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Terminal platform and OS properties
|
76
|
+
#
|
77
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def platform
|
81
|
+
require 'tty-platform'
|
82
|
+
TTY::Platform.new
|
83
|
+
end
|
84
|
+
|
85
|
+
# The interactive prompt
|
86
|
+
#
|
87
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def prompt(**options)
|
91
|
+
require 'tty-prompt'
|
92
|
+
TTY::Prompt.new(options)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Get terminal screen properties
|
96
|
+
#
|
97
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def screen
|
101
|
+
require 'tty-screen'
|
102
|
+
TTY::Screen
|
103
|
+
end
|
104
|
+
|
105
|
+
# The unix which utility
|
106
|
+
#
|
107
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def which(*args)
|
111
|
+
require 'tty-which'
|
112
|
+
TTY::Which.which(*args)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check if executable exists
|
116
|
+
#
|
117
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
118
|
+
#
|
119
|
+
# @api public
|
120
|
+
def exec_exist?(*args)
|
121
|
+
require 'tty-which'
|
122
|
+
TTY::Which.exist?(*args)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
|
5
|
+
module Kuberun
|
6
|
+
module Commands
|
7
|
+
class RunPod < Kuberun::Command
|
8
|
+
NEW_POD = 'Start new one'
|
9
|
+
|
10
|
+
def initialize(deployment_name, options)
|
11
|
+
@deployment_name = deployment_name
|
12
|
+
@options = options
|
13
|
+
super(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
output.puts(Kuberun::Pastel.yellow('Checking access to needed commands...'))
|
18
|
+
Kuberun::Kubectl.auth_check('create', resource: 'pods')
|
19
|
+
Kuberun::Kubectl.auth_check('exec', resource: 'pods')
|
20
|
+
output.puts(Kuberun::Pastel.green('You have all permissions needed.'))
|
21
|
+
|
22
|
+
output.puts(Kuberun::Pastel.yellow('Searching for existing pods'))
|
23
|
+
existing_pods = Kuberun::Kubectl.get(resource: 'pods', options: "-l kuberun-provisioned=true,kuberun-source=#{@deployment_name}")
|
24
|
+
if existing_pods['items'].size > 0
|
25
|
+
select = existing_pods['items'].map { |item| item.dig('metadata', 'name') }
|
26
|
+
select << NEW_POD
|
27
|
+
|
28
|
+
selection = prompt.select('I found some already running pods. Do you want to use one?', select)
|
29
|
+
|
30
|
+
if selection == NEW_POD
|
31
|
+
create_pod_from_deployment(output)
|
32
|
+
else
|
33
|
+
@generated_pod_name = selection
|
34
|
+
end
|
35
|
+
else
|
36
|
+
create_pod_from_deployment(output)
|
37
|
+
end
|
38
|
+
|
39
|
+
execute_command(input, output)
|
40
|
+
|
41
|
+
unless prompt.no?('Should I delete pod?')
|
42
|
+
Kuberun::Kubectl.delete(resource: 'pod', resource_name: generated_pod_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
output.puts(Kuberun::Pastel.green('Done!'))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def create_pod_from_deployment(output)
|
51
|
+
deployment = Kuberun::Kubectl.get(resource: 'deployment', resource_name: @deployment_name, options: '--export')
|
52
|
+
pod_template = deployment['spec']['template']
|
53
|
+
prepare_pod_template(pod_template)
|
54
|
+
|
55
|
+
Kuberun::Kubectl.create(configuration: pod_template)
|
56
|
+
wait_while do
|
57
|
+
pod = Kuberun::Kubectl.get(resource: 'pod', resource_name: generated_pod_name)
|
58
|
+
pod.dig('status', 'phase') == 'Running'
|
59
|
+
end
|
60
|
+
|
61
|
+
output.puts(Kuberun::Pastel.green('Pod is running!'))
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare_pod_template(pod_template)
|
65
|
+
pod_template['apiVersion'] = 'v1'
|
66
|
+
pod_template['kind'] = 'Pod'
|
67
|
+
pod_template['metadata']['name'] = generated_pod_name
|
68
|
+
|
69
|
+
pod_template['metadata']['labels'] = {
|
70
|
+
'kuberun' => Kuberun::VERSION.to_s,
|
71
|
+
'kuberun-provisioned' => 'true',
|
72
|
+
'kuberun-source' => @deployment_name,
|
73
|
+
}
|
74
|
+
|
75
|
+
pod_template['spec']['containers'][0].delete('livenessProbe')
|
76
|
+
pod_template['spec']['containers'][0].delete('readinessProbe')
|
77
|
+
pod_template['spec'].delete('affinity')
|
78
|
+
end
|
79
|
+
|
80
|
+
def wait_while
|
81
|
+
loop do
|
82
|
+
begin
|
83
|
+
status = yield
|
84
|
+
raise 'Not ok' unless status
|
85
|
+
break
|
86
|
+
rescue
|
87
|
+
sleep(1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def generated_pod_name
|
93
|
+
@generated_pod_name ||= "kuberun-#{@deployment_name}-#{Time.now.to_i}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_command(_input, output)
|
97
|
+
output.puts(Kuberun::Pastel.green('Executing command'))
|
98
|
+
|
99
|
+
Kuberun::Kubectl.exec(pod: generated_pod_name, command: '-it /bin/sh')
|
100
|
+
|
101
|
+
output.puts(Kuberun::Pastel.green('Kubectl exec exited'))
|
102
|
+
end
|
103
|
+
|
104
|
+
def prompt
|
105
|
+
TTY::Prompt.new
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'pty'
|
5
|
+
|
6
|
+
class Kubectl
|
7
|
+
CAT_MULTILINE = 'EOFCONFIG'
|
8
|
+
KUBECTL_OPTIONS = Kuberun::CLI::BASE_KUBECTL_OPTIONS.keys.map(&:to_s)
|
9
|
+
|
10
|
+
def load_options(options)
|
11
|
+
self.kubectl_options = parsed_options(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def auth_check(verb, resource:, resource_name: nil)
|
15
|
+
cmd.run(kubectl_base_command("auth can-i #{verb}", resource: resource, resource_name: resource_name))
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(resource:, resource_name: nil, options: nil)
|
19
|
+
options = "#{options} --output=json"
|
20
|
+
parsed_json do
|
21
|
+
cmd.run(kubectl_base_command('get', resource: resource, resource_name: resource_name, options: options)).out
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(configuration:, options: nil)
|
26
|
+
cmd.run(kubectl_base_input_command('create', configuration: configuration, options: options))
|
27
|
+
end
|
28
|
+
|
29
|
+
def exec(pod:, command:)
|
30
|
+
old_state = `stty -g`
|
31
|
+
|
32
|
+
PTY.spawn("#{kubectl_base_command('exec', resource: pod)} #{command}") do |o, i, pid|
|
33
|
+
t_in = Thread.new do
|
34
|
+
until i.closed? do
|
35
|
+
input = $stdin.getch
|
36
|
+
i.write(input)
|
37
|
+
i.flush
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
t_out = Thread.new do
|
42
|
+
begin
|
43
|
+
until o.eof? do
|
44
|
+
$stdout.print(o.readchar)
|
45
|
+
$stdout.flush
|
46
|
+
end
|
47
|
+
rescue Errno::EIO, EOFError
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
t_in.run
|
53
|
+
|
54
|
+
Process::waitpid(pid) rescue nil
|
55
|
+
# "rescue nil" is there in case process already ended.
|
56
|
+
|
57
|
+
t_out.join
|
58
|
+
t_in.kill
|
59
|
+
sleep 0.1
|
60
|
+
ensure
|
61
|
+
t_out&.kill
|
62
|
+
t_in&.kill
|
63
|
+
$stdout.puts
|
64
|
+
$stdout.flush
|
65
|
+
system "stty #{ old_state }"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete(resource:, resource_name:)
|
70
|
+
cmd.run(kubectl_base_command('delete', resource: resource, resource_name: resource_name))
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
attr_accessor :kubectl_options
|
76
|
+
|
77
|
+
def cmd(tty_options = {})
|
78
|
+
TTY::Command.new(tty_options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def kubectl_base_command(verb, resource:, resource_name: nil, options: nil)
|
82
|
+
base = "#{kubectl} #{options} #{verb} #{resource}"
|
83
|
+
base = "#{base}/#{resource_name}" if resource_name
|
84
|
+
base
|
85
|
+
end
|
86
|
+
|
87
|
+
def kubectl_base_input_command(verb, configuration:, options:)
|
88
|
+
"cat << '#{CAT_MULTILINE}' | #{kubectl} #{options} #{verb} -f - 2>&1\n#{configuration.to_json}\n#{CAT_MULTILINE}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def kubectl
|
92
|
+
"kubectl #{kubectl_options}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def parsed_options(options)
|
96
|
+
options.each_with_object([]) do |(option_name, option_value), arr|
|
97
|
+
next if !KUBECTL_OPTIONS.include?(option_name) || option_value == ''
|
98
|
+
|
99
|
+
arr << "--#{option_name}=#{option_value}"
|
100
|
+
end.join(' ')
|
101
|
+
end
|
102
|
+
|
103
|
+
def parsed_json
|
104
|
+
JSON.load(yield)
|
105
|
+
end
|
106
|
+
end
|