-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chef 11169 optimize k8s container connection v2 #22
Open
sathish-progress
wants to merge
17
commits into
main
Choose a base branch
from
CHEF-11169-optimize-k8s-container-connection-v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c4847e9
modify exec command to open shell
sathish-progress b3d5b64
setup session and stream pipe for command streaming to kubectl exec s…
sathish-progress a0e37b0
call streaming pipe from execute
sathish-progress a62e7dd
set session at class level
sathish-progress 5d26715
build shell command
sathish-progress 6791413
build instruction suitable for k8s exec client
sathish-progress ea850fc
require open3, pty and expect
sathish-progress a82433b
disconnect logic
sathish-progress de2315b
strip the ansi sequence
sathish-progress 5c7b077
execute and close logic
sathish-progress d2d0914
send command
sathish-progress c408d6b
Train extras
sathish-progress cd6cd20
set session from PTY
sathish-progress ae20888
write cmd string
sathish-progress 559956c
catch standardError
sathish-progress 8635230
fix connections for session
sathish-progress 4cb1485
catch syntax error
sathish-progress File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,121 @@ | ||
require "mixlib/shellout" unless defined?(Mixlib::ShellOut) | ||
require "train/options" # only to load the following requirement `train/extras` | ||
require "train/extras" | ||
require "open3" | ||
require "pty" | ||
require "expect" | ||
|
||
module Train | ||
module K8s | ||
module Container | ||
class KubectlExecClient | ||
attr_reader :pod, :container_name, :namespace | ||
attr_reader :pod, :container_name, :namespace, :reader, :writer, :pid | ||
|
||
DEFAULT_NAMESPACE = "default".freeze | ||
@@session = {} | ||
|
||
def initialize(pod:, namespace: nil, container_name: nil) | ||
@pod = pod | ||
@container_name = container_name | ||
@namespace = namespace | ||
|
||
@reader = @@session[:reader] || nil | ||
@writer = @@session[:writer] || nil | ||
@pid = @@session[:pid] || nil | ||
connect if @@session.empty? | ||
end | ||
|
||
def execute(command) | ||
instruction = build_instruction(command) | ||
shell = Mixlib::ShellOut.new(instruction) | ||
res = shell.run_command | ||
Train::Extras::CommandResult.new(res.stdout, res.stderr, res.exitstatus) | ||
rescue Errno::ENOENT => _e | ||
def connect | ||
@reader, @writer, @pid = PTY.spawn("kubectl exec --stdin --tty #{@pod} -n #{@namespace} -c #{@container_name} -- /bin/bash") | ||
@writer.sync = true | ||
@@session[:reader] = @reader | ||
@@session[:writer] = @writer | ||
@@session[:pid] = @pid | ||
rescue StandardError => e | ||
puts "Error connecting: #{e.message}" | ||
sleep 1 | ||
retry | ||
end | ||
|
||
def reconnect | ||
disconnect | ||
connect | ||
end | ||
|
||
def disconnect | ||
@writer.puts "exit" if @writer | ||
[@reader, @writer].each do |io| | ||
io.close if io && !io.closed? | ||
end | ||
@@session = {} | ||
rescue IOError | ||
Train::Extras::CommandResult.new("", "", 1) | ||
end | ||
|
||
private | ||
def strip_ansi_sequences(text) | ||
text.gsub(/\e\[.*?m/, "").gsub(/\e\]0;.*?\a/, "").gsub(/\e\[A/, "").gsub(/\e\[C/, "").gsub(/\e\[K/, "") | ||
Check failure Code scanning / CodeQL Polynomial regular expression used on uncontrolled data High
This
regular expression Error loading related location Loading library input Error loading related location Loading Check failure Code scanning / CodeQL Polynomial regular expression used on uncontrolled data High
This
regular expression Error loading related location Loading library input Error loading related location Loading |
||
end | ||
|
||
def send_command(command) | ||
cmd_string = "#{command} 2>&1 ; echo EXIT_CODE=$?" | ||
@writer.puts(cmd_string) | ||
@writer.flush | ||
|
||
def build_instruction(command) | ||
["kubectl exec"].tap do |arr| | ||
arr << "--stdin" | ||
arr << pod if pod | ||
if namespace | ||
arr << "-n" | ||
arr << namespace | ||
end | ||
if container_name | ||
arr << "-c" | ||
arr << container_name | ||
stdout = "" | ||
stderr = "" | ||
status = nil | ||
buffer = "" | ||
|
||
begin | ||
while (line = @reader.gets) | ||
buffer << line | ||
if line =~ /EXIT_CODE=(\d+)/ | ||
status = $1.to_i | ||
break | ||
elsif line =~ /bash: syntax error/ | ||
status = 2 | ||
break | ||
end | ||
end | ||
arr << "--" | ||
arr << sh_run_command(command) | ||
end.join("\s") | ||
rescue Errno::EIO => e | ||
raise StandardError, e.message | ||
end | ||
|
||
# Clean up the buffer by removing ANSI escape sequences | ||
buffer = strip_ansi_sequences(buffer) | ||
# Process the buffer to remove the command echo and the EXIT_CODE | ||
stdout_lines = buffer.lines | ||
# TODO: there is a known bug with this approach and that is if an executable that is not found in the | ||
# environment is tried and executed, then it will remove not be present in the STDERR, because the following | ||
# line filters that exact command as well for example, | ||
# for the command 'foo' | ||
# `["bash: foo: command not found\r\n"].reject! { |l| l =~ /#{Regexp.escape('foo')}/ }` returns an empty [] | ||
stdout_lines.reject! { |l| l =~ /#{Regexp.escape(command)}/ } | ||
stdout_lines.reject! { |l| l =~ /EXIT_CODE=/ } | ||
|
||
# Separate stdout and stderr | ||
if status != 0 | ||
stderr = stdout_lines.join.strip | ||
stdout = "" | ||
else | ||
stdout = stdout_lines.join.strip | ||
end | ||
|
||
Train::Extras::CommandResult.new(stdout, stderr, status) | ||
end | ||
|
||
def sh_run_command(command) | ||
%W{/bin/sh -c "#{command}"} | ||
def execute(command) | ||
send_command(command) | ||
rescue StandardError => e | ||
reconnect | ||
Train::Extras::CommandResult.new("", e.message, 1) | ||
end | ||
|
||
def close | ||
disconnect | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / CodeQL
Unsafe shell command constructed from library input Medium