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.
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
@@ -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