-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allowing URI path prefixes in server URLs #110
Conversation
Looks like the build in Travis is only failing because of bundler 2.x vs 1.x differences in 2.5. It certainly isn't caused by anything I did, but if you need me to try and resolve it I'm happy to. |
I have made a bunch of rubocop / dry-types / travis fixes that were included in #112 It should remove the unrelated changes from this PR. |
Let's get #112 merged first to make master branch more stable |
#112 merged, please rebase / merge master. |
It looks like #112 removed |
Merged in from master, retested, and pushed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@kke PTAL |
lib/k8s/transport.rb
Outdated
# @param auth_token [String] optional Authorization: Bearer token | ||
# @param options [Hash] @see Excon.new | ||
def initialize(server, auth_token: nil, **options) | ||
@server = server | ||
@path_prefix = URI.parse(server).path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to change the way @server is initialized too.
uri = URI.parse(server)
@server = #{uri.protocol}://#{uri.host}:#{uri.port}"
@path_prefix = uri.path
lib/k8s/transport.rb
Outdated
File.join('/', *path) | ||
path_components = ['/', *path] | ||
# Only add the path_prefix if it isn't there already... | ||
path_components.insert(0, path_prefix) unless [*path].join.match?(/^#{path_prefix}/) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[*path]
is redundant, it will just return a copy of the same array:
> path = ['a', 'b']
> [*path]
=> ['a', 'b']
[*path].join.match
will not add slashes to the path:
['a', 'b', 'c'].join => 'abc'
['a', 'b', 'c'].join('/') => 'a/b/c'
Also the regex is unnecessary, it could just be path.join('/').start_with?(path_prefix)
Maybe something like this:
def path(*path)
result = path.join('/')
result = File.join(path_prefix, path) unless result.start_with?(path_prefix)
result.start_with?('/') ? result : "/#{result}"
end
But is it actually necessary to skip adding the prefix if path already includes it?
That would force you to use a prefix that is not a kube api endpoint path. If the prefix is /api
, maybe it should just generate /api/api
. If you don't want that, just don't add the prefix to your server uri.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That being said, I think the whole method can then be:
def path(*path)
File.join(path_prefix, *path)
end
if the path_prefix
is set to /
in the initializer in case the uri path was empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this one-line path
yielded what looks to be a recursive issue with the construction of the path containing a prefix:
2.5.3 :001 > client = K8s::Client.config(K8s::Config.load_file(ENV['KUBECONFIG']))
=> #<K8s::Client:0x00007fd2e01d15b8 @transport=#<K8s::Transport:0x00007fd2e01d1ae0 @server="https://REDACTED.example.com:443", @path_prefix="/k8s/clusters/c-vkf6c", @auth_token="...
2.5.3 :002 > client.api.resource('pods', namespace: 'cattle-system').list.each do |pod|
2.5.3 :003 > puts "namespace=#{pod.metadata.namespace} pod: #{pod.metadata.name}"
2.5.3 :004?> end
Traceback (most recent call last):
16: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
15: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
14: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
13: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/cli.rb:463:in `exec'
12: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:28:in `run'
11: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `kernel_load'
10: from /Users/jlg438/.rvm/gems/ruby-2.5.3/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `load'
9: from exe/console:8:in `<top (required)>'
8: from (irb):2
7: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/api_client.rb:73:in `resource'
6: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/api_client.rb:61:in `find_api_resource'
5: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/api_client.rb:54:in `api_resources'
4: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/api_client.rb:46:in `api_resources!'
3: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/transport.rb:326:in `get'
2: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/transport.rb:250:in `request'
1: from /Users/jlg438/Development/ruby/k8s-client/lib/k8s/transport.rb:228:in `parse_response'
K8s::Error::NotFound (GET /k8s/clusters/c-vkf6c/k8s/clusters/c-vkf6c/api/v1 => HTTP 404 Not Found: {"paths"=>["/apis", "/apis/", "/apis/apiextensions.k8s.io", "/apis/apiextensions.k8s.io/v1beta1", "/health
z", "/healthz/etcd", "/healthz/log", "/healthz/ping", "/healthz/poststarthook/generic-apiserver-start-informers", "/healthz/poststarthook/start-apiextensions-controllers", "/healthz/poststarthook/start-api
extensions-informers", "/metrics", "/openapi/v2", "/swagger-2.0.0.json", "/swagger-2.0.0.pb-v1", "/swagger-2.0.0.pb-v1.gz", "/swagger.json", "/swaggerapi", "/version"]})
Notice the GET /k8s/clusters/c-vkf6c/k8s/clusters/c-vkf6c/api/v1
.
Using the "Maybe something like this" works better and all the tests still pass:
2.5.3 :001 > client = K8s::Client.config(K8s::Config.load_file(ENV['KUBECONFIG']))
=> #<K8s::Client:0x00007fd0520055a8 @transport=#<K8s::Transport:0x00007fd052005aa8 @server="https://REDACTED.example.com:443", @path_prefix="/k8s/clusters/c-vkf6c", @auth_token="...
2.5.3 :002 > client.api.resource('pods', namespace: 'cattle-system').list.each do |pod|
2.5.3 :003 > puts "namespace=#{pod.metadata.namespace} pod: #{pod.metadata.name}"
2.5.3 :004?> end
namespace=cattle-system pod: cattle-cluster-agent-78c9cd6ddb-wdf4c
namespace=cattle-system pod: cattle-node-agent-4b9zl
namespace=cattle-system pod: cattle-node-agent-88r5s
namespace=cattle-system pod: cattle-node-agent-8dqrd
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The [*path].join.match?(/^#{path_prefix}/)
will never match a prefix with /
slashes in it.
@kke, please review my comments and the additional commit. I made the initializer change you mentioned for |
There was some sort of recursion going on result = path.join('/')
result = File.join(path_prefix, path) unless result.start_with?(path_prefix)
# ^^^ the original array, should've been "result" probably.
result.start_with?('/') ? result : "/#{result}" I resolved the conflict and made it a bit prettier now. |
@server = server | ||
uri = URI.parse(server) | ||
@server = "#{uri.scheme}://#{uri.host}:#{uri.port}" | ||
@path_prefix = File.join('/', uri.path, '/') # add leading and/or trailing slashes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
irb(main):001:0> File.join('/', '', '/')
=> "/"
irb(main):004:0> File.join('/', '/', '/')
=> "/"
irb(main):003:0> File.join('/', '/foo/', '/')
=> "/foo/"
irb(main):005:0> File.join('/', '/foo', '/')
=> "/foo/"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prefix now always has either /
or a path with leading and trailing /
s.
@jakolehm seems fine to me now, ptala. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@jgnagy How do you configure the client for Rancher? I'm using my existing
|
@rbq what does your request look like? ( |
@kke I tried different things, e.g. the example from the README. Right now my script looks like this: #!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'k8s-client'
end
config = K8s::Config.load_file File.expand_path '~/.kube/config'
client = K8s::Client.config config
client.api('v1').resource('ingresses', namespace: 'default').list().each do |i|
puts "namespace=#{i.metadata.namespace} name=#{i.metadata.name}"
end |
Oh, and the rest of the exception, in case that's useful in any way: Traceback (most recent call last):
7: from ./index.rb:12:in `<main>'
6: from […]/gems/k8s-client-0.10.2/lib/k8s/api_client.rb:73:in `resource'
5: from […]/gems/k8s-client-0.10.2/lib/k8s/api_client.rb:61:in `find_api_resource'
4: from […]/gems/k8s-client-0.10.2/lib/k8s/api_client.rb:54:in `api_resources'
3: from […]/gems/k8s-client-0.10.2/lib/k8s/api_client.rb:46:in `api_resources!'
2: from […]/gems/k8s-client-0.10.2/lib/k8s/transport.rb:362:in `get'
1: from […]/gems/k8s-client-0.10.2/lib/k8s/transport.rb:286:in `request'
[…]/gems/k8s-client-0.10.2/lib/k8s/transport.rb:264:in `parse_response': ⏎
GET /k8s/clusters/c-blahblah/k8s/clusters/c-blahblah/api/v1 => HTTP 404 ⏎
Not Found: {"paths"=>["/apis", "/apis/", "/apis/apiextensions.k8s.io", ⏎
"/apis/apiextensions.k8s.io/v1beta1", "/healthz", "/healthz/etcd", ⏎
"/healthz/log", "/healthz/ping", "/healthz/poststarthook/crd-informer-synced", ⏎
"/healthz/poststarthook/generic-apiserver-start-informers", ⏎
"/healthz/poststarthook/start-apiextensions-controllers", ⏎
"/healthz/poststarthook/start-apiextensions-informers", "/metrics", ⏎
"/openapi/v2", "/version"]} (K8s::Error::NotFound) |
@rbq, my original PR for this didn't have this issue, but as I mentioned in this comment, the PR was modified by @kke to change the behavior. I have not been able to use this k8s-client with Rancher given how k8s-client handles this proxying. My team just drops to shell commands for |
@rbq / @jgnagy can you try with the branch of #158 ? #!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'k8s-client', github: 'kontena/k8s-client', branch: 'fix/prefix_path'
end
config = K8s::Config.load_file File.expand_path '~/.kube/config'
client = K8s::Client.config config
client.api('v1').resource('ingresses', namespace: 'default').list().each do |i|
puts "namespace=#{i.metadata.namespace} name=#{i.metadata.name}"
end |
@kke The branch works (but unfortuanately my script doesn't, since Ingress is not actually a v1 resource ;). |
Some Kubernetes platforms (like Rancher 2) proxy kubernetes API requests through a reverse-proxy. The
kubectl
command (as well as other kubernetes tools that use it) work properly with this approach. This PR brings k8s-client closer to the behavior users expect from tools like these.I experienced this issue while trying to use mortar and tracked the issue down to how this library was handling proxied requests like these.
While the testing that I added is very far from exhaustive, it does at least ensure that when provided a server with a URI path, it is handled correctly from a Transport perspective. I also verified that I can now see resources in my Rancher cluster.
Hopefully this can be reviewed and released so I can pester the mortar maintainers to pull it in at that level.