podjumper 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea343627011964fdee87c79c4df313160cd859f0
4
- data.tar.gz: bcae43efa8419d6aaa3931bc3d563b3778f7b35a
3
+ metadata.gz: 20feea954a12a81227f2ef8bff7f62697a159525
4
+ data.tar.gz: 8cc529ef8186915511690c17c0b205ef95131bf3
5
5
  SHA512:
6
- metadata.gz: 0b8a1209c432c93c1903b10260768f9dd45bbbf80b5b7d189ba0cd865aaa80eb419cddbc332c1f231c3a6a83073181461995db921366a6e451755de770694c7a
7
- data.tar.gz: 343905c5063e31abcb19034534da29c339f1aefa3341a93d78744aeb2256257c85b264a877ac9affc79fc1dd3cb4e62ae3bc62f2c023504c0c242e9774cd3e7e
6
+ metadata.gz: 4934b0b9d82315c32bfd67594f6c84caf1eda555c21f097dff57d1432576aac241fa8795d7df6b6868cf4e7c6b779bbeda6724dfc939b1bba2fc8e76a359e13d
7
+ data.tar.gz: ae7355ddff207b2c07a2192ea29ac3c0d72d90aadfc2be9682906f6ba8dca9031644bbc70f8fdf6606e4f894e21bfeeed3c262efe113a7f9b581c0d38fee4778
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.3.0
data/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Podjumper
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/podjumper`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Podjumper is a tiny command line tool for handling qa servers on a kubernetes cluster.
4
+ It makes a lof of assumptions of your kubernetes setup. For example, for shorthand commands which
5
+ are documented below it assumes that all pods have a label 'subdomain' and that there is only
6
+ one pod for each unique subdomain label.
4
7
 
5
- TODO: Delete this and the text above, and describe your gem
8
+ This is how podjumper is currently intended to work. More tooling is in development.
9
+ Some basic examples are below.
6
10
 
7
11
  ## Installation
8
12
 
@@ -22,7 +26,15 @@ Or install it yourself as:
22
26
 
23
27
  ## Usage
24
28
 
25
- TODO: Write usage instructions here
29
+ Tailing logs of a QA server
30
+ ```ruby
31
+ $ podj logs qa4
32
+ ```
33
+
34
+ List all rake commands remotely
35
+ ```ruby
36
+ $ podj exec qa4 'bundle exec rake tasks -T'
37
+ ```
26
38
 
27
39
  ## Development
28
40
 
@@ -32,7 +44,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
44
 
33
45
  ## Contributing
34
46
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/podjumper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/johndavidmartinez/podjumper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
48
 
37
49
  ## License
38
50
 
data/bin/podj CHANGED
@@ -1,13 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'gli'
4
+ require 'tty-command'
4
5
  require 'podjumper'
5
6
 
6
7
  include Podjumper
7
8
  include GLI::App
8
9
 
10
+ using Refinements
11
+
9
12
  version Podjumper::VERSION
10
13
 
14
+ desc 'Navigate to a container by picking through namespaces and subdomains.'
15
+ command %i[navigate nav] do |cmd|
16
+ cmd.action do |global_options, options, args|
17
+ info = JumpInfo.load(args)
18
+ puts "Namespace: #{info.namespace}",
19
+ "Subdomain: #{info.subdomain}",
20
+ "Pod Name: #{info.pod_name}",
21
+ "Container: #{info.container_name}"
22
+ end
23
+ end
24
+
11
25
  desc 'List things'
12
26
  command [:list, :ls] do |l|
13
27
  l.desc 'List all avaliable namespaces'
@@ -28,19 +42,29 @@ command [:list, :ls] do |l|
28
42
  l.desc 'List all containers in a namespace\'s pod'
29
43
  l.command [:containers, :c] do |c|
30
44
  c.action do |global_options, options, args|
31
- namespace = args[0]
32
- subdomain = args[1]
45
+ info = JumpInfo.load_upto_pod(args)
33
46
  filter = /#{args[2]}/
34
- puts containers(namespace, subdomain).select { |name| name =~ filter }
47
+ puts info.pod.container_names.select_regex(filter)
35
48
  end
36
49
  end
37
50
  end
38
51
 
39
- desc 'Jump straight into a rails pod'
40
- command [:jump] do |j|
41
- j.action do |global_options, options, args|
52
+ desc 'Shorthand command runs given command on nearest pod with subdomain label'
53
+ command [:exec] do |j|
54
+ j.action do |_, _, args|
55
+ subdomain = args[0]
56
+ command = args[1...args.length].join(' ')
57
+ info = JumpInfo.load_rails_from_subdomain(subdomain)
58
+ Command.new(info).exec(command)
59
+ end
60
+ end
61
+
62
+ desc 'Shorthand command tails logs on nearest pod with subdomain label'
63
+ command [:logs] do |l|
64
+ l.action do |_, _, args|
42
65
  subdomain = args[0]
43
- puts tty_command(subdomain)
66
+ info = JumpInfo.load_rails_from_subdomain(subdomain)
67
+ Command.new(info).logs
44
68
  end
45
69
  end
46
70
 
@@ -0,0 +1,39 @@
1
+ module Podjumper
2
+ class Command
3
+ def initialize(info)
4
+ @namespace = info.namespace
5
+ @pod = info.pod_name
6
+ @container = info.container_name
7
+ end
8
+
9
+ def exec(command)
10
+ tty = TTY::Command.new
11
+ tty.run(exec_command(command))
12
+ end
13
+
14
+ def logs
15
+ tty = TTY::Command.new(printer: :null)
16
+ tty.run(logs_command) do |out, _|
17
+ puts out if !ping?(out)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def exec_command(command)
24
+ "kubectl exec -ti #{@pod} -c #{@container} --namespace #{@namespace} -- #{command}"
25
+ end
26
+
27
+ def logs_command
28
+ "kubectl logs #{@pod} #{@container} --namespace #{@namespace} -f --since=1s"
29
+ end
30
+
31
+ def ping?(log_line)
32
+ return ping_matchers.any? { |regex| log_line =~ regex }
33
+ end
34
+
35
+ def ping_matchers
36
+ [/api\/ping/, /Api::SystemController#ping/, /Completed 200 OK/]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,83 @@
1
+ using Refinements
2
+
3
+ module Podjumper
4
+ JumpInfo = Struct.new(:namespace, :pod, :container_name) do
5
+ def subdomain
6
+ pod&.subdomain
7
+ end
8
+
9
+ def pod_name
10
+ pod&.name
11
+ end
12
+ end
13
+
14
+ class << JumpInfo
15
+ def load(args)
16
+ args = args.dup
17
+ namespace = disambiguate_namespace(args.shift)
18
+ pod = disambiguate_pod(namespace, args.shift)
19
+ container = disambiguate_container(pod, args.shift)
20
+
21
+ new(namespace, pod, container)
22
+ end
23
+
24
+ def load_upto_pod(args)
25
+ args = args.dup
26
+ namespace = disambiguate_namespace(args.shift)
27
+ pod = disambiguate_pod(namespace, args.shift)
28
+
29
+ new(namespace, pod, nil)
30
+ end
31
+
32
+ def load_upto_namespace(args)
33
+ args = args.dup
34
+ namespace = disambiguate_namespace(args.shift)
35
+
36
+ new(namespace, nil, nil)
37
+ end
38
+
39
+ def load_rails_from_subdomain(subdomain)
40
+ pod = Pod.find_by_subdomain(subdomain)
41
+ new(pod.namespace, pod, pod.container('rails'))
42
+ end
43
+
44
+ private
45
+
46
+ def disambiguate_namespace(query)
47
+ disambiguate(namespaces, query: query, title: 'Namespaces')
48
+ end
49
+
50
+ def disambiguate_pod(namespace, query)
51
+ all_pods = Pod.where(namespace: namespace)
52
+ subdomains = all_pods.map(&:subdomain).uniq
53
+ subdomain = disambiguate(subdomains, query: query, title: "Subdomains in namespace #{namespace.inspect}")
54
+ pods = all_pods.select { |pod| pod.subdomain == subdomain }
55
+
56
+ disambiguate(pods, title: "Pods in #{namespace} with subdomain #{subdomain.inspect}", &:name)
57
+ end
58
+
59
+ def disambiguate_container(pod, query)
60
+ disambiguate(pod.container_names, query: query, title: "Containers in pod #{pod.name.inspect}")
61
+ end
62
+
63
+ def disambiguate(choices, query: nil, title: nil, &choice_name)
64
+ choice_name ||= :itself
65
+ matches = matching_choices(choices, query, &choice_name)
66
+ if matches.count == 1
67
+ matches.first
68
+ else
69
+ puts title if title
70
+ cli.ask_for_choice(matches, &choice_name)
71
+ end
72
+ end
73
+
74
+ def matching_choices(choices, query)
75
+ return choices if query.nil?
76
+ choices.select { |c| yield(c) =~ /#{query}/ }
77
+ end
78
+
79
+ def cli
80
+ @cli ||= HighLine.new
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,83 @@
1
+ module Podjumper
2
+ class Pod
3
+ def initialize(description)
4
+ @description = description
5
+ validate_resource!
6
+ validate_version!
7
+ end
8
+
9
+ def namespace
10
+ metadata['namespace']
11
+ end
12
+
13
+ def subdomain
14
+ labels['subdomain']
15
+ end
16
+
17
+ def name
18
+ metadata['name']
19
+ end
20
+
21
+ def labels
22
+ metadata['labels'] || {}
23
+ end
24
+
25
+ def container(name)
26
+ container_names.find { |container_name| container_name =~ /#{name}/ }
27
+ end
28
+
29
+ def container_names
30
+ containers.map { |container| container['name'] }
31
+ end
32
+
33
+ def to_h
34
+ JSON.parse(to_json)
35
+ end
36
+
37
+ def to_json
38
+ description.to_json
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :description
44
+
45
+ def kind
46
+ description['kind']
47
+ end
48
+
49
+ def api_version
50
+ description['apiVersion']
51
+ end
52
+
53
+ def metadata
54
+ description['metadata']
55
+ end
56
+
57
+ def containers
58
+ description['spec']['containers']
59
+ end
60
+
61
+ def validate_resource!
62
+ raise "Expected a pod, got #{kind.inspect}" if kind != 'Pod'
63
+ end
64
+
65
+ def validate_version!
66
+ raise "Invalid API version #{api_version.inspect}" if api_version != 'v1'
67
+ end
68
+ end
69
+
70
+ class << Pod
71
+ def where(namespace: nil, subdomain: nil)
72
+ namespace_flag = namespace ? "-n '#{namespace}'" : '--all-namespaces'
73
+ subdomain_flag = subdomain ? "-l 'subdomain=#{subdomain}'" : ''
74
+ json = JSON.parse(`kubectl get pods --output=json #{namespace_flag} #{subdomain_flag}`)
75
+
76
+ json['items'].map(&Pod.method(:new))
77
+ end
78
+
79
+ def find_by_subdomain(subdomain)
80
+ new(JSON.parse(`kubectl get pods --output=json --all-namespaces --selector='subdomain=#{subdomain}'`)['items'].first)
81
+ end
82
+ end
83
+ end
@@ -14,4 +14,39 @@ module Refinements
14
14
  end
15
15
  end
16
16
  end
17
+
18
+ refine HighLine do
19
+ def ask_for_choice(choices)
20
+ padding_max = choices.count.to_s.length
21
+ puts choices.each_with_index.map { |choice, index|
22
+ index += 1
23
+ choice = yield choice if block_given?
24
+ padding = ' ' * (padding_max - index.to_s.length)
25
+ "#{index}. #{padding}#{choice}"
26
+ }
27
+
28
+ index = ask_for_index(choices.count)
29
+ return if index.nil?
30
+
31
+ choices[index]
32
+ end
33
+
34
+ def ask_for_index(count)
35
+ answer = ask("Choose from 1..#{count}, or (q)uit...") do |q|
36
+ q.case = :downcase
37
+ q.validate = /\A\d+|q\Z/
38
+ end
39
+ exit 0 if answer == 'q' # TODO: Safe exit
40
+ index = Integer(answer)
41
+ if index < 1
42
+ puts 'The first choice is 1'
43
+ ask_for_index(count)
44
+ elsif index > count
45
+ puts "The last choice is #{count}"
46
+ ask_for_index(count)
47
+ else
48
+ index - 1
49
+ end
50
+ end
51
+ end
17
52
  end
@@ -1,3 +1,3 @@
1
1
  module Podjumper
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/podjumper.rb CHANGED
@@ -1,63 +1,11 @@
1
+ require 'highline'
2
+ require 'json'
1
3
  require 'podjumper/version'
2
4
  require 'podjumper/refinements'
3
- require 'json'
5
+ require 'podjumper/pod'
6
+ require 'podjumper/jump_info'
7
+ require 'podjumper/command'
4
8
 
5
9
  module Podjumper
6
10
  using Refinements
7
-
8
- def namespaces
9
- `kubectl get namespaces --output=name`
10
- .split("\n")
11
- .map { |str| str.drop_prefix('namespaces/') }
12
- end
13
-
14
- def subdomains(namespace = nil)
15
- description = describe_pods(namespace: namespace)
16
- description.map do |pod|
17
- subdomain = get_subdomain(pod)
18
- next unless subdomain
19
- namespace = get_namespace(pod)
20
- [namespace, subdomain].join(' ')
21
- end.compact
22
- end
23
-
24
- def containers(namespace, subdomain)
25
- description = describe_pods(namespace: namespace, subdomain: subdomain)
26
- description.flat_map do |pod|
27
- subdomain = get_subdomain(pod)
28
- next unless subdomain
29
- namespace = get_namespace(pod)
30
- container_names(pod).map do |container_name|
31
- [namespace, subdomain, container_name].join(' ')
32
- end
33
- end.compact
34
- end
35
-
36
- def describe_pods(namespace: nil, subdomain: nil)
37
- namespace_flag = namespace ? "--namespace='#{namespace}'" : '--all-namespaces'
38
- subdomain_flag = subdomain ? "--selector='subdomain=#{subdomain}'" : ''
39
- JSON.parse(`kubectl get pods --output=json #{namespace_flag} #{subdomain_flag}`)['items']
40
- end
41
-
42
- def get_subdomain(pod)
43
- return unless pod['metadata'].key?('labels')
44
- pod['metadata']['labels']['subdomain']
45
- end
46
-
47
- def get_namespace(pod)
48
- pod['metadata']['namespace']
49
- end
50
-
51
- def container_names(pod)
52
- pod['spec']['containers'].map { |container| container['name'] }
53
- end
54
-
55
- def tty_command(subdomain)
56
- pods = describe_pods
57
- pod = pods.select! { |pod| pod&.[]('metadata')&.[]('labels')&.[]('subdomain') == subdomain }.first
58
- pod_name = pod['metadata']['name']
59
- container = container_names(pod).select_regex(/rails/).first
60
- namespace = get_namespace(pod)
61
- "kubectl exec -ti #{pod_name} -c #{container} --namespace #{namespace} -- bash"
62
- end
63
11
  end
data/podjumper.gemspec CHANGED
@@ -26,8 +26,8 @@ Gem::Specification.new do |spec|
26
26
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
27
  f.match(%r{^(test|spec|features)/})
28
28
  end
29
- spec.bindir = "exe"
30
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.bindir = "bin"
30
+ spec.executables = ["podj"]
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  spec.add_development_dependency "bundler", "~> 1.15"
@@ -35,8 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency "rspec", "~> 3.0"
36
36
  spec.add_development_dependency "byebug"
37
37
 
38
- spec.add_dependency "gli", "~> 2.17.1"
39
-
40
- spec.bindir = 'bin'
41
- spec.executables << 'podj'
38
+ spec.add_dependency "gli", "~> 2.17"
39
+ spec.add_dependency "highline", ">= 1.7"
40
+ spec.add_dependency "tty-command"
42
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: podjumper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - johndavidmartinez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-29 00:00:00.000000000 Z
11
+ date: 2018-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,14 +72,42 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 2.17.1
75
+ version: '2.17'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 2.17.1
82
+ version: '2.17'
83
+ - !ruby/object:Gem::Dependency
84
+ name: highline
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '1.7'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '1.7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tty-command
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description:
84
112
  email:
85
113
  - johndavidmartinez1@gmail.com
@@ -90,6 +118,7 @@ extra_rdoc_files: []
90
118
  files:
91
119
  - ".gitignore"
92
120
  - ".rspec"
121
+ - ".ruby-version"
93
122
  - ".travis.yml"
94
123
  - CODE_OF_CONDUCT.md
95
124
  - Gemfile
@@ -102,6 +131,9 @@ files:
102
131
  - bin/setup
103
132
  - cmd
104
133
  - lib/podjumper.rb
134
+ - lib/podjumper/command.rb
135
+ - lib/podjumper/jump_info.rb
136
+ - lib/podjumper/pod.rb
105
137
  - lib/podjumper/refinements.rb
106
138
  - lib/podjumper/version.rb
107
139
  - podjumper.gemspec