cloud-sh 1.0.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/.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
|