-
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
Fix for platfrom detection and refactoring #20
base: main
Are you sure you want to change the base?
Changes from all commits
46f6593
5fbf605
acbee5c
c4847e9
b3d5b64
a0e37b0
a62e7dd
5d26715
6791413
ea850fc
a82433b
de2315b
5c7b077
d2d0914
c408d6b
cd6cd20
ae20888
559956c
8635230
4cb1485
3ba1f40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,16 @@ | ||
# frozen_string_literal: true | ||
libdir = File.dirname(__FILE__) | ||
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) | ||
|
||
require_relative "train/k8s/container/version" | ||
require_relative "train/k8s/container/platform" | ||
require_relative "train/k8s/container/connection" | ||
require_relative "train/k8s/container/transport" | ||
require_relative "train/k8s/container/kubectl_exec_client" | ||
require_relative "train-k8s-container/version" | ||
require_relative "train-k8s-container/connection" | ||
require_relative "train-k8s-container/transport" | ||
require_relative "train-k8s-container/kubectl_exec_client" | ||
|
||
module Train | ||
module K8s | ||
module Container | ||
class ConnectionError < StandardError | ||
end | ||
# Your code goes here... | ||
end | ||
end | ||
end | ||
# module TrainPlugins | ||
# module K8sContainer | ||
# class ConnectionError < StandardError | ||
# # Your code goes here... | ||
# end | ||
# end | ||
# end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# frozen_string_literal: true | ||
require "train" | ||
require "train/plugins" | ||
require "train/file/remote/linux" | ||
module TrainPlugins | ||
module K8sContainer | ||
class Connection < Train::Plugins::Transport::BaseConnection | ||
include Train::Platforms::Common | ||
|
||
# URI format: k8s-container://<namespace>/<pod>/<container_name> | ||
# @example k8s-container://default/shell-demo/nginx | ||
|
||
def initialize(options) | ||
super(options) | ||
|
||
if RUBY_PLATFORM =~ /windows|mswin|msys|mingw|cygwin/ | ||
raise "Unsupported host platform." | ||
end | ||
|
||
uri_path = options[:path]&.gsub(%r{^/}, "") | ||
@pod = options[:pod] || uri_path&.split("/")&.first | ||
@container_name = options[:container_name] || uri_path&.split("/")&.last | ||
host = (!options[:host].nil? && !options[:host].empty?) ? options[:host] : nil | ||
@namespace = options[:namespace] || host || TrainPlugins::K8sContainer::KubectlExecClient::DEFAULT_NAMESPACE | ||
validate_parameters | ||
end | ||
|
||
def uri | ||
"k8s-container://#{@namespace}/#{@pod}/#{@container_name}" | ||
end | ||
|
||
def platform | ||
@platform ||= Train::Platforms::Detect.scan(self) | ||
end | ||
|
||
private | ||
|
||
attr_reader :pod, :container_name, :namespace | ||
|
||
def run_command_via_connection(cmd, &_data_handler) | ||
KubectlExecClient.new(pod: pod, namespace: namespace, container_name: container_name).execute(cmd) | ||
end | ||
|
||
def validate_parameters | ||
raise ArgumentError, "Missing Parameter `pod`" unless pod | ||
raise ArgumentError, "Missing Parameter `container_name`" unless container_name | ||
end | ||
|
||
def file_via_connection(path, *_args) | ||
::Train::File::Remote::Linux.new(self, path) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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, :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 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 | ||
|
||
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 | ||
|
||
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 | ||
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 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# frozen_string_literal: true | ||
require "train" | ||
require "train/plugins" | ||
|
||
module TrainPlugins | ||
module K8sContainer | ||
class Transport < Train.plugin(1) | ||
require_relative "connection" | ||
|
||
name "k8s-container" | ||
|
||
option :kubeconfig, default: ENV["KUBECONFIG"] || "~/.kube/config" | ||
option :pod, default: nil | ||
option :container_name, default: nil | ||
option :namespace, default: nil | ||
|
||
def connection(state = nil, &block) | ||
opts = merge_options(@options, state || {}) | ||
create_new_connection(opts, &block) | ||
end | ||
|
||
def create_new_connection(options, &block) | ||
@connection_options = options | ||
@connection = Connection.new(options, &block) | ||
end | ||
end | ||
end | ||
end |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# frozen_string_literal: true | ||
require_relative "spec_helper" | ||
|
||
RSpec.describe TrainPlugins::K8sContainer do | ||
it "has a version number" do | ||
expect(TrainPlugins::K8sContainer::VERSION).not_to be nil | ||
end | ||
end |
Check warning
Code scanning / CodeQL
Unsafe shell command constructed from library input Medium