podjumper 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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