cloud-sh 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/gempush.yml +32 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +126 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/cloud-sh.gemspec +37 -0
- data/exe/cloud-sh +7 -0
- data/exe/kubetail +357 -0
- data/lib/cloud/sh/cli.rb +73 -0
- data/lib/cloud/sh/commands/base.rb +33 -0
- data/lib/cloud/sh/commands/k8s_exec.rb +88 -0
- data/lib/cloud/sh/commands/k8s_tail.rb +41 -0
- data/lib/cloud/sh/commands/refresh.rb +144 -0
- data/lib/cloud/sh/config.rb +106 -0
- data/lib/cloud/sh/helpers/commands.rb +55 -0
- data/lib/cloud/sh/providers/base.rb +42 -0
- data/lib/cloud/sh/providers/digital_ocean.rb +100 -0
- data/lib/cloud/sh/version.rb +7 -0
- data/lib/cloud/sh.rb +51 -0
- metadata +128 -0
data/exe/kubetail
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# copied from https://raw.githubusercontent.com/johanhaleby/kubetail/master/kubetail (SHA: b13267a6d25b364eeb636060f426c7eba4e28a39)
|
4
|
+
|
5
|
+
KUBECTL_BIN=${KUBECTL_BIN:-"kubectl"}
|
6
|
+
|
7
|
+
readonly PROGNAME=$(basename $0)
|
8
|
+
|
9
|
+
calculate_default_namespace() {
|
10
|
+
local config_namespace=$(${KUBECTL_BIN} config view --minify --output 'jsonpath={..namespace}')
|
11
|
+
echo "${KUBETAIL_NAMESPACE:-${config_namespace:-default}}"
|
12
|
+
}
|
13
|
+
|
14
|
+
default_previous="${KUBETAIL_PREVIOUS:-false}"
|
15
|
+
default_since="${KUBETAIL_SINCE:-10s}"
|
16
|
+
default_namespace=$(calculate_default_namespace)
|
17
|
+
default_follow="${KUBETAIL_FOLLOW:-true}"
|
18
|
+
default_line_buffered="${KUBETAIL_LINE_BUFFERED:-}"
|
19
|
+
default_colored_output="${KUBETAIL_COLORED_OUTPUT:-line}"
|
20
|
+
default_timestamps="${KUBETAIL_TIMESTAMPS:-}"
|
21
|
+
default_jq_selector="${KUBETAIL_JQ_SELECTOR:-}"
|
22
|
+
default_skip_colors="${KUBETAIL_SKIP_COLORS:-7,8}"
|
23
|
+
default_tail="${KUBETAIL_TAIL:--1}"
|
24
|
+
default_show_color_index="${KUBETAIL_SHOW_COLOR_INDEX:-false}"
|
25
|
+
|
26
|
+
namespace="${default_namespace}"
|
27
|
+
follow="${default_follow}"
|
28
|
+
line_buffered="${default_line_buffered}"
|
29
|
+
colored_output="${default_colored_output}"
|
30
|
+
timestamps="${default_timestamps}"
|
31
|
+
jq_selector="${default_jq_selector}"
|
32
|
+
skip_colors="${default_skip_colors}"
|
33
|
+
tail="${default_tail}"
|
34
|
+
show_color_index="${default_show_color_index}"
|
35
|
+
|
36
|
+
if [[ ${1} != -* ]]
|
37
|
+
then
|
38
|
+
pod="${1}"
|
39
|
+
fi
|
40
|
+
containers=()
|
41
|
+
selector=()
|
42
|
+
regex='substring'
|
43
|
+
previous="${default_previous}"
|
44
|
+
since="${default_since}"
|
45
|
+
version="1.6.11-SNAPSHOT"
|
46
|
+
dryrun=false
|
47
|
+
cluster=""
|
48
|
+
namespace_arg="-n ${default_namespace}"
|
49
|
+
|
50
|
+
usage="${PROGNAME} <search term> [-h] [-c] [-n] [-t] [-l] [-d] [-p] [-s] [-b] [-k] [-v] [-r] [-i] -- tail multiple Kubernetes pod logs at the same time
|
51
|
+
|
52
|
+
where:
|
53
|
+
-h, --help Show this help text
|
54
|
+
-c, --container The name of the container to tail in the pod (if multiple containers are defined in the pod).
|
55
|
+
Defaults to all containers in the pod. Can be used multiple times.
|
56
|
+
-t, --context The k8s context. ex. int1-context. Relies on ~/.kube/config for the contexts.
|
57
|
+
-l, --selector Label selector. If used the pod name is ignored.
|
58
|
+
-n, --namespace The Kubernetes namespace where the pods are located (defaults to \"${default_namespace}\")
|
59
|
+
-f, --follow Specify if the logs should be streamed. (true|false) Defaults to ${default_follow}.
|
60
|
+
-d, --dry-run Print the names of the matched pods and containers, then exit.
|
61
|
+
-p, --previous Return logs for the previous instances of the pods, if available. (true|false) Defaults to ${default_previous}.
|
62
|
+
-s, --since Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to ${default_since}.
|
63
|
+
-b, --line-buffered This flags indicates to use line-buffered. Defaults to false.
|
64
|
+
-e, --regex The type of name matching to use (regex|substring)
|
65
|
+
-j, --jq If your output is json - use this jq-selector to parse it.
|
66
|
+
example: --jq \".logger + \\\" \\\" + .message\"
|
67
|
+
-k, --colored-output Use colored output (pod|line|false).
|
68
|
+
pod = only color pod name, line = color entire line, false = don't use any colors.
|
69
|
+
Defaults to ${default_colored_output}.
|
70
|
+
-z, --skip-colors Comma-separated list of colors to not use in output
|
71
|
+
If you have green foreground on black, this will skip dark grey and some greens -z 2,8,10
|
72
|
+
Defaults to: ${default_skip_colors}
|
73
|
+
--timestamps Show timestamps for each log line
|
74
|
+
--tail Lines of recent log file to display. Defaults to ${default_tail}, showing all log lines.
|
75
|
+
-v, --version Prints the kubetail version
|
76
|
+
-r, --cluster The name of the kubeconfig cluster to use.
|
77
|
+
-i, --show-color-index Show the color index before the pod name prefix that is shown before each log line.
|
78
|
+
Normally only the pod name is added as a prefix before each line, for example \"[app-5b7ff6cbcd-bjv8n]\",
|
79
|
+
but if \"show-color-index\" is true then color index is added as well: \"[1:app-5b7ff6cbcd-bjv8n]\".
|
80
|
+
This is useful if you have color blindness or if you want to know which colors to exclude (see \"--skip-colors\").
|
81
|
+
Defaults to ${default_show_color_index}.
|
82
|
+
|
83
|
+
examples:
|
84
|
+
${PROGNAME} my-pod-v1
|
85
|
+
${PROGNAME} my-pod-v1 -c my-container
|
86
|
+
${PROGNAME} my-pod-v1 -t int1-context -c my-container
|
87
|
+
${PROGNAME} '(service|consumer|thing)' -e regex
|
88
|
+
${PROGNAME} -l service=my-service
|
89
|
+
${PROGNAME} --selector service=my-service --since 10m
|
90
|
+
${PROGNAME} --tail 1"
|
91
|
+
|
92
|
+
if [ "$#" -ne 0 ]; then
|
93
|
+
while [ "$#" -gt 0 ]
|
94
|
+
do
|
95
|
+
case "$1" in
|
96
|
+
-h|--help)
|
97
|
+
echo "$usage"
|
98
|
+
exit 0
|
99
|
+
;;
|
100
|
+
-v|--version)
|
101
|
+
echo "$version"
|
102
|
+
exit 0
|
103
|
+
;;
|
104
|
+
-c|--container)
|
105
|
+
containers+=("$2")
|
106
|
+
;;
|
107
|
+
-e|--regex)
|
108
|
+
regex="regex"
|
109
|
+
;;
|
110
|
+
-t|--context)
|
111
|
+
context="$2"
|
112
|
+
;;
|
113
|
+
-r|--cluster)
|
114
|
+
cluster="--cluster $2"
|
115
|
+
;;
|
116
|
+
-l|--selector)
|
117
|
+
selector=(--selector "$2")
|
118
|
+
pod=""
|
119
|
+
;;
|
120
|
+
-d|--dry-run)
|
121
|
+
dryrun=true
|
122
|
+
;;
|
123
|
+
-p|--previous)
|
124
|
+
previous=true
|
125
|
+
;;
|
126
|
+
-s|--since)
|
127
|
+
if [ -z "$2" ]; then
|
128
|
+
since="${default_since}"
|
129
|
+
else
|
130
|
+
since="$2"
|
131
|
+
fi
|
132
|
+
;;
|
133
|
+
-n|--namespace)
|
134
|
+
if [ -z "$2" ]; then
|
135
|
+
# using namespace from context
|
136
|
+
:
|
137
|
+
else
|
138
|
+
namespace_arg="--namespace $2"
|
139
|
+
fi
|
140
|
+
;;
|
141
|
+
-f|--follow)
|
142
|
+
if [ "$2" = "false" ]; then
|
143
|
+
follow="false"
|
144
|
+
fi
|
145
|
+
;;
|
146
|
+
-b|--line-buffered)
|
147
|
+
if [ "$2" = "true" ]; then
|
148
|
+
line_buffered="| grep - --line-buffered"
|
149
|
+
fi
|
150
|
+
;;
|
151
|
+
-k|--colored-output)
|
152
|
+
if [ -z "$2" ]; then
|
153
|
+
colored_output="${default_colored_output}"
|
154
|
+
else
|
155
|
+
colored_output="$2"
|
156
|
+
fi
|
157
|
+
;;
|
158
|
+
-j|--jq)
|
159
|
+
if [ -z "$2" ]; then
|
160
|
+
jq_selector="${default_jq_selector}"
|
161
|
+
else
|
162
|
+
jq_selector="$2"
|
163
|
+
fi
|
164
|
+
;;
|
165
|
+
-z|--skip-colors)
|
166
|
+
if [ -z "$2" ]; then
|
167
|
+
skip_colors="${default_skip_colors}"
|
168
|
+
else
|
169
|
+
skip_colors="$2"
|
170
|
+
fi
|
171
|
+
;;
|
172
|
+
--timestamps)
|
173
|
+
if [ "$2" = "false" ]; then
|
174
|
+
timestamps="$1=$2"
|
175
|
+
else
|
176
|
+
timestamps="$1"
|
177
|
+
fi
|
178
|
+
;;
|
179
|
+
--tail)
|
180
|
+
if [ -z "$2" ]; then
|
181
|
+
tail="${default_tail}"
|
182
|
+
else
|
183
|
+
tail="$2"
|
184
|
+
fi
|
185
|
+
;;
|
186
|
+
-i|--show-color-index)
|
187
|
+
if [ -z "$2" ]; then
|
188
|
+
show_color_index="${default_show_color_index}"
|
189
|
+
else
|
190
|
+
show_color_index="$2"
|
191
|
+
fi
|
192
|
+
;;
|
193
|
+
--)
|
194
|
+
break
|
195
|
+
;;
|
196
|
+
-*)
|
197
|
+
echo "Invalid option '$1'. Use --help to see the valid options" >&2
|
198
|
+
exit 1
|
199
|
+
;;
|
200
|
+
# an option argument, continue
|
201
|
+
*) ;;
|
202
|
+
esac
|
203
|
+
shift
|
204
|
+
done
|
205
|
+
else
|
206
|
+
echo "$usage"
|
207
|
+
exit 1
|
208
|
+
fi
|
209
|
+
|
210
|
+
# Join function that supports a multi-character separator (copied from http://stackoverflow.com/a/23673883/398441)
|
211
|
+
function join() {
|
212
|
+
# $1 is return variable name
|
213
|
+
# $2 is sep
|
214
|
+
# $3... are the elements to join
|
215
|
+
local retname=$1 sep=$2 ret=$3
|
216
|
+
shift 3 || shift $(($#))
|
217
|
+
printf -v "$retname" "%s" "$ret${@/#/$sep}"
|
218
|
+
}
|
219
|
+
|
220
|
+
# Check if pod query contains a comma and we've not specified "regex" explicitly,
|
221
|
+
# if so we convert the pod query string into a regex that matches all pods seperated by the comma
|
222
|
+
if [[ "${pod}" = *","* ]] && [ ! "${regex}" == 'regex' ]; then
|
223
|
+
|
224
|
+
# Split the supplied query string (in variable pod) by comma into an array named "pods_to_match"
|
225
|
+
IFS=',' read -r -a pods_to_match <<< "${pod}"
|
226
|
+
|
227
|
+
# Join all pod names into a string with ".*|.*" as delimiter
|
228
|
+
join pod ".*|.*" "${pods_to_match[@]}"
|
229
|
+
|
230
|
+
# Prepend and initial ".*" and and append the last ".*"
|
231
|
+
pod=".*${pod}.*"
|
232
|
+
|
233
|
+
# Force the use of regex matching
|
234
|
+
regex='regex'
|
235
|
+
fi
|
236
|
+
|
237
|
+
grep_matcher=''
|
238
|
+
if [ "${regex}" == 'regex' ]; then
|
239
|
+
echo "Using regex '${pod}' to match pods"
|
240
|
+
grep_matcher='-E'
|
241
|
+
fi
|
242
|
+
|
243
|
+
# Get all pods matching the input and put them in an array. If no input then all pods are matched.
|
244
|
+
matching_pods=(`${KUBECTL_BIN} get pods ${context:+--context=${context}} "${selector[@]}" ${namespace_arg} ${cluster} --output=jsonpath='{.items[*].metadata.name}' | xargs -n1 | grep --color=never $grep_matcher "${pod}"`)
|
245
|
+
matching_pods_size=${#matching_pods[@]}
|
246
|
+
|
247
|
+
if [ ${matching_pods_size} -eq 0 ]; then
|
248
|
+
echo "No pod exists that matches ${pod}"
|
249
|
+
exit 1
|
250
|
+
fi
|
251
|
+
|
252
|
+
color_end=$(tput sgr0)
|
253
|
+
|
254
|
+
# Wrap all pod names in the "kubectl logs <name> -f=true/false" command
|
255
|
+
display_names_preview=()
|
256
|
+
pod_logs_commands=()
|
257
|
+
i=0
|
258
|
+
color_index=0
|
259
|
+
|
260
|
+
function next_col {
|
261
|
+
potential_col=$(($1+1))
|
262
|
+
[[ $skip_colors =~ (^|,)$potential_col($|,) ]] && echo `next_col $potential_col` || echo $potential_col
|
263
|
+
}
|
264
|
+
|
265
|
+
# Allows for more colors, this is useful if one tails a lot pods
|
266
|
+
if [ ${colored_output} != "false" ]; then
|
267
|
+
export TERM=xterm-256color
|
268
|
+
fi
|
269
|
+
|
270
|
+
# Function that kills all kubectl processes that are started by kubetail in the background
|
271
|
+
function kill_kubectl_processes {
|
272
|
+
kill 0
|
273
|
+
}
|
274
|
+
|
275
|
+
# Invoke the "kill_kubectl_processes" function when the script is stopped (including ctrl+c)
|
276
|
+
# Note that "INT" is not used because if, for example, kubectl cannot find a container
|
277
|
+
# (for example when running "kubetail something -c non_matching")
|
278
|
+
trap kill_kubectl_processes EXIT
|
279
|
+
|
280
|
+
# Putting all needed values in a variable so that multiple requests to Kubernetes api can be avoided, thus making it faster
|
281
|
+
all_pods_containers=$(echo -e `${KUBECTL_BIN} get pods ${namespace_arg} ${context:+--context=${context}} --output=jsonpath="{range .items[*]}{.metadata.name} {.spec['containers', 'initContainers'][*].name} \n{end}"`)
|
282
|
+
|
283
|
+
|
284
|
+
for pod in ${matching_pods[@]}; do
|
285
|
+
if [ ${#containers[@]} -eq 0 ]; then
|
286
|
+
pod_containers=($(echo -e "$all_pods_containers" | grep $pod | cut -d ' ' -f2- | xargs -n1))
|
287
|
+
else
|
288
|
+
pod_containers=("${containers[@]}")
|
289
|
+
fi
|
290
|
+
|
291
|
+
for container in ${pod_containers[@]}; do
|
292
|
+
if [ ${colored_output} == "false" ] || [ ${matching_pods_size} -eq 1 -a ${#pod_containers[@]} -eq 1 ]; then
|
293
|
+
color_start=$(tput sgr0)
|
294
|
+
color_index_prefix=""
|
295
|
+
else
|
296
|
+
color_index=`next_col $color_index`
|
297
|
+
color_start=$(tput setaf $color_index)
|
298
|
+
color_index_prefix=`if [ ${show_color_index} == "true" ]; then echo "${color_index}:"; else echo ""; fi`
|
299
|
+
fi
|
300
|
+
|
301
|
+
if [ ${#pod_containers[@]} -eq 1 ]; then
|
302
|
+
display_name="${pod}"
|
303
|
+
else
|
304
|
+
display_name="${pod} ${container}"
|
305
|
+
fi
|
306
|
+
|
307
|
+
if [ ${colored_output} == "false" ]; then
|
308
|
+
display_names_preview+=("${display_name}")
|
309
|
+
else
|
310
|
+
display_names_preview+=("$color_index_prefix${color_start}${display_name}${color_end}")
|
311
|
+
fi
|
312
|
+
|
313
|
+
if [ ${colored_output} == "false" ]; then
|
314
|
+
colored_line="[${display_name}] \$REPLY"
|
315
|
+
elif [ ${colored_output} == "pod" ]; then
|
316
|
+
colored_line="${color_start}[${color_end}${color_index_prefix}${color_start}${display_name}]${color_end} \$REPLY"
|
317
|
+
else
|
318
|
+
# color_index_prefix=`if [ ${show_color_index} == "true" ]; then echo "${color_index}:"; else echo ""; fi`
|
319
|
+
colored_line="${color_start}[${color_end}${color_index_prefix}${color_start}${display_name}] \$REPLY ${color_end}"
|
320
|
+
fi
|
321
|
+
|
322
|
+
kubectl_cmd="${KUBECTL_BIN} ${context:+--context=${context}} logs ${pod} ${container} -f=${follow} --previous=${previous} --since=${since} --tail=${tail} ${namespace_arg} ${cluster}"
|
323
|
+
colorify_lines_cmd="while read -r; do echo \"$colored_line\" | tail -n +1; done"
|
324
|
+
if [ "z" == "z$jq_selector" ]; then
|
325
|
+
logs_commands+=("${kubectl_cmd} ${timestamps} | ${colorify_lines_cmd}");
|
326
|
+
else
|
327
|
+
logs_commands+=("${kubectl_cmd} | jq --unbuffered -r -R --stream '. as \$line | try (fromjson | $jq_selector) catch \$line' | ${colorify_lines_cmd}");
|
328
|
+
fi
|
329
|
+
|
330
|
+
# There are only 11 usable colors
|
331
|
+
i=$(( ($i+1)%13 ))
|
332
|
+
done
|
333
|
+
done
|
334
|
+
|
335
|
+
# Preview pod colors
|
336
|
+
echo "Will tail ${#display_names_preview[@]} logs..."
|
337
|
+
for preview in "${display_names_preview[@]}"; do
|
338
|
+
echo "$preview"
|
339
|
+
done
|
340
|
+
|
341
|
+
if [[ ${dryrun} == true ]];
|
342
|
+
then
|
343
|
+
exit 0
|
344
|
+
fi
|
345
|
+
|
346
|
+
# Join all log commands into one string separated by " & "
|
347
|
+
join command_to_tail " & " "${logs_commands[@]}"
|
348
|
+
|
349
|
+
# Aggregate all logs and print to stdout
|
350
|
+
# Note that tail +1f doesn't work on some Linux distributions so we use this slightly longer alternative
|
351
|
+
# Note that if --follow=false, then the tail command should also not be followed
|
352
|
+
tail_follow_command="-f"
|
353
|
+
if [[ ${follow} == false ]];
|
354
|
+
then
|
355
|
+
tail_follow_command=""
|
356
|
+
fi
|
357
|
+
tail ${tail_follow_command} -n +1 <( eval "${command_to_tail}" ) $line_buffered
|
data/lib/cloud/sh/cli.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cloud
|
4
|
+
module Sh
|
5
|
+
class Cli
|
6
|
+
extend GLI::App
|
7
|
+
subcommand_option_handling :normal
|
8
|
+
arguments :strict
|
9
|
+
program_desc "Cloud shell helpers"
|
10
|
+
|
11
|
+
desc "Refresh aliases"
|
12
|
+
command :refresh do |c|
|
13
|
+
c.action do |global_options, options, args|
|
14
|
+
Cloud::Sh::Commands::Refresh.execute(global_options, options, args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# desc "SSH Into machines"
|
19
|
+
# arg_name 'hostname_or_ip'
|
20
|
+
# command :ssh do |c|
|
21
|
+
# c.action do |global_options, options, args|
|
22
|
+
# Cloud::Commands::Ssh.execute(global_options, options, args)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
|
26
|
+
# desc "DB Commands"
|
27
|
+
# command :db do |c|
|
28
|
+
# c.desc "Open a cli to to the db"
|
29
|
+
# c.arg "database_name_or_url"
|
30
|
+
# c.command :cli do |sc|
|
31
|
+
# sc.flag :cli, desc: "DB CLI tool to use (psql, mysql, mycli)", required: false, default_value: "auto", arg_name: "cli"
|
32
|
+
# sc.action do |global_options, options, args|
|
33
|
+
# Cloud::Commands::Db::Cli.execute(global_options, options, args)
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
|
37
|
+
# c.desc "Dump the database content to a sql file"
|
38
|
+
# c.arg "database_name_or_url"
|
39
|
+
# c.command :dump do |sc|
|
40
|
+
# sc.action do |global_options, options, args|
|
41
|
+
# Cloud::Commands::Db::Dump.execute(global_options, options, args)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
|
46
|
+
desc "K8S Commands"
|
47
|
+
command :k8s do |c|
|
48
|
+
c.desc "Open a shell in a container"
|
49
|
+
c.command :exec do |sc|
|
50
|
+
sc.flag :context, desc: "K8S Context", required: true, arg_name: "context"
|
51
|
+
sc.flag :namespace, desc: "K8S Namespace", required: true, arg_name: "namespace"
|
52
|
+
sc.flag :pod, desc: "K8S Pod (prefix)", required: true, arg_name: "pod"
|
53
|
+
sc.flag :cmd, desc: "Shell / Command to execute", required: false, arg_name: "cmd", default_value: "bash"
|
54
|
+
sc.action do |global_options, options, args|
|
55
|
+
Cloud::Sh::Commands::K8sExec.execute(global_options, options, args)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
c.desc "Tail output for a pod"
|
60
|
+
c.command :tail do |sc|
|
61
|
+
sc.flag :context, desc: "K8S Context", required: true, arg_name: "context"
|
62
|
+
sc.flag :namespace, desc: "K8S Namespace", required: true, arg_name: "namespace"
|
63
|
+
sc.flag :pod, desc: "K8S Pod (prefix)", required: true, arg_name: "pod", default_value: "all"
|
64
|
+
sc.flag :tail, desc: "Number of lines to tail initially", required: false, arg_name: "tail", default_value: "10"
|
65
|
+
sc.action do |global_options, options, args|
|
66
|
+
puts [global_options, options, args].inspect
|
67
|
+
Cloud::Sh::Commands::K8sTail.execute(global_options, options, args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cloud
|
4
|
+
module Sh
|
5
|
+
module Commands
|
6
|
+
class Base
|
7
|
+
include Cloud::Sh::Helpers::Commands
|
8
|
+
|
9
|
+
attr_reader :options, :args
|
10
|
+
|
11
|
+
def self.execute(global_options, options, args)
|
12
|
+
new(options: global_options.merge(options), args: args).execute
|
13
|
+
rescue Exception => e
|
14
|
+
puts e.backtrace.join("\n")
|
15
|
+
puts e.inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(options:, args:)
|
19
|
+
@options = options
|
20
|
+
@args = args
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def config
|
28
|
+
Cloud::Sh.config
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cloud/sh/commands/base"
|
4
|
+
|
5
|
+
module Cloud
|
6
|
+
module Sh
|
7
|
+
module Commands
|
8
|
+
class K8sExec < Base
|
9
|
+
def execute
|
10
|
+
pod = kubectl.context(options[:context]).namespace(options[:namespace]).get.pod.no_headers(true).map(:name).find do |pod|
|
11
|
+
pod.name.start_with?(options[:pod])
|
12
|
+
end
|
13
|
+
raise "Cannot find pod." unless pod
|
14
|
+
command = kubectl.context(options[:context]).namespace(options[:namespace]).exec.with(pod.name).stdin(true).tty(true).with(options[:cmd])
|
15
|
+
puts "Command: #{command}\n"
|
16
|
+
command.replace_current_process
|
17
|
+
end
|
18
|
+
|
19
|
+
def kubectl
|
20
|
+
command_chain("kubectl").kubeconfig(Cloud::Sh::Providers::DigitalOcean.kube_config)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if false
|
28
|
+
#!/usr/bin/env ruby
|
29
|
+
|
30
|
+
require "optparse"
|
31
|
+
|
32
|
+
context = ARGV[0]
|
33
|
+
namespace = ARGV[1]
|
34
|
+
pod = ARGV[2]
|
35
|
+
|
36
|
+
pod_index = 1
|
37
|
+
container_name = nil
|
38
|
+
command = "bash"
|
39
|
+
|
40
|
+
OptionParser.new do |opts|
|
41
|
+
opts.banner = "Usage: k8s-#{context}-#{namespace}-tail-#{pod} [options]"
|
42
|
+
opts.on("-p","--pod [POD_INDEX]", Integer, "Pod index to show (default: 1)") do |v,c|
|
43
|
+
pod_index = v
|
44
|
+
end
|
45
|
+
opts.on("-c","--container [CONTAINER_NAME]", String, "Container name (default: first one found)") do |v|
|
46
|
+
container_name = v
|
47
|
+
end
|
48
|
+
opts.on("-x","--command [COMMAND]", String, "Command to execute (default: bash)") do |v|
|
49
|
+
command = v
|
50
|
+
end
|
51
|
+
end.parse(ARGV[3..100])
|
52
|
+
|
53
|
+
pods = `kubectl --context #{context} --namespace #{namespace} get pod -o name | cut -f2 -d/ | egrep -e "^#{pod}"`.split("\n").map(&:strip)
|
54
|
+
if pods.size == 0
|
55
|
+
puts "No pods found."
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
if pod_index <= 0 || pod_index.to_i > pods.size
|
60
|
+
puts "Unrecognized pod index. Run the command again with a valid pod number: "
|
61
|
+
pods.each_with_index { |p, i| puts "#{i + 1}. #{p}" }
|
62
|
+
puts "\nExample: k8s-#{context}-#{namespace}-tail-#{pod} --pod 1"
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
pod_name = pods[pod_index.to_i - 1]
|
66
|
+
|
67
|
+
|
68
|
+
containers = `kubectl --context #{context} --namespace #{namespace} get pod #{pod_name} -o jsonpath='{.spec.containers[*].name}'`.split(" ")
|
69
|
+
container_name ||= containers.first
|
70
|
+
unless containers.include?(container_name)
|
71
|
+
puts "Unrecognized container name. Available options: #{containers.join(", ")}"
|
72
|
+
puts "\nExample: k8s-#{context}-#{namespace}-tail-#{pod} --pod #{pod_index} --container #{containers.first}"
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
puts "Context: #{context}"
|
77
|
+
puts "Namespace: #{namespace}"
|
78
|
+
puts "Pod: #{pod_name} (Options: #{pods.map.with_index { |p, i| "#{i + 1} - #{p}" }.join(", ")})"
|
79
|
+
puts "Container: #{container_name} (Options: #{containers.join(", ")})"
|
80
|
+
puts "Command: #{command}"
|
81
|
+
|
82
|
+
puts ""
|
83
|
+
|
84
|
+
cmd = "kubectl --context #{context} --namespace #{namespace} exec #{pod_name} -c #{container_name} -t -i #{command}"
|
85
|
+
puts "Running: #{cmd}"
|
86
|
+
puts ""
|
87
|
+
exec cmd
|
88
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cloud/sh/commands/base"
|
4
|
+
|
5
|
+
module Cloud
|
6
|
+
module Sh
|
7
|
+
module Commands
|
8
|
+
class K8sTail < Base
|
9
|
+
def execute
|
10
|
+
puts "Command: #{command}"
|
11
|
+
puts "Env: #{env.inspect}\n"
|
12
|
+
exec env, command
|
13
|
+
end
|
14
|
+
|
15
|
+
def command
|
16
|
+
[
|
17
|
+
exe,
|
18
|
+
"^" + (options[:pod] == "all" ? "." : options[:pod]),
|
19
|
+
"--context #{options[:context]}",
|
20
|
+
"--namespace #{options[:namespace]}",
|
21
|
+
"--regex",
|
22
|
+
"--tail #{options[:tail]}",
|
23
|
+
"--since 240h",
|
24
|
+
"--colored-output pod",
|
25
|
+
"--follow true"
|
26
|
+
].join(" ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def exe
|
30
|
+
File.expand_path("../../../../exe/vendor/kubetail", __dir__)
|
31
|
+
end
|
32
|
+
|
33
|
+
def env
|
34
|
+
{
|
35
|
+
"KUBECONFIG" => Cloud::Sh::Providers::DigitalOcean.kube_config
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|