pax_global_header00006660000000000000000000000064143507056100014513gustar00rootroot0000000000000052 comment=ca8fec9fb47accdea54db2be0a7ceba9a66ae70a kubeclient-4.11.0/000077500000000000000000000000001435070561000137235ustar00rootroot00000000000000kubeclient-4.11.0/.github/000077500000000000000000000000001435070561000152635ustar00rootroot00000000000000kubeclient-4.11.0/.github/workflows/000077500000000000000000000000001435070561000173205ustar00rootroot00000000000000kubeclient-4.11.0/.github/workflows/actions.yml000066400000000000000000000027571435070561000215160ustar00rootroot00000000000000name: CI on: push: branches: - '**' tags: - '**' pull_request: branches: - '**' jobs: build: continue-on-error: true runs-on: ${{ matrix.os_and_command.os }} strategy: matrix: ruby: [ '2.7', '3.0', '3.1', 'ruby-head', 'truffleruby-head' ] os_and_command: - os: macos-latest command: 'env TESTOPTS="--verbose" bundle exec rake test' - os: windows-latest command: 'env TESTOPTS="--verbose" bundle exec rake test' - os: ubuntu-latest # Sometimes minitest starts and then just hangs printing nothing. # Github by default kills after 6hours(!). Hopefully SIGTERM may let it print some details? command: 'timeout --signal=TERM 3m env TESTOPTS="--verbose" test/config/update_certs_k0s.rb' include: # run rubocop against lowest supported ruby - os: ubuntu-latest ruby: '2.7' command: 'bundle exec rake rubocop' name: ${{ matrix.os_and_command.os }} ${{ matrix.ruby }} rake ${{ matrix.os_and_command.command }} steps: - uses: actions/checkout@v2 # actions/setup-ruby did not support truffle or bundler caching - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: false # disable running 'bundle install' and caching installed gems see https://github.com/httprb/http/issues/572 - run: bundle install - run: ${{ matrix.os_and_command.command }} timeout-minutes: 10 kubeclient-4.11.0/.gitignore000066400000000000000000000002161435070561000157120ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ *.bundle *.so *.o *.a mkmf.log *.idea* /Gemfile.dev.rb kubeclient-4.11.0/.rubocop.yml000066400000000000000000000061151435070561000162000ustar00rootroot00000000000000AllCops: DisplayCopNames: true TargetRubyVersion: 2.7 # Oldest version kubeclient supports MethodLength: Enabled: false ClassLength: Enabled: false Metrics/AbcSize: Enabled: false Metrics/ParameterLists: Max: 5 CountKeywordArgs: false Metrics/ModuleLength: Enabled: false Metrics/BlockLength: Exclude: [kubeclient.gemspec] Security/MarshalLoad: Exclude: [test/**/*] Style/FileName: Exclude: [Gemfile, Rakefile, Gemfile.dev.rb] Style/RegexpLiteral: Enabled: false # Cops that have active offences in the codebase. Lint/RedundantCopDisableDirective: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Max: 8 Metrics/PerceivedComplexity: Enabled: false Max: 8 Style/MethodCallWithArgsParentheses: Enabled: false IgnoredMethods: [require, require_relative, raise, include, attr_reader, refute, assert] Exclude: [Gemfile, Rakefile, kubeclient.gemspec, Gemfile.dev.rb] Style/FrozenStringLiteralComment: Enabled: false Lint/UnreachableLoop: Enabled: false Style/RedundantRegexpEscape: Enabled: false Layout/MultilineMethodCallIndentation: Enabled: false Lint/UselessAssignment: Enabled: false Style/StringLiterals: Enabled: false Layout/ExtraSpacing: Enabled: false Layout/IndentationWidth: Enabled: false Naming/MethodParameterName: Enabled: false Layout/HashAlignment: Enabled: false Layout/TrailingWhitespace: Enabled: false Naming/RescuedExceptionsVariableName: Enabled: false Style/RedundantBegin: Enabled: false Style/WordArray: Enabled: false Style/ExplicitBlockArgument: Enabled: false Layout/LeadingEmptyLines: Enabled: false Layout/EmptyLineAfterGuardClause: Enabled: false Style/SafeNavigation: Enabled: false Style/SoleNestedConditional: Enabled: false Lint/MissingSuper: Enabled: false Style/IfUnlessModifier: Enabled: false Layout/LineLength: Enabled: false Lint/MissingCopEnableDirective: Enabled: false Naming/MethodName: Enabled: false Style/StringConcatenation: Enabled: false Style/SlicingWithRange: Enabled: false Lint/MixedRegexpCaptureTypes: Enabled: false Style/AccessorGrouping: Enabled: false Style/HashEachMethods: Enabled: false Naming/AccessorMethodName: Enabled: false Style/RedundantAssignment: Enabled: false Gemspec/OrderedDependencies: Enabled: false Style/ExpandPathArguments: Enabled: false Style/Encoding: Enabled: false # New Cops to configure Lint/DuplicateBranch: # (new in 1.3) Enabled: false Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1) Enabled: false Lint/EmptyBlock: # (new in 1.1) Enabled: false Lint/EmptyClass: # (new in 1.3) Enabled: false Lint/NoReturnInBeginEndBlocks: # (new in 1.2) Enabled: false Lint/ToEnumArguments: # (new in 1.1) Enabled: false Lint/UnmodifiedReduceAccumulator: # (new in 1.1) Enabled: false Style/ArgumentsForwarding: # (new in 1.1) Enabled: false Style/CollectionCompact: # (new in 1.2) Enabled: false Style/DocumentDynamicEvalDefinition: # (new in 1.1) Enabled: false Style/NegatedIfElseCondition: # (new in 1.2) Enabled: false Style/NilLambda: # (new in 1.3) Enabled: false Style/SwapValues: # (new in 1.1) Enabled: false kubeclient-4.11.0/CHANGELOG.md000066400000000000000000000272121435070561000155400ustar00rootroot00000000000000# Changelog Notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). Kubeclient release versioning follows [SemVer](https://semver.org/). ## 4.11.0 — 2022-12-22 ### Removed - Dropped support for EOL Ruby versions 2.5, 2.6. (#589) ### Added - Relaxed dependency on `http` gem (used for watches) to allow 5.y.z versions. (#589) - Specifically, http 5.1.1 may fix issues watching with IPv6. (#585) ## 4.10.1 — 2022-10-01 ### Removed - Dropped debug logging about bearer token options that was added in 4.10.0. (#577) ## 4.10.0 — 2022-08-29 ### Added - When using `:bearer_token_file`, re-read the file on every request. (#566 closed #561) Kubernetes version 1.21 graduated [BoundServiceAccountTokenVolume feature][] to beta and enabled it by default, so standard in-cluster auth now uses short-lived tokens. This changes allows a long-lived `Client` object to keep working when the token file gets rotated. It's not optimized at all, if you feel the performance overhead, please report! [BoundServiceAccountTokenVolume feature]: https://github.com/kubernetes/enhancements/issues/542 ## 4.9.3 — 2022-03-23 ### Fixed - VULNERABILITY FIX: Previously, whenever kubeconfig did not define custom CA (normal situation for production clusters with public domain and certificate!), `Config` was returning ssl_options[:verify_ssl] hard-coded to `VERIFY_NONE` :-( Assuming you passed those ssl_options to Kubeclient::Client, this means that instead of checking server's certificate against your system CA store, it would accept ANY certificate, allowing easy man-in-the middle attacks. This is especially dangerous with user/password or token credentials because MITM attacker could simply steal those credentials to the cluster and do anything you could do on the cluster. This was broken IN ALL RELEASES MADE BEFORE 2022, ever since [`Kubeclient::Config` was created](https://github.com/ManageIQ/kubeclient/pull/127/files#diff-32e70f2f6781a9e9c7b83ae5e7eaf5ffd068a05649077fa38f6789e72f3de837R41-R48). [#554](https://github.com/ManageIQ/kubeclient/issues/554). - Bug fix: kubeconfig `insecure-skip-tls-verify` field was ignored. When kubeconfig did define custom CA, `Config` was returning hard-coded `VERIFY_PEER`. Now we honor it, return `VERIFY_NONE` iff kubeconfig has explicit `insecure-skip-tls-verify: true`, otherwise `VERIFY_PEER`. [#555](https://github.com/ManageIQ/kubeclient/issues/555). - `Config`: fixed parsing of `certificate-authority` file containing concatenation of several certificates. Previously, server's cert was checked against only first CA cert, resulting in possible "certificate verify failed" errors. An important use case is a chain of root & intermediate cert(s) - necessary when cluster's CA itself is signed by another custom CA. But also helps when you simply concatenate independent certs. (#461, #552) - Still broken (#460): inline `certificate-authority-data` is still parsed using `add_cert` method that handles only one cert. These don't affect code that supplies `Client` parameters directly, only code that uses `Config`. ## 4.9.2 — 2021-05-30 ### Added - Ruby 3.0 compatibility (#500, #505). ### Removed - Reduce .gem size by dropping test/ directory, it's useless at run time (#502). ## 4.9.1 — 2020-08-31 ### Fixed - Now should work with apiserver deployed not at root of domain but a sub-path, which is standard with Rancher. Notably, `create_...` methods were sending bad apiVersion and getting 400 error. (#457, hopefully fixes #318, #418 and https://gitlab.com/gitlab-org/gitlab/-/issues/22043) ## 4.9.0 - 2020-08-03 ### Added - Support for `user: exec` credential plugins using TLS client auth (#453) ## 4.8.0 — 2020-07-03 ### Added - Support for server-side apply (#448). ### Fixed - Declared forgotten dependency on jsonpath, needed for `gcp` provider with `cmd-path` (#450). ## 4.7.0 — 2020-06-14 ### Fixed - Ruby 2.7 compatibility: bumped minimum recursive-open-struct to one that works on 2.7 (#439). - Ruby 2.7 warnings (#433, #438). - Improved watch documentation, including behavior planned to change in 5.0.0 (#436). ### Added - Google Application Default Credentials: Added `userinfo.email` to requested scopes, which is necessary for RBAC policies (#441). ## 4.6.0 — 2019-12-30 ### Fixed - AmazonEksCredentials was sometimes leaving base64 padding that IAM auth of the EKS cluster rejects. Now padding is always stripped. (#424, #423) ### Added - Allow calling `watch_foos` methods with a block, simpler to use and guarantees closing the connection. (#425) - Support `limitBytes` query parameter for `get_pod_log`. (#426) ## 4.5.0 — 2019-09-27 ### Added - Support `:resourceVersion` parameter in `get_foos` methods (similar to existing support in `watch_foos` methods). (#420) - Relax dependency on `http` gem to allow both 3.x and 4.x. (#413) ## 4.4.0 — 2019-05-03 ### Added - GCP configs with `user[auth-provider][name] == 'gcp'` will execute credential plugin (normally the `gcloud config config-helper` subcommand) when the config specifies it in `cmd-path`, `cmd-args` fields (similar to `exec` support). This code path works without `googleauth` gem. Otherwise, `GoogleApplicationDefaultCredentials` path will be tried as before. (#410) - `AmazonEksCredentials` helper for obtaining a token to authenticate against Amazon EKS. This is not currently integrated in `Config`, you will need to invoke it yourself. You'll need some aws gems that Kubeclient _does not_ include. (#404, #406) ### Changed - OpenID Connect tokens which cannot be validaded because we cannot identify the key they were signed with will be considered expired and refreshed as usual. (#407) ## 4.3.0 — 2019-03-03 ### Changed - `GoogleApplicationDefaultCredentials` will now automatically be used by `Config` if the `user[auth-provider][name] == 'gcp'` in the provided context. Note that `user[exec]` is checked first in anticipation of this functionality being added to GCP sometime in the future. Kubeclient _does not_ include the required `googleauth` gem, so you will need to include it in your calling application. (#394) ### Added - OpenID Connect credentials will automatically be used if the `user[auth-provider][name] == 'oidc'` in the provided context. Note that `user[exec]` is checked first. Kubeclient _does not_ include the required `openid_connect` gem, so you will need to include it in your calling application. (#396) - Support for `json_patch_#{entity}` and `merge_patch_#{entity}`. `patch_#{entity}` will continue to use strategic merge patch. (#390) ## 4.2.2 — 2019-01-09 ### Added - New `http_max_redirects` option (#374). ### Changed - Default max redirects for watch increased from 4 to 10, to match other verbs (#374). ## 4.2.1 — 2018-12-26 ### Fixed - For resources that contain dashes in name, there will be an attempt to resolve the method name based on singular name prefix or by replacing the dash in names with underscores (#383). ## 4.2.0 — 2018-12-20 ### Added - Support `user: exec: ...` credential plugins like in Go client (#363, #375). ### Security - Really made `Kubeclient::Config.new(data, nil)` prevent external file lookups. (#372) README documented this since 3.1.1 (#334) but alas that was a lie — absolute paths always worked. Now this also prevents credential plugin execution. Even in this mode, using config from untrusted sources is not recommended. This release included all changes up to 4.1.1, but NOT 4.1.2 which was branched off later (4.2.1 does include same fix). ## 4.1.2 — 2018-12-26 ### Fixed - For resources that contain dashes in name, there will be an attempt to resolve the method name based on singular name prefix or by replacing the dash in names with underscores (#382). ## 4.1.1 — 2018-12-17 ### Fixed - Fixed method names for non-suffix plurals such as y -> ies (#377). ## 4.1.0 — 2018-11-28 — REGRESSION This version broke method names where plural is not just adding a suffix, notably y -> ies (bug #376). ### Fixed - Support custom resources with lowercase `kind` (#361). - `create_security_context_constraint` now works (#366). - `get_security_context_constraints.kind`, `get_endpoints.kind` are now plural as in kubernetes (#366). ### Added - Add support for retrieving large lists of objects in chunks (#356). ## 4.0.0 — 2018-07-23 ### Removed - Bumped officially supported kubernetes versions to >= 1.3. - Specifically `proxy_url` no longer works for <= 1.2 (#323). ### Fixed - `proxy_url` now works for kubernetes 1.10 and later (#323). ### Changed - Switched `http` gem dependency from 2.y to 3.y (#321). ## 3.1.2 — 2018-06-11 ### Fixed - Fixed `Kubeclient::Config.read` regression, no longer crashes on YAML timestamps (#338). ## 3.1.1 - 2018-06-01 — REGRESSION In this version `Kubeclient::Config.read` raises Psych::DisallowedClass on legal yaml configs containing a timestamp, for example gcp access-token expiry (bug #337). ### Security - Changed `Kubeclient::Config.read` to use `YAML.safe_load` (#334). Previously, could deserialize arbitrary ruby classes. The risk depends on ruby classes available in the application; sometimes a class may have side effects - up to arbitrary code execution - when instantiated and/or built up with `x[key] = value` during YAML parsing. Despite this fix, using config from untrusted sources is not recommended. ## 3.1.0 - 2018-05-27 ### Fixed - Fixed watch `.finish` sometimes caused `HTTP::ConnectionError` exception from the reading loop (#315). ### Added - `get_pod_log` now has `timestamps`, `since_time` (#319) and `tail_lines` (#326) params. - `Kubeclient::Config::Context#namespace` now set, if present in kubeconfig file (#308). - Improved README directions for authenticating within a kubernetes cluster (#316). - `Kubeclient::GoogleApplicationDefaultCredentials` helper for Google application default credentials (#213). Needs `googleauth` gem. - New `as: :parsed` and `as: :parsed_symbolized` formats (#306). - Allow setting default `as:` format for the whole client (#299, #305). - Relaxed `recursive-open-struct` dependency to allow 1.1+ as well (#313). ## 3.0.0 - 2018-02-04 ### Removed - Dropped entity classes (`Kubeclient::Pod` etc.), only `Kubeclient::Resource` exists now (#292, #288). - Ruby 2.0, 2.1 no longer supported (#253, #291). ### Fixed - Added missing singular `get_security_context_constraint`, fixed `get_security_context_constraints` to mean plural (#261). - Fixed `@http_proxy_uri` undefined warning (#261). - Documentation fixes & improvements (#225, #229, #243, #296). ### Added - `delete_options:` parameter to `delete_*` methods, useful for cascade delete (#267). - `as: :raw` option for watch (#285). - Now raises `Kubeclient::HttpError`. Rescuing `KubeException` still works but is deprecated. (#195, #288) - 404 error raise `Kubeclient::ResourceNotFoundError`, a subclass of `HttpError` (#233). - Include request info in exception message (#221). - Ruby 2.4 and 2.5 are now supported & tested (#247, #295). ### Changed - `Kubeclient::Config#context(nonexistent_context_name)` raises `KeyError` instead of `RuntimeError`. - `update_*`, `delete_*`, `patch_*` now all return `RecursiveOpenStruct` consistently (#290). - Many dependencies bumped (#204, #231, #253, #269). ## 2.5.2 - 2018-02-04 - Watch results are now `RecursiveOpenStruct` inside arrays too (#279). - Fixed watch `.finish` sometimes caused `Errno::EBADF` exception from the reading loop (#280). - Easing dependency version (#287, #301) ## 2.5.1 - 2017-10-12 No changes since 2.5.0, fixed packaging mistake. ## [2.5.0 - 2017-10-12 was YANKED] ### Added - `as: raw` option for `get_*` methods returning a string (#262 via #271). ## 2.4.0 - 2017-05-10 kubeclient-4.11.0/Gemfile000066400000000000000000000003201435070561000152110ustar00rootroot00000000000000source 'https://rubygems.org' dev_gemfile = File.expand_path('Gemfile.dev.rb', __dir__) eval_gemfile(dev_gemfile) if File.exist?(dev_gemfile) # Specify your gem's dependencies in kubeclient.gemspec gemspec kubeclient-4.11.0/LICENSE.txt000066400000000000000000000020551435070561000155500ustar00rootroot00000000000000Copyright (c) 2014 Alissa Bonas MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kubeclient-4.11.0/README.md000066400000000000000000001024341435070561000152060ustar00rootroot00000000000000# Kubeclient [![Gem Version](https://badge.fury.io/rb/kubeclient.svg)](http://badge.fury.io/rb/kubeclient) [![Build Status](https://travis-ci.org/abonas/kubeclient.svg?branch=master)](https://travis-ci.org/abonas/kubeclient) [![Code Climate](http://img.shields.io/codeclimate/github/abonas/kubeclient.svg)](https://codeclimate.com/github/abonas/kubeclient) A Ruby client for Kubernetes REST api. The client supports GET, POST, PUT, DELETE on all the entities available in kubernetes in both the core and group apis. The client currently supports Kubernetes REST api version v1. To learn more about groups and versions in kubernetes refer to [k8s docs](https://kubernetes.io/docs/api/) ## VULNERABILITY❗ If you use `Kubeclient::Config`, all gem versions released before 2022 could return incorrect `ssl_options[:verify_ssl]`, endangering your connection and cluster credentials. See https://github.com/ManageIQ/kubeclient/issues/554 for details and which versions got a fix. ## Installation Add this line to your application's Gemfile: ```ruby gem 'kubeclient' ``` And then execute: ```Bash bundle ``` Or install it yourself as: ```Bash gem install kubeclient ``` ## Usage Initialize the client: ```ruby client = Kubeclient::Client.new('http://localhost:8080/api/', "v1") ``` Or without specifying version (it will be set by default to "v1") ```ruby client = Kubeclient::Client.new('http://localhost:8080/api/') ``` For A Group Api: ```ruby client = Kubeclient::Client.new('http://localhost:8080/apis/batch', 'v1') ``` Another option is to initialize the client with URI object: ```ruby uri = URI::HTTP.build(host: "somehostname", port: 8080) client = Kubeclient::Client.new(uri) ``` ### SSL It is also possible to use https and configure ssl with: ```ruby ssl_options = { client_cert: OpenSSL::X509::Certificate.new(File.read('/path/to/client.crt')), client_key: OpenSSL::PKey::RSA.new(File.read('/path/to/client.key')), ca_file: '/path/to/ca.crt', verify_ssl: OpenSSL::SSL::VERIFY_PEER } client = Kubeclient::Client.new( 'https://localhost:8443/api/', "v1", ssl_options: ssl_options ) ``` As an alternative to the `ca_file` it's possible to use the `cert_store`: ```ruby cert_store = OpenSSL::X509::Store.new cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_cert_data)) ssl_options = { cert_store: cert_store, verify_ssl: OpenSSL::SSL::VERIFY_PEER } client = Kubeclient::Client.new( 'https://localhost:8443/api/', "v1", ssl_options: ssl_options ) ``` For testing and development purpose you can disable the ssl check with: ```ruby ssl_options = { verify_ssl: OpenSSL::SSL::VERIFY_NONE } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', ssl_options: ssl_options ) ``` ### Authentication If you are using basic authentication or bearer tokens as described [here](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/authentication.md) then you can specify one of the following: ```ruby auth_options = { username: 'username', password: 'password' } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', auth_options: auth_options ) ``` or (fixed token, if it expires it's up to you to create a new `Client` object): ```ruby auth_options = { bearer_token: 'MDExMWJkMjItOWY1Ny00OGM5LWJlNDEtMjBiMzgxODkxYzYz' } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', auth_options: auth_options ) ``` or (will automatically re-read the token if file is updated): ```ruby auth_options = { bearer_token_file: '/path/to/token_file' } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', auth_options: auth_options ) ``` #### Inside a Kubernetes cluster The [recommended way to locate the API server](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) within the pod is with the `kubernetes.default.svc` DNS name, which resolves to a Service IP which in turn will be routed to an API server. The recommended way to authenticate to the API server is with a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/). kube-system associates a pod with a service account and a bearer token for that service account is placed into the filesystem tree of each container in that pod at `/var/run/secrets/kubernetes.io/serviceaccount/token`. If available, a certificate bundle is placed into the filesystem tree of each container at `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`, and should be used to verify the serving certificate of the API server. For example: ```ruby auth_options = { bearer_token_file: '/var/run/secrets/kubernetes.io/serviceaccount/token' } ssl_options = {} if File.exist?("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt") ssl_options[:ca_file] = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" end client = Kubeclient::Client.new( 'https://kubernetes.default.svc', 'v1', auth_options: auth_options, ssl_options: ssl_options ) ``` Finally, the default namespace to be used for namespaced API operations is placed in a file at `/var/run/secrets/kubernetes.io/serviceaccount/namespace` in each container. It is recommended that you use this namespace when issuing API commands below. ```ruby namespace = File.read('/var/run/secrets/kubernetes.io/serviceaccount/namespace') ``` You can find information about tokens in [this guide](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) and in [this reference](http://kubernetes.io/docs/admin/authentication/). ### Non-blocking IO You can also use kubeclient with non-blocking sockets such as Celluloid::IO, see [here](https://github.com/httprb/http/wiki/Parallel-requests-with-Celluloid%3A%3AIO) for details. For example: ```ruby require 'celluloid/io' socket_options = { socket_class: Celluloid::IO::TCPSocket, ssl_socket_class: Celluloid::IO::SSLSocket } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', socket_options: socket_options ) ``` This affects only `.watch_*` sockets, not one-off actions like `.get_*`, `.delete_*` etc. ### Proxies You can also use kubeclient with an http proxy server such as tinyproxy. It can be entered as a string or a URI object. For example: ```ruby proxy_uri = URI::HTTP.build(host: "myproxyhost", port: 8443) client = Kubeclient::Client.new( 'https://localhost:8443/api/', http_proxy_uri: proxy_uri ) ``` ### Redirects You can optionally not allow redirection with kubeclient. For example: ```ruby client = Kubeclient::Client.new( 'https://localhost:8443/api/', http_max_redirects: 0 ) ``` ### Timeouts Watching configures the socket to never time out (however, sooner or later all watches terminate). One-off actions like `.get_*`, `.delete_*` have a configurable timeout: ```ruby timeouts = { open: 10, # unit is seconds read: nil # nil means never time out } client = Kubeclient::Client.new( 'https://localhost:8443/api/', timeouts: timeouts ) ``` Default timeouts match `Net::HTTP` and `RestClient`, which unfortunately depends on ruby version: - open was infinite up to ruby 2.2, 60 seconds in 2.3+. - read is 60 seconds. If you want ruby-independent behavior, always specify `:open`. ### Discovery Discovery from the kube-apiserver is done lazily on method calls so it would not change behavior. It can also be done explicitly: ```ruby client = Kubeclient::Client.new('http://localhost:8080/api', 'v1') client.discover ``` It is possible to check the status of discovery ```ruby unless client.discovered client.discover end ``` ### Kubeclient::Config If you've been using `kubectl` and have a `.kube/config` file (possibly referencing other files in fields such as `client-certificate`), you can auto-populate a config object using `Kubeclient::Config`: ```ruby # assuming $KUBECONFIG is one file, won't merge multiple like kubectl config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '/path/to/.kube/config') ``` This will lookup external files; relative paths will be resolved relative to the file's directory, if config refers to them with relative path. This includes external [`exec:` credential plugins][exec] to be executed. [exec]: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins You can also construct `Config` directly from nested data. For example if you have JSON or YAML config data in a variable: ```ruby config = Kubeclient::Config.new(YAML.safe_load(yaml_text), nil) # or config = Kubeclient::Config.new(JSON.parse(json_text), nil) ``` The 2nd argument is a base directory for finding external files, if config refers to them with relative path. Setting it to `nil` disables file lookups, and `exec:` execution - such configs will raise an exception. (A config can be self-contained by using inline fields such as `client-certificate-data`.) To create a client based on a Config object: ```ruby # default context according to `current-context` field: context = config.context # or to use a specific context, by name: context = config.context('default/192-168-99-100:8443/system:admin') Kubeclient::Client.new( context.api_endpoint, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) ``` #### Amazon EKS Credentials On Amazon EKS by default the authentication method is IAM. When running kubectl a temporary token is generated by shelling out to the aws-iam-authenticator binary which is sent to authenticate the user. See [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator). To replicate that functionality, the `Kubeclient::AmazonEksCredentials` class can accept a set of IAM credentials and contains a helper method to generate the authentication token for you. This requires a set of gems which are _not_ included in `kubeclient` dependencies (`aws-sigv4`) so you should add them to your bundle. You will also require either the `aws-sdk` v2 or `aws-sdk-core` v3 gems to generate the required `Aws:Credentials` object to pass to this method. To obtain a token: ```ruby require 'aws-sdk-core' # Use keys credentials = Aws::Credentials.new(access_key, secret_key) # Or a profile credentials = Aws::SharedCredentials.new(profile_name: 'default').credentials auth_options = { bearer_token: Kubeclient::AmazonEksCredentials.token(credentials, eks_cluster_name) } client = Kubeclient::Client.new( eks_cluster_https_endpoint, 'v1', auth_options: auth_options ) ``` Note that this returns a token good for one minute. If your code requires authorization for longer than that, you should plan to acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section. #### Google GCP credential plugin If kubeconfig file has `user: {auth-provider: {name: gcp, cmd-path: ..., cmd-args: ..., token-key: ...}}`, the command will be executed to obtain a token. (Normally this would be a `gcloud config config-helper` command.) Note that this returns an expiring token. If your code requires authorization for a long time, you should plan to acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section. #### Google's Application Default Credentials On Google Compute Engine, Google App Engine, or Google Cloud Functions, as well as `gcloud`-configured systems with [application default credentials](https://developers.google.com/identity/protocols/application-default-credentials), kubeclient can use `googleauth` gem to authorize. This requires the [`googleauth` gem](https://github.com/google/google-auth-library-ruby) that is _not_ included in `kubeclient` dependencies so you should add it to your bundle. If you use `Config.context(...).auth_options` and the kubeconfig file has `user: {auth-provider: {name: gcp}}`, but does not contain `cmd-path` key, kubeclient will automatically try this (raising LoadError if you don't have `googleauth` in your bundle). Or you can obtain a token manually: ```ruby require 'googleauth' auth_options = { bearer_token: Kubeclient::GoogleApplicationDefaultCredentials.token } client = Kubeclient::Client.new( 'https://localhost:8443/api/', 'v1', auth_options: auth_options ) ``` Note that this returns a token good for one hour. If your code requires authorization for longer than that, you should plan to acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section. #### OIDC Auth Provider If the cluster you are using has OIDC authentication enabled you can use the `openid_connect` gem to obtain id-tokens if the one in your kubeconfig has expired. This requires the [`openid_connect` gem](https://github.com/nov/openid_connect) which is not included in the `kubeclient` dependencies so should be added to your own applications bundle. The OIDC Auth Provider will not perform the initial setup of your `$KUBECONFIG` file. You will need to use something like [`dexter`](https://github.com/gini/dexter) in order to configure the auth-provider in your `$KUBECONFIG` file. If you use `Config.context(...).auth_options` and the `$KUBECONFIG` file has user: `{auth-provider: {name: oidc}}`, kubeclient will automatically obtain a token (or use `id-token` if still valid) Tokens are typically short-lived (e.g. 1 hour) and the expiration time is determined by the OIDC Provider (e.g. Google). If your code requires authentication for longer than that you should obtain a new token periodically, see [How to manually renew](#how-to-manually-renew-expired-credentials) section. Note: id-tokens retrieved via this provider are not written back to the `$KUBECONFIG` file as they would be when using `kubectl`. #### How to manually renew expired credentials Kubeclient [does not yet](https://github.com/abonas/kubeclient/issues/393) help with this. The division of labor between `Config` and `Context` objects may change, for now please make no assumptions at which stage `exec:` and `auth-provider:` are handled and whether they're cached. The currently guaranteed way to renew is create a new `Config` object. The more painful part is that you'll then need to create new `Client` object(s) with the credentials from new config. So repeat all of this: ```ruby config = Kubeclient::Config.read(ENV['KUBECONFIG'] || '/path/to/.kube/config') context = config.context ssl_options = context.ssl_options auth_options = context.auth_options client = Kubeclient::Client.new( context.api_endpoint, 'v1', ssl_options: ssl_options, auth_options: auth_options ) # and additional Clients if needed... ``` #### Security: Don't use config from untrusted sources `Config.read` is catastrophically unsafe — it will execute arbitrary command lines specified by the config! `Config.new(data, nil)` is better but Kubeclient was never reviewed for behaving safely with malicious / malformed config. It might crash / misbehave in unexpected ways... #### namespace Additionally, the `config.context` object will contain a `namespace` attribute, if it was defined in the file. It is recommended that you use this namespace when issuing API commands below. This is the same behavior that is implemented by `kubectl` command. You can read it as follows: ```ruby puts config.context.namespace ``` ### Supported kubernetes versions We try to support the last 3 minor versions, matching the [official support policy for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/release/versioning.md#supported-releases-and-component-skew). Kubernetes 1.2 and below have known issues and are unsupported. Kubernetes 1.3 presumed to still work although nobody is really testing on such old versions... ## Supported actions & examples: Summary of main CRUD actions: ``` get_foos(namespace: 'namespace', **opts) # namespaced collection get_foos(**opts) # all namespaces or global collection get_foo('name', 'namespace', opts) # namespaced get_foo('name', nil, opts) # global watch_foos(namespace: ns, **opts) # namespaced collection watch_foos(**opts) # all namespaces or global collection watch_foos(namespace: ns, name: 'name', **opts) # namespaced single object watch_foos(name: 'name', **opts) # global single object delete_foo('name', 'namespace', opts) # namespaced delete_foo('name', nil, opts) # global create_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...})) create_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...})) # global update_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...})) update_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...})) # global patch_foo('name', patch, 'namespace') # namespaced patch_foo('name', patch) # global apply_foo(Kubeclient::Resource.new({metadata: {name: 'name', namespace: 'namespace', ...}, ...}), field_manager: 'myapp', **opts) apply_foo(Kubeclient::Resource.new({metadata: {name: 'name', ...}, ...}), field_manager: 'myapp', **opts) # global ``` These grew to be quite inconsistent :confounded:, see https://github.com/abonas/kubeclient/issues/312 and https://github.com/abonas/kubeclient/issues/332 for improvement plans. ### Get all instances of a specific entity type Such as: `get_pods`, `get_secrets`, `get_services`, `get_nodes`, `get_replication_controllers`, `get_resource_quotas`, `get_limit_ranges`, `get_persistent_volumes`, `get_persistent_volume_claims`, `get_component_statuses`, `get_service_accounts` ```ruby pods = client.get_pods ``` Get all entities of a specific type in a namespace: ```ruby services = client.get_services(namespace: 'development') ``` You can get entities which have specific labels by specifying a parameter named `label_selector` (named `labelSelector` in Kubernetes server): ```ruby pods = client.get_pods(label_selector: 'name=redis-master') ``` You can specify multiple labels (that option will return entities which have both labels: ```ruby pods = client.get_pods(label_selector: 'name=redis-master,app=redis') ``` There is also [a limited ability to filter by *some* fields](https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/). Which fields are supported is not documented, you can try and see if you get an error... ```ruby client.get_pods(field_selector: 'spec.nodeName=master-0') ``` You can ask for entities at a specific version by specifying a parameter named `resource_version`: ```ruby pods = client.get_pods(resource_version: '0') ``` but it's not guaranteed you'll get it. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions to understand the semantics. With default (`as: :ros`) return format, the returned object acts like an array of the individual pods, but also supports a `.resourceVersion` method. With `:parsed` and `:parsed_symbolized` formats, the returned data structure matches kubernetes list structure: it's a hash containing `metadata` and `items` keys, the latter containing the individual pods. #### Get all entities of a specific type in chunks ```ruby continue = nil loop do entities = client.get_pods(limit: 1_000, continue: continue) continue = entities.continue break if entities.last? end ``` See https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks for more information. The continue tokens expire after a short amount of time, so similar to a watch if you don't request a subsequent page within aprox. 5 minutes of the previous page being returned the server will return a `410 Gone` error and the client must request the list from the start (i.e. omit the continue token for the next call). Support for chunking was added in v1.9 so previous versions will ignore the option and return the full collection. #### Get a specific instance of an entity (by name) Such as: `get_service "service name"` , `get_pod "pod name"` , `get_replication_controller "rc name"`, `get_secret "secret name"`, `get_resource_quota "resource quota name"`, `get_limit_range "limit range name"` , `get_persistent_volume "persistent volume name"` , `get_persistent_volume_claim "persistent volume claim name"`, `get_component_status "component name"`, `get_service_account "service account name"` The GET request should include the namespace name, except for nodes and namespaces entities. ```ruby node = client.get_node "127.0.0.1" ``` ```ruby service = client.get_service "guestbook", 'development' ``` Note - Kubernetes doesn't work with the uid, but rather with the 'name' property. Querying with uid causes 404. #### Getting raw responses To avoid overhead from parsing and building `RecursiveOpenStruct` objects for each reply, pass the `as: :raw` option when initializing `Kubeclient::Client` or when calling `get_` / `watch_` methods. The result can then be printed, or searched with a regex, or parsed via `JSON.parse(r)`. ```ruby client = Kubeclient::Client.new(as: :raw) ``` or ```ruby pods = client.get_pods as: :raw node = client.get_node "127.0.0.1", as: :raw ``` Other formats are: - `:ros` (default) for `RecursiveOpenStruct` - `:parsed` for `JSON.parse` - `:parsed_symbolized` for `JSON.parse(..., symbolize_names: true)` ### Watch — Receive entities updates See https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes for an overview. It is possible to receive live update notices watching the relevant entities: ```ruby client.watch_pods do |notice| # process notice data end ``` The notices have `.type` field which may be `'ADDED'`, `'MODIFIED'`, `'DELETED'`, or currently `'ERROR'`, and an `.object` field containing the object. **UPCOMING CHANGE**: In next major version, we plan to raise exceptions instead of passing on ERROR into the block. For namespaced entities, the default watches across all namespaces, and you can specify `client.watch_secrets(namespace: 'foo')` to only watch in a single namespace. You can narrow down using `label_selector:` and `field_selector:` params, like with `get_pods` methods. You can also watch a single object by specifying `name:` e.g. `client.watch_nodes(name: 'gandalf')` (not namespaced so a name is enough) or `client.watch_pods(namespace: 'foo', name: 'bar')` (namespaced, need both params). Note the method name is still plural! There is no `watch_pod`, only `watch_pods`. The yielded "type" remains the same — watch notices, it's just they'll always refer to the same object. You can use `as:` param to control the format of the yielded notices. #### All watches come to an end! While nominally the watch block *looks* like an infinite loop, that's unrealistic. Network connections eventually get severed, and kubernetes apiserver is known to terminate watches. Unfortunately, this sometimes raises an exception and sometimes the loop just exits. **UPCOMING CHANGE**: In next major version, non-deliberate termination will always raise an exception; the block will only exit silenty if stopped deliberately. #### Deliberately stopping a watch You can use `break` or `return` inside the watch block. It is possible to interrupt the watcher from another thread with: ```ruby watcher = client.watch_pods watcher.each do |notice| # process notice data end # <- control will pass here after .finish is called ### In another thread ### watcher.finish ``` #### Starting watch version You can specify version to start from, commonly used in "List+Watch" pattern: ``` list = client.get_pods collection_version = list.resourceVersion # or with other return formats: list = client.get_pods(as: :parsed) collection_version = list['metadata']['resourceVersion'] # note spelling resource_version vs resourceVersion. client.watch_pods(resource_version: collection_version) do |notice| # process notice data end ``` It's important to understand [the effects of unset/0/specific resource_version](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions) as it modifies the behavior of the watch — in some modes you'll first see a burst of synthetic 'ADDED' notices for all existing objects. If you re-try a terminated watch again without specific resourceVersion, you might see previously seen notices again, and might miss some events. To attempt resuming a watch from same point, you can try using last resourceVersion observed during the watch. Or do list+watch again. Whenever you ask for a specific version, you must be prepared for an 410 "Gone" error if the server no longer recognizes it. #### Watch events about a particular object Events are [entities in their own right](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#event-v1-core). You can use the `field_selector` option as part of the watch methods. ```ruby client.watch_events(namespace: 'development', field_selector: 'involvedObject.name=redis-master') do |notice| # process notice date end ``` ### Delete an entity (by name) For example: `delete_pod "pod name"` , `delete_replication_controller "rc name"`, `delete_node "node name"`, `delete_secret "secret name"` Input parameter - name (string) specifying service name, pod name, replication controller name. ```ruby deleted = client.delete_service("redis-service") ``` If you want to cascade delete, for example a deployment, you can use the `delete_options` parameter. ```ruby deployment_name = 'redis-deployment' namespace = 'default' delete_options = Kubeclient::Resource.new( apiVersion: 'meta/v1', gracePeriodSeconds: 0, kind: 'DeleteOptions', propagationPolicy: 'Foreground' # Orphan, Foreground, or Background ) client.delete_deployment(deployment_name, namespace, delete_options: delete_options) ``` ### Create an entity For example: `create_pod pod_object`, `create_replication_controller rc_obj`, `create_secret secret_object`, `create_resource_quota resource_quota_object`, `create_limit_range limit_range_object`, `create_persistent_volume persistent_volume_object`, `create_persistent_volume_claim persistent_volume_claim_object`, `create_service_account service_account_object` Input parameter - object of type `Service`, `Pod`, `ReplicationController`. The below example is for v1 ```ruby service = Kubeclient::Resource.new service.metadata = {} service.metadata.name = "redis-master" service.metadata.namespace = 'staging' service.spec = {} service.spec.ports = [{ 'port' => 6379, 'targetPort' => 'redis-server' }] service.spec.selector = {} service.spec.selector.name = "redis" service.spec.selector.role = "master" service.metadata.labels = {} service.metadata.labels.app = 'redis' service.metadata.labels.role = 'slave' client.create_service(service) ``` ### Update an entity For example: `update_pod`, `update_service`, `update_replication_controller`, `update_secret`, `update_resource_quota`, `update_limit_range`, `update_persistent_volume`, `update_persistent_volume_claim`, `update_service_account` Input parameter - object of type `Pod`, `Service`, `ReplicationController` etc. The below example is for v1 ```ruby updated = client.update_service(service1) ``` ### Patch an entity (by name) For example: `patch_pod`, `patch_service`, `patch_secret`, `patch_resource_quota`, `patch_persistent_volume` Input parameters - name (string) specifying the entity name, patch (hash) to be applied to the resource, optional: namespace name (string) The PATCH request should include the namespace name, except for nodes and namespaces entities. The below example is for v1 ```ruby patched = client.patch_pod("docker-registry", {metadata: {annotations: {key: 'value'}}}, "default") ``` `patch_#{entity}` is called using a [strategic merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#notes-on-the-strategic-merge-patch). `json_patch_#{entity}` and `merge_patch_#{entity}` are also available that use JSON patch and JSON merge patch, respectively. These strategies are useful for resources that do not support strategic merge patch, such as Custom Resources. Consult the [Kubernetes docs](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) for more information about the different patch strategies. ### Apply an entity This is similar to `kubectl apply --server-side` (kubeclient doesn't implement logic for client-side apply). See https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply For example: `apply_pod` Input parameters - resource (Kubeclient::Resource) representing the desired state of the resource, field_manager (String) to identify the system managing the state of the resource, force (Boolean) whether or not to override a field managed by someone else. Example: ```ruby service = Kubeclient::Resource.new( metadata: { name: 'redis-master', namespace: 'staging', }, spec: { ... } ) client.apply_service(service, field_manager: 'myapp') ``` ### Get all entities of all types : all_entities Makes requests for all entities of each discovered kind (in this client's API group). This method is a convenience method instead of calling each entity's get method separately. Returns a hash with keys being the *singular* entity kind, in lowercase underscore style. For example for core API group may return keys `"node'`, `"secret"`, `"service"`, `"pod"`, `"replication_controller"`, `"namespace"`, `"resource_quota"`, `"limit_range"`, `"endpoint"`, `"event"`, `"persistent_volume"`, `"persistent_volume_claim"`, `"component_status"`, `"service_account"`. Each key points to an EntityList of same type. ```ruby client.all_entities ``` ### Get a proxy URL You can get a complete URL for connecting a kubernetes entity via the proxy. ```ruby client.proxy_url('service', 'srvname', 'srvportname', 'ns') # => "https://localhost.localdomain:8443/api/v1/proxy/namespaces/ns/services/srvname:srvportname" ``` Note the third parameter, port, is a port name for services and an integer for pods: ```ruby client.proxy_url('pod', 'podname', 5001, 'ns') # => "https://localhost.localdomain:8443/api/v1/namespaces/ns/pods/podname:5001/proxy" ``` ### Get the logs of a pod You can get the logs of a running pod, specifying the name of the pod and the namespace where the pod is running: ```ruby client.get_pod_log('pod-name', 'default') # => "Running...\nRunning...\nRunning...\n" ``` If that pod has more than one container, you must specify the container: ```ruby client.get_pod_log('pod-name', 'default', container: 'ruby') # => "..." ``` If a container in a pod terminates, a new container is started, and you want to retrieve the logs of the dead container, you can pass in the `:previous` option: ```ruby client.get_pod_log('pod-name', 'default', previous: true) # => "..." ``` Kubernetes can add timestamps to every log line or filter by lines time: ```ruby client.get_pod_log('pod-name', 'default', timestamps: true, since_time: '2018-04-27T18:30:17.480321984Z') # => "..." ``` `since_time` can be a a `Time`, `DateTime` or `String` formatted according to RFC3339 Kubernetes can fetch a specific number of lines from the end of the logs: ```ruby client.get_pod_log('pod-name', 'default', tail_lines: 10) # => "..." ``` Kubernetes can fetch a specific number of bytes from the log, but the exact size is not guaranteed and last line may not be terminated: ```ruby client.get_pod_log('pod-name', 'default', limit_bytes: 10) # => "..." ``` You can also watch the logs of a pod to get a stream of data: ```ruby client.watch_pod_log('pod-name', 'default', container: 'ruby') do |line| puts line end ``` ### OpenShift: Process a template Returns a processed template containing a list of objects to create. Input parameter - template (hash) Besides its metadata, the template should include a list of objects to be processed and a list of parameters to be substituted. Note that for a required parameter that does not provide a generated value, you must supply a value. ##### Note: This functionality is not supported by K8s at this moment. See the following [issue](https://github.com/kubernetes/kubernetes/issues/23896) ```ruby client.process_template template ``` ## Upgrading Kubeclient release versioning follows [SemVer](https://semver.org/). See [CHANGELOG.md](CHANGELOG.md) for full changelog. #### past version 4.0 Old kubernetes versions < 1.3 no longer supported. #### past version 3.0 Ruby versions < 2.2 are no longer supported Specific entity classes mentioned in [past version 1.2.0](#past_version_1.2.0) have been dropped. Return values and expected classes are always Kubeclient::Resource. Checking the type of a resource can be done using: ``` > pod.kind => "Pod" ``` update_* delete_* and patch_* now return a RecursiveOpenStruct like the get_* methods The `Kubeclient::Client` class raises `Kubeclient::HttpError` or subclasses now. Catching `KubeException` still works but is deprecated. `Kubeclient::Config#context` raises `KeyError` instead of `RuntimeError` for non-existent context name. #### past version 1.2.0 Replace Specific Entity class references: ```ruby Kubeclient::Service ``` with the generic ```ruby Kubeclient::Resource.new ``` Where ever possible. ## Contributing 1. Fork it ( https://github.com/[my-github-username]/kubeclient/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Test your changes with `rake test rubocop`, add new tests if needed. 4. If you added a new functionality, add it to README 5. Commit your changes (`git commit -am 'Add some feature'`) 6. Push to the branch (`git push origin my-new-feature`) 7. Create a new Pull Request ## Tests This client is tested with Minitest and also uses VCR recordings in some tests. Please run all tests before submitting a Pull Request, and add new tests for new functionality. Running tests: ```ruby rake test ``` kubeclient-4.11.0/RELEASING.md000066400000000000000000000035231435070561000155610ustar00rootroot00000000000000# Releasing Kubeclient ## Versioning Kubeclient release versioning follows [SemVer](https://semver.org/). At some point in time it is decided to release version x.y.z. ## 0. (once) Install gem-release, needed for several commands here: ```bash gem install gem-release ``` ## 1. PR(s) for changelog & bump ```bash RELEASE_BRANCH="master" RELEASE_VERSION=x.y.z git checkout -b "release-$RELEASE_VERSION" $RELEASE_BRANCH ``` Edit `CHANGELOG.md` as necessary. Even if all included changes remembered to update it, you should replace "Unreleased" section header with appropriate "x.y.z — 20yy-mm-dd" header. Bump `lib/kubeclient/version.rb` manually, or by using: ```bash # Won't work with uncommitted changes, you have to commit the changelog first. gem bump --version $RELEASE_VERSION git show # View version bump change. ``` Open a PR with target branch $RELEASE_BRANCH and get it reviewed & merged (if open for long, remember to update date in CHANGELOG to actual day of release). ## 2. (once) Grabbing an authentication token for rubygems.org api ```bash RUBYGEMS_USERNAME=bob curl -u $RUBYGEMS_USERNAME https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials cat ~/.gem/credentials # Should look like this: :rubygems_api_key: **** ``` ## 3. Actual release Make sure we're locally after the bump PR *merge commit*: ```bash git checkout $RELEASE_BRANCH git status # Make sure there are no local changes git pull --ff-only https://github.com/ManageIQ/kubeclient $RELEASE_BRANCH git log -n1 ``` Last sanity check: ```bash bundle install bundle exec rake test rubocop ``` Create and push the tag: ```bash gem tag --no-push git push --tags --dry-run https://github.com/abonas/kubeclient # Check for unexpected tags git push --tags https://github.com/abonas/kubeclient ``` Release onto rubygems.org: ```bash gem release ``` kubeclient-4.11.0/Rakefile000066400000000000000000000002771435070561000153760ustar00rootroot00000000000000require 'bundler/gem_tasks' require 'rake/testtask' require 'rubocop/rake_task' require 'yaml' task default: %i[test rubocop] # same as .travis.yml Rake::TestTask.new RuboCop::RakeTask.new kubeclient-4.11.0/kubeclient.gemspec000066400000000000000000000033001435070561000174110ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'kubeclient/version' Gem::Specification.new do |spec| spec.name = 'kubeclient' spec.version = Kubeclient::VERSION spec.authors = ['Alissa Bonas'] spec.email = ['abonas@redhat.com'] spec.summary = 'A client for Kubernetes REST api' spec.description = 'A client for Kubernetes REST api' spec.homepage = 'https://github.com/abonas/kubeclient' spec.license = 'MIT' git_files = `git ls-files -z`.split("\x0") spec.files = git_files.grep_v(%r{^(test|spec|features)/}) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = [] spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.7.0' spec.add_development_dependency 'bundler', '>= 1.6' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'minitest', '~> 5.15.0' spec.add_development_dependency 'minitest-rg' spec.add_development_dependency 'webmock', '~> 3.0' spec.add_development_dependency 'vcr' spec.add_development_dependency 'rubocop', '= 0.49.1' spec.add_development_dependency 'googleauth', '~> 0.5.1' spec.add_development_dependency('mocha', '~> 1.5') spec.add_development_dependency 'openid_connect', '~> 1.1' spec.add_development_dependency 'net-smtp' # needed on Windows, at least for openid_connect spec.add_development_dependency 'tzinfo-data' spec.add_dependency 'jsonpath', '~> 1.0' spec.add_dependency 'rest-client', '~> 2.0' spec.add_dependency 'recursive-open-struct', '~> 1.1', '>= 1.1.1' spec.add_dependency 'http', '>= 3.0', '< 6.0' end kubeclient-4.11.0/lib/000077500000000000000000000000001435070561000144715ustar00rootroot00000000000000kubeclient-4.11.0/lib/kubeclient.rb000066400000000000000000000014061435070561000171440ustar00rootroot00000000000000require 'json' require 'rest-client' require 'kubeclient/aws_eks_credentials' require 'kubeclient/common' require 'kubeclient/config' require 'kubeclient/entity_list' require 'kubeclient/exec_credentials' require 'kubeclient/gcp_auth_provider' require 'kubeclient/http_error' require 'kubeclient/missing_kind_compatibility' require 'kubeclient/oidc_auth_provider' require 'kubeclient/resource' require 'kubeclient/resource_not_found_error' require 'kubeclient/version' require 'kubeclient/watch_stream' module Kubeclient # Kubernetes Client class Client include ClientMixin def initialize( uri, version = 'v1', **options ) initialize_client( uri, '/api', version, **options ) end end end kubeclient-4.11.0/lib/kubeclient/000077500000000000000000000000001435070561000166165ustar00rootroot00000000000000kubeclient-4.11.0/lib/kubeclient/aws_eks_credentials.rb000066400000000000000000000032201435070561000231510ustar00rootroot00000000000000# frozen_string_literal: true module Kubeclient # Get a bearer token to authenticate against aws eks. class AmazonEksCredentials class AmazonEksDependencyError < LoadError # rubocop:disable Lint/InheritException end class << self def token(credentials, eks_cluster) begin require 'aws-sigv4' require 'base64' require 'cgi' rescue LoadError => e raise AmazonEksDependencyError, 'Error requiring aws gems. Kubeclient itself does not include the following ' \ 'gems: [aws-sigv4]. To support auth-provider eks, you must ' \ "include it in your calling application. Failed with: #{e.message}" end # https://github.com/aws/aws-sdk-ruby/pull/1848 # Get a signer # Note - sts only has ONE endpoint (not regional) so 'us-east-1' hardcoding should be OK signer = Aws::Sigv4::Signer.new( service: 'sts', region: 'us-east-1', credentials: credentials ) # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html#presign_url-instance_method presigned_url_string = signer.presign_url( http_method: 'GET', url: 'https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15', body: '', credentials: credentials, expires_in: 60, headers: { 'X-K8s-Aws-Id' => eks_cluster } ) kube_token = 'k8s-aws-v1.' + Base64.urlsafe_encode64(presigned_url_string.to_s).sub(/=*$/, '') # rubocop:disable Metrics/LineLength kube_token end end end end kubeclient-4.11.0/lib/kubeclient/common.rb000066400000000000000000000577511435070561000204520ustar00rootroot00000000000000require 'json' require 'rest-client' module Kubeclient # Common methods # this is mixed in by other gems module ClientMixin ENTITY_METHODS = %w[get watch delete create update patch json_patch merge_patch apply].freeze DEFAULT_SSL_OPTIONS = { client_cert: nil, client_key: nil, ca_file: nil, cert_store: nil, verify_ssl: OpenSSL::SSL::VERIFY_PEER }.freeze DEFAULT_AUTH_OPTIONS = { username: nil, password: nil, bearer_token: nil, bearer_token_file: nil }.freeze DEFAULT_SOCKET_OPTIONS = { socket_class: nil, ssl_socket_class: nil }.freeze DEFAULT_TIMEOUTS = { # These do NOT affect watch, watching never times out. open: Net::HTTP.new('127.0.0.1').open_timeout, # depends on ruby version read: Net::HTTP.new('127.0.0.1').read_timeout }.freeze DEFAULT_HTTP_PROXY_URI = nil DEFAULT_HTTP_MAX_REDIRECTS = 10 SEARCH_ARGUMENTS = { 'labelSelector' => :label_selector, 'fieldSelector' => :field_selector, 'resourceVersion' => :resource_version, 'limit' => :limit, 'continue' => :continue }.freeze WATCH_ARGUMENTS = { 'labelSelector' => :label_selector, 'fieldSelector' => :field_selector, 'resourceVersion' => :resource_version }.freeze attr_reader :api_endpoint attr_reader :ssl_options attr_reader :auth_options attr_reader :http_proxy_uri attr_reader :http_max_redirects attr_reader :headers attr_reader :discovered def initialize_client( uri, path, version, ssl_options: DEFAULT_SSL_OPTIONS, auth_options: DEFAULT_AUTH_OPTIONS, socket_options: DEFAULT_SOCKET_OPTIONS, timeouts: DEFAULT_TIMEOUTS, http_proxy_uri: DEFAULT_HTTP_PROXY_URI, http_max_redirects: DEFAULT_HTTP_MAX_REDIRECTS, as: :ros ) validate_auth_options(auth_options) handle_uri(uri, path) @entities = {} @discovered = false @api_version = version @headers = {} @ssl_options = ssl_options @auth_options = auth_options.dup @socket_options = socket_options # Allow passing partial timeouts hash, without unspecified # @timeouts[:foo] == nil resulting in infinite timeout. @timeouts = DEFAULT_TIMEOUTS.merge(timeouts) @http_proxy_uri = http_proxy_uri ? http_proxy_uri.to_s : nil @http_max_redirects = http_max_redirects @as = as if auth_options[:bearer_token_file] validate_bearer_token_file bearer_token(File.read(@auth_options[:bearer_token_file])) elsif auth_options[:bearer_token] bearer_token(@auth_options[:bearer_token]) end end def method_missing(method_sym, *args, &block) if discovery_needed?(method_sym) discover send(method_sym, *args, &block) else super end end def respond_to_missing?(method_sym, include_private = false) if discovery_needed?(method_sym) discover respond_to?(method_sym, include_private) else super end end def discovery_needed?(method_sym) !@discovered && ENTITY_METHODS.any? { |x| method_sym.to_s.start_with?(x) } end def handle_exception yield rescue RestClient::Exception => e json_error_msg = begin JSON.parse(e.response || '') || {} rescue JSON::ParserError {} end err_message = json_error_msg['message'] || e.message error_klass = e.http_code == 404 ? ResourceNotFoundError : HttpError raise error_klass.new(e.http_code, err_message, e.response) end def discover load_entities define_entity_methods @discovered = true end def get_headers bearer_token(File.read(@auth_options[:bearer_token_file])) if @auth_options[:bearer_token_file] @headers end def self.parse_definition(kind, name) # Kubernetes gives us 3 inputs: # kind: "ComponentStatus", "NetworkPolicy", "Endpoints" # name: "componentstatuses", "networkpolicies", "endpoints" # singularName: "componentstatus" etc (usually omitted, defaults to kind.downcase) # and want to derive singular and plural method names, with underscores: # "network_policy" # "network_policies" # kind's CamelCase word boundaries determine our placement of underscores. if IRREGULAR_NAMES[kind] # In a few cases, the given kind / singularName itself is still plural. # We require a distinct singular method name, so force it. method_names = IRREGULAR_NAMES[kind] else # TODO: respect singularName from discovery? # But how? If it differs from kind.downcase, kind's word boundaries don't apply. singular_name = kind.downcase if !(/[A-Z]/ =~ kind) # Some custom resources have a fully lowercase kind - can't infer underscores. method_names = [singular_name, name] else # Some plurals are not exact suffixes, e.g. NetworkPolicy -> networkpolicies. # So don't expect full last word to match. /^(?(.*[A-Z]))(?[^A-Z]*)$/ =~ kind # "NetworkP", "olicy" if name.start_with?(prefix.downcase) plural_suffix = name[prefix.length..-1] # "olicies" prefix_underscores = ClientMixin.underscore_entity(prefix) # "network_p" method_names = [prefix_underscores + singular_suffix, # "network_policy" prefix_underscores + plural_suffix] # "network_policies" else method_names = resolve_unconventional_method_names(name, kind, singular_name) end end end OpenStruct.new( entity_type: kind, resource_name: name, method_names: method_names ) end def self.resolve_unconventional_method_names(name, kind, singular_name) underscored_name = name.tr('-', '_') singular_underscores = ClientMixin.underscore_entity(kind) if underscored_name.start_with?(singular_underscores) [singular_underscores, underscored_name] else # fallback to lowercase, no separators for both names [singular_name, underscored_name.tr('_', '')] end end def handle_uri(uri, path) raise ArgumentError, 'Missing uri' unless uri @api_endpoint = (uri.is_a?(URI) ? uri : URI.parse(uri)) # This regex will anchor at the last `/api`, `/oapi` or`/apis/:group`) part of the URL # The whole path will be matched and if existing, the api_group will be extracted. re = /^(?.*\/o?api(?:s\/(?[^\/]+))?)$/mi match = re.match(@api_endpoint.path.chomp('/')) if match # Since `re` captures 2 groups, match will always have 3 elements # If thus we have a non-nil value in match 2, this is our api_group. @api_group = match[:apigroup].nil? ? '' : match[:apigroup] + '/' @api_endpoint.path = match[:path] else # This is a fallback, for when `/api` was not provided as part of the uri @api_group = '' @api_endpoint.path = @api_endpoint.path.chomp('/') + path end end def build_namespace_prefix(namespace) namespace.to_s.empty? ? '' : "namespaces/#{namespace}/" end # rubocop:disable Metrics/BlockLength def define_entity_methods @entities.values.each do |entity| # get all entities of a type e.g. get_nodes, get_pods, etc. define_singleton_method("get_#{entity.method_names[1]}") do |options = {}| get_entities(entity.entity_type, entity.resource_name, options) end # watch all entities of a type e.g. watch_nodes, watch_pods, etc. define_singleton_method("watch_#{entity.method_names[1]}") do |options = {}, &block| # This method used to take resource_version as a param, so # this conversion is to keep backwards compatibility options = { resource_version: options } unless options.is_a?(Hash) watch_entities(entity.resource_name, options, &block) end # get a single entity of a specific type by name define_singleton_method("get_#{entity.method_names[0]}") \ do |name, namespace = nil, opts = {}| get_entity(entity.resource_name, name, namespace, opts) end define_singleton_method("delete_#{entity.method_names[0]}") \ do |name, namespace = nil, opts = {}| delete_entity(entity.resource_name, name, namespace, **opts) end define_singleton_method("create_#{entity.method_names[0]}") do |entity_config| create_entity(entity.entity_type, entity.resource_name, entity_config) end define_singleton_method("update_#{entity.method_names[0]}") do |entity_config| update_entity(entity.resource_name, entity_config) end define_singleton_method("patch_#{entity.method_names[0]}") \ do |name, patch, namespace = nil| patch_entity(entity.resource_name, name, patch, 'strategic-merge-patch', namespace) end define_singleton_method("json_patch_#{entity.method_names[0]}") \ do |name, patch, namespace = nil| patch_entity(entity.resource_name, name, patch, 'json-patch', namespace) end define_singleton_method("merge_patch_#{entity.method_names[0]}") \ do |name, patch, namespace = nil| patch_entity(entity.resource_name, name, patch, 'merge-patch', namespace) end define_singleton_method("apply_#{entity.method_names[0]}") do |resource, opts = {}| apply_entity(entity.resource_name, resource, **opts) end end end # rubocop:enable Metrics/BlockLength def self.underscore_entity(entity_name) entity_name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase end def create_rest_client(path = nil) path ||= @api_endpoint.path options = { ssl_ca_file: @ssl_options[:ca_file], ssl_cert_store: @ssl_options[:cert_store], verify_ssl: @ssl_options[:verify_ssl], ssl_client_cert: @ssl_options[:client_cert], ssl_client_key: @ssl_options[:client_key], proxy: @http_proxy_uri, max_redirects: @http_max_redirects, user: @auth_options[:username], password: @auth_options[:password], open_timeout: @timeouts[:open], read_timeout: @timeouts[:read] } RestClient::Resource.new(@api_endpoint.merge(path).to_s, options) end def rest_client @rest_client ||= begin create_rest_client("#{@api_endpoint.path}/#{@api_version}") end end # Accepts the following options: # :namespace (string) - the namespace of the entity. # :name (string) - the name of the entity to watch. # :label_selector (string) - a selector to restrict the list of returned objects by labels. # :field_selector (string) - a selector to restrict the list of returned objects by fields. # :resource_version (string) - shows changes that occur after passed version of a resource. # :as (:raw|:ros) - defaults to :ros # :raw - return the raw response body as a string # :ros - return a collection of RecursiveOpenStruct objects # Accepts an optional block, that will be called with each entity, # otherwise returns a WatchStream def watch_entities(resource_name, options = {}, &block) ns = build_namespace_prefix(options[:namespace]) path = "watch/#{ns}#{resource_name}" path += "/#{options[:name]}" if options[:name] uri = @api_endpoint.merge("#{@api_endpoint.path}/#{@api_version}/#{path}") params = {} WATCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] } uri.query = URI.encode_www_form(params) if params.any? watcher = Kubeclient::Common::WatchStream.new( uri, http_options(uri), formatter: ->(value) { format_response(options[:as] || @as, value) } ) return_or_yield_to_watcher(watcher, &block) end # Accepts the following options: # :namespace (string) - the namespace of the entity. # :label_selector (string) - a selector to restrict the list of returned objects by labels. # :field_selector (string) - a selector to restrict the list of returned objects by fields. # :limit (integer) - a maximum number of items to return in each response # :continue (string) - a token used to retrieve the next chunk of entities # :as (:raw|:ros) - defaults to :ros # :raw - return the raw response body as a string # :ros - return a collection of RecursiveOpenStruct objects def get_entities(entity_type, resource_name, options = {}) params = {} SEARCH_ARGUMENTS.each { |k, v| params[k] = options[v] if options[v] } ns_prefix = build_namespace_prefix(options[:namespace]) response = handle_exception do rest_client[ns_prefix + resource_name] .get({ 'params' => params }.merge(get_headers)) end format_response(options[:as] || @as, response.body, entity_type) end # Accepts the following options: # :as (:raw|:ros) - defaults to :ros # :raw - return the raw response body as a string # :ros - return a collection of RecursiveOpenStruct objects def get_entity(resource_name, name, namespace = nil, options = {}) ns_prefix = build_namespace_prefix(namespace) response = handle_exception do rest_client[ns_prefix + resource_name + "/#{name}"] .get(get_headers) end format_response(options[:as] || @as, response.body) end # delete_options are passed as a JSON payload in the delete request def delete_entity(resource_name, name, namespace = nil, delete_options: {}) delete_options_hash = delete_options.to_hash ns_prefix = build_namespace_prefix(namespace) payload = delete_options_hash.to_json unless delete_options_hash.empty? response = handle_exception do rs = rest_client[ns_prefix + resource_name + "/#{name}"] RestClient::Request.execute( rs.options.merge( method: :delete, url: rs.url, headers: { 'Content-Type' => 'application/json' }.merge(get_headers), payload: payload ) ) end format_response(@as, response.body) end def create_entity(entity_type, resource_name, entity_config) # Duplicate the entity_config to a hash so that when we assign # kind and apiVersion, this does not mutate original entity_config obj. hash = entity_config.to_hash ns_prefix = build_namespace_prefix(hash[:metadata][:namespace]) # TODO: temporary solution to add "kind" and apiVersion to request # until this issue is solved # https://github.com/GoogleCloudPlatform/kubernetes/issues/6439 hash[:kind] = entity_type hash[:apiVersion] = @api_group + @api_version response = handle_exception do rest_client[ns_prefix + resource_name] .post(hash.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers)) end format_response(@as, response.body) end def update_entity(resource_name, entity_config) name = entity_config[:metadata][:name] ns_prefix = build_namespace_prefix(entity_config[:metadata][:namespace]) response = handle_exception do rest_client[ns_prefix + resource_name + "/#{name}"] .put(entity_config.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers)) end format_response(@as, response.body) end def patch_entity(resource_name, name, patch, strategy, namespace) ns_prefix = build_namespace_prefix(namespace) response = handle_exception do rest_client[ns_prefix + resource_name + "/#{name}"] .patch( patch.to_json, { 'Content-Type' => "application/#{strategy}+json" }.merge(get_headers) ) end format_response(@as, response.body) end def apply_entity(resource_name, resource, field_manager:, force: true) name = "#{resource[:metadata][:name]}?fieldManager=#{field_manager}&force=#{force}" ns_prefix = build_namespace_prefix(resource[:metadata][:namespace]) response = handle_exception do rest_client[ns_prefix + resource_name + "/#{name}"] .patch( resource.to_json, { 'Content-Type' => 'application/apply-patch+yaml' }.merge(get_headers) ) end format_response(@as, response.body) end def all_entities(options = {}) discover unless @discovered @entities.values.each_with_object({}) do |entity, result_hash| # method call for get each entities # build hash of entity name to array of the entities method_name = "get_#{entity.method_names[1]}" begin result_hash[entity.method_names[0]] = send(method_name, options) rescue Kubeclient::HttpError next # do not fail due to resources not supporting get end end end def get_pod_log(pod_name, namespace, container: nil, previous: false, timestamps: false, since_time: nil, tail_lines: nil, limit_bytes: nil) params = {} params[:previous] = true if previous params[:container] = container if container params[:timestamps] = timestamps if timestamps params[:sinceTime] = format_datetime(since_time) if since_time params[:tailLines] = tail_lines if tail_lines params[:limitBytes] = limit_bytes if limit_bytes ns = build_namespace_prefix(namespace) handle_exception do rest_client[ns + "pods/#{pod_name}/log"] .get({ 'params' => params }.merge(get_headers)) end end def watch_pod_log(pod_name, namespace, container: nil, &block) # Adding the "follow=true" query param tells the Kubernetes API to keep # the connection open and stream updates to the log. params = { follow: true } params[:container] = container if container ns = build_namespace_prefix(namespace) uri = @api_endpoint.dup uri.path += "/#{@api_version}/#{ns}pods/#{pod_name}/log" uri.query = URI.encode_www_form(params) watcher = Kubeclient::Common::WatchStream.new( uri, http_options(uri), formatter: ->(value) { value } ) return_or_yield_to_watcher(watcher, &block) end def proxy_url(kind, name, port, namespace = '') discover unless @discovered entity_name_plural = if %w[services pods nodes].include?(kind.to_s) kind.to_s else @entities[kind.to_s].resource_name end ns_prefix = build_namespace_prefix(namespace) rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url end def process_template(template) ns_prefix = build_namespace_prefix(template[:metadata][:namespace]) response = handle_exception do rest_client[ns_prefix + 'processedtemplates'] .post(template.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(get_headers)) end JSON.parse(response) end def api_valid? result = api result.is_a?(Hash) && (result['versions'] || []).any? do |group| @api_group.empty? ? group.include?(@api_version) : group['version'] == @api_version end end def api response = handle_exception { create_rest_client.get(get_headers) } JSON.parse(response) end private IRREGULAR_NAMES = { # In a few cases, the given kind itself is still plural. # https://github.com/kubernetes/kubernetes/issues/8115 'Endpoints' => %w[endpoint endpoints], 'SecurityContextConstraints' => %w[security_context_constraint security_context_constraints] }.freeze # Format datetime according to RFC3339 def format_datetime(value) case value when DateTime, Time value.strftime('%FT%T.%9N%:z') when String value else raise ArgumentError, "unsupported type '#{value.class}' of time value '#{value}'" end end def format_response(as, body, list_type = nil) case as when :raw body when :parsed JSON.parse(body) when :parsed_symbolized JSON.parse(body, symbolize_names: true) when :ros result = JSON.parse(body) if list_type resource_version = result.fetch('resourceVersion') do result.fetch('metadata', {}).fetch('resourceVersion', nil) end # If 'limit' was passed save the continue token # see https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks continue = result.fetch('metadata', {}).fetch('continue', nil) # result['items'] might be nil due to https://github.com/kubernetes/kubernetes/issues/13096 collection = result['items'].to_a.map { |item| Kubeclient::Resource.new(item) } Kubeclient::Common::EntityList.new(list_type, resource_version, collection, continue) else Kubeclient::Resource.new(result) end else raise ArgumentError, "Unsupported format #{as.inspect}" end end def load_entities @entities = {} fetch_entities['resources'].each do |resource| next if resource['name'].include?('/') # Not a regular entity, special functionality covered by `process_template`. # https://github.com/openshift/origin/issues/21668 next if resource['kind'] == 'Template' && resource['name'] == 'processedtemplates' resource['kind'] ||= Kubeclient::Common::MissingKindCompatibility.resource_kind(resource['name']) entity = ClientMixin.parse_definition(resource['kind'], resource['name']) @entities[entity.method_names[0]] = entity if entity end end def fetch_entities JSON.parse(handle_exception { rest_client.get(get_headers) }) end def bearer_token(bearer_token) @headers ||= {} @headers[:Authorization] = "Bearer #{bearer_token}" end def validate_auth_options(opts) # maintain backward compatibility: opts[:username] = opts[:user] if opts[:user] if %i[bearer_token bearer_token_file username].count { |key| opts[key] } > 1 raise( ArgumentError, 'Invalid auth options: specify only one of username/password,' \ ' bearer_token or bearer_token_file' ) elsif %i[username password].count { |key| opts[key] } == 1 raise ArgumentError, 'Basic auth requires both username & password' end end def validate_bearer_token_file msg = "Token file #{@auth_options[:bearer_token_file]} does not exist" raise ArgumentError, msg unless File.file?(@auth_options[:bearer_token_file]) msg = "Cannot read token file #{@auth_options[:bearer_token_file]}" raise ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file]) end def return_or_yield_to_watcher(watcher, &block) return watcher unless block_given? begin watcher.each(&block) ensure watcher.finish end end def http_options(uri) options = { basic_auth_user: @auth_options[:username], basic_auth_password: @auth_options[:password], headers: get_headers, http_proxy_uri: @http_proxy_uri, http_max_redirects: http_max_redirects } options[:bearer_token_file] = @auth_options[:bearer_token_file] if @auth_options[:bearer_token_file] if uri.scheme == 'https' options[:ssl] = { ca_file: @ssl_options[:ca_file], cert: @ssl_options[:client_cert], cert_store: @ssl_options[:cert_store], key: @ssl_options[:client_key], # ruby HTTP uses verify_mode instead of verify_ssl # http://ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html verify_mode: @ssl_options[:verify_ssl] } end options.merge(@socket_options) end end end kubeclient-4.11.0/lib/kubeclient/config.rb000066400000000000000000000150151435070561000204120ustar00rootroot00000000000000require 'yaml' require 'base64' require 'pathname' module Kubeclient # Kubernetes client configuration class class Config # Kubernetes client configuration context class class Context attr_reader :api_endpoint, :api_version, :ssl_options, :auth_options, :namespace def initialize(api_endpoint, api_version, ssl_options, auth_options, namespace) @api_endpoint = api_endpoint @api_version = api_version @ssl_options = ssl_options @auth_options = auth_options @namespace = namespace end end # data (Hash) - Parsed kubeconfig data. # kcfg_path (string) - Base directory for resolving relative references to external files. # If set to nil, all external lookups & commands are disabled (even for absolute paths). # See also the more convenient Config.read def initialize(data, kcfg_path) @kcfg = data @kcfg_path = kcfg_path raise 'Unknown kubeconfig version' if @kcfg['apiVersion'] != 'v1' end # Builds Config instance by parsing given file, with lookups relative to file's directory. def self.read(filename) parsed = if RUBY_VERSION >= '2.6' YAML.safe_load(File.read(filename), permitted_classes: [Date, Time]) else YAML.safe_load(File.read(filename), [Date, Time]) end Config.new(parsed, File.dirname(filename)) end def contexts @kcfg['contexts'].map { |x| x['name'] } end def context(context_name = nil) cluster, user, namespace = fetch_context(context_name || @kcfg['current-context']) if user.key?('exec') exec_opts = expand_command_option(user['exec'], 'command') user['exec_result'] = ExecCredentials.run(exec_opts) end client_cert_data = fetch_user_cert_data(user) client_key_data = fetch_user_key_data(user) auth_options = fetch_user_auth_options(user) ssl_options = {} ssl_options[:verify_ssl] = if cluster['insecure-skip-tls-verify'] == true OpenSSL::SSL::VERIFY_NONE else OpenSSL::SSL::VERIFY_PEER end if cluster_ca_data?(cluster) cert_store = OpenSSL::X509::Store.new populate_cert_store_from_cluster_ca_data(cluster, cert_store) ssl_options[:cert_store] = cert_store end unless client_cert_data.nil? ssl_options[:client_cert] = OpenSSL::X509::Certificate.new(client_cert_data) end unless client_key_data.nil? ssl_options[:client_key] = OpenSSL::PKey.read(client_key_data) end Context.new(cluster['server'], @kcfg['apiVersion'], ssl_options, auth_options, namespace) end private def allow_external_lookups? @kcfg_path != nil end def ext_file_path(path) unless allow_external_lookups? raise "Kubeclient::Config: external lookups disabled, can't load '#{path}'" end Pathname(path).absolute? ? path : File.join(@kcfg_path, path) end def ext_command_path(path) unless allow_external_lookups? raise "Kubeclient::Config: external lookups disabled, can't execute '#{path}'" end # Like go client https://github.com/kubernetes/kubernetes/pull/59495#discussion_r171138995, # distinguish 3 cases: # - absolute (e.g. /path/to/foo) # - $PATH-based (e.g. curl) # - relative to config file's dir (e.g. ./foo) if Pathname(path).absolute? path elsif File.basename(path) == path path else File.join(@kcfg_path, path) end end def fetch_context(context_name) context = @kcfg['contexts'].detect do |x| break x['context'] if x['name'] == context_name end raise KeyError, "Unknown context #{context_name}" unless context cluster = @kcfg['clusters'].detect do |x| break x['cluster'] if x['name'] == context['cluster'] end raise KeyError, "Unknown cluster #{context['cluster']}" unless cluster user = @kcfg['users'].detect do |x| break x['user'] if x['name'] == context['user'] end || {} namespace = context['namespace'] [cluster, user, namespace] end def cluster_ca_data?(cluster) cluster.key?('certificate-authority') || cluster.key?('certificate-authority-data') end def populate_cert_store_from_cluster_ca_data(cluster, cert_store) if cluster.key?('certificate-authority') cert_store.add_file(ext_file_path(cluster['certificate-authority'])) elsif cluster.key?('certificate-authority-data') ca_cert_data = Base64.decode64(cluster['certificate-authority-data']) cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_cert_data)) end end def fetch_user_cert_data(user) if user.key?('client-certificate') File.read(ext_file_path(user['client-certificate'])) elsif user.key?('client-certificate-data') Base64.decode64(user['client-certificate-data']) elsif user.key?('exec_result') && user['exec_result'].key?('clientCertificateData') user['exec_result']['clientCertificateData'] end end def fetch_user_key_data(user) if user.key?('client-key') File.read(ext_file_path(user['client-key'])) elsif user.key?('client-key-data') Base64.decode64(user['client-key-data']) elsif user.key?('exec_result') && user['exec_result'].key?('clientKeyData') user['exec_result']['clientKeyData'] end end def fetch_user_auth_options(user) options = {} if user.key?('token') options[:bearer_token] = user['token'] elsif user.key?('exec_result') && user['exec_result'].key?('token') options[:bearer_token] = user['exec_result']['token'] elsif user.key?('auth-provider') options[:bearer_token] = fetch_token_from_provider(user['auth-provider']) else %w[username password].each do |attr| options[attr.to_sym] = user[attr] if user.key?(attr) end end options end def fetch_token_from_provider(auth_provider) case auth_provider['name'] when 'gcp' config = expand_command_option(auth_provider['config'], 'cmd-path') Kubeclient::GCPAuthProvider.token(config) when 'oidc' Kubeclient::OIDCAuthProvider.token(auth_provider['config']) end end def expand_command_option(config, key) config = config.dup config[key] = ext_command_path(config[key]) if config[key] config end end end kubeclient-4.11.0/lib/kubeclient/entity_list.rb000066400000000000000000000007431435070561000215160ustar00rootroot00000000000000require 'delegate' module Kubeclient module Common # Kubernetes Entity List class EntityList < DelegateClass(Array) attr_reader :continue, :kind, :resourceVersion def initialize(kind, resource_version, list, continue = nil) @kind = kind # rubocop:disable Style/VariableName @resourceVersion = resource_version @continue = continue super(list) end def last? continue.nil? end end end end kubeclient-4.11.0/lib/kubeclient/exec_credentials.rb000066400000000000000000000051641435070561000224520ustar00rootroot00000000000000# frozen_string_literal: true module Kubeclient # An exec-based client auth provide # https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration # Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go class ExecCredentials class << self def run(opts) require 'open3' require 'json' raise ArgumentError, 'exec options are required' if opts.nil? cmd = opts['command'] args = opts['args'] env = map_env(opts['env']) # Validate exec options validate_opts(opts) out, err, st = Open3.capture3(env, cmd, *args) raise "exec command failed: #{err}" unless st.success? creds = JSON.parse(out) validate_credentials(opts, creds) creds['status'] end private def validate_opts(opts) raise KeyError, 'exec command is required' unless opts['command'] end def validate_client_credentials_status(status) has_client_cert_data = status.key?('clientCertificateData') has_client_key_data = status.key?('clientKeyData') if has_client_cert_data && !has_client_key_data raise 'exec plugin didn\'t return client key data' end if !has_client_cert_data && has_client_key_data raise 'exec plugin didn\'t return client certificate data' end has_client_cert_data && has_client_key_data end def validate_credentials_status(status) raise 'exec plugin didn\'t return a status field' if status.nil? has_client_credentials = validate_client_credentials_status(status) has_token = status.key?('token') if has_client_credentials && has_token raise 'exec plugin returned both token and client data' end return if has_client_credentials || has_token raise 'exec plugin didn\'t return a token or client data' unless has_token end def validate_credentials(opts, creds) # out should have ExecCredential structure raise 'invalid credentials' if creds.nil? # Verify apiVersion? api_version = opts['apiVersion'] if api_version && api_version != creds['apiVersion'] raise "exec plugin is configured to use API version #{api_version}, " \ "plugin returned version #{creds['apiVersion']}" end validate_credentials_status(creds['status']) end # Transform name/value pairs to hash def map_env(env) return {} unless env Hash[env.map { |e| [e['name'], e['value']] }] end end end end kubeclient-4.11.0/lib/kubeclient/gcp_auth_provider.rb000066400000000000000000000007721435070561000226550ustar00rootroot00000000000000# frozen_string_literal: true require 'kubeclient/google_application_default_credentials' require 'kubeclient/gcp_command_credentials' module Kubeclient # Handle different ways to get a bearer token for Google Cloud Platform. class GCPAuthProvider class << self def token(config) if config.key?('cmd-path') Kubeclient::GCPCommandCredentials.token(config) else Kubeclient::GoogleApplicationDefaultCredentials.token end end end end end kubeclient-4.11.0/lib/kubeclient/gcp_command_credentials.rb000066400000000000000000000013111435070561000237630ustar00rootroot00000000000000# frozen_string_literal: true module Kubeclient # Generates a bearer token for Google Cloud Platform. class GCPCommandCredentials class << self def token(config) require 'open3' require 'shellwords' require 'json' require 'jsonpath' cmd = config['cmd-path'] args = config['cmd-args'] token_key = config['token-key'] out, err, st = Open3.capture3(cmd, *args.split(' ')) raise "exec command failed: #{err}" unless st.success? extract_token(out, token_key) end private def extract_token(output, token_key) JsonPath.on(output, token_key.gsub(/^{|}$/, '')).first end end end end kubeclient-4.11.0/lib/kubeclient/google_application_default_credentials.rb000066400000000000000000000017321435070561000270660ustar00rootroot00000000000000# frozen_string_literal: true module Kubeclient # Get a bearer token from the Google's application default credentials. class GoogleApplicationDefaultCredentials class GoogleDependencyError < LoadError # rubocop:disable Lint/InheritException end class << self def token begin require 'googleauth' rescue LoadError => e raise GoogleDependencyError, 'Error requiring googleauth gem. Kubeclient itself does not include the ' \ 'googleauth gem. To support auth-provider gcp, you must include it in your ' \ "calling application. Failed with: #{e.message}" end scopes = [ 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/userinfo.email' ] authorization = Google::Auth.get_application_default(scopes) authorization.apply({}) authorization.access_token end end end end kubeclient-4.11.0/lib/kubeclient/http_error.rb000066400000000000000000000012211435070561000213270ustar00rootroot00000000000000# TODO: remove this on next major version bump # Deprected http exception class KubeException < StandardError attr_reader :error_code, :message, :response def initialize(error_code, message, response) @error_code = error_code @message = message @response = response end def to_s string = "HTTP status code #{@error_code}, #{@message}" if @response.is_a?(RestClient::Response) && @response.request string << " for #{@response.request.method.upcase} #{@response.request.url}" end string end end module Kubeclient # Exception that is raised when a http request fails class HttpError < KubeException end end kubeclient-4.11.0/lib/kubeclient/missing_kind_compatibility.rb000066400000000000000000000065201435070561000245550ustar00rootroot00000000000000module Kubeclient module Common # Backward compatibility for old versions where kind is missing (e.g. OpenShift Enterprise 3.1) class MissingKindCompatibility MAPPING = { 'bindings' => 'Binding', 'componentstatuses' => 'ComponentStatus', 'endpoints' => 'Endpoints', 'events' => 'Event', 'limitranges' => 'LimitRange', 'namespaces' => 'Namespace', 'nodes' => 'Node', 'persistentvolumeclaims' => 'PersistentVolumeClaim', 'persistentvolumes' => 'PersistentVolume', 'pods' => 'Pod', 'podtemplates' => 'PodTemplate', 'replicationcontrollers' => 'ReplicationController', 'resourcequotas' => 'ResourceQuota', 'secrets' => 'Secret', 'securitycontextconstraints' => 'SecurityContextConstraints', 'serviceaccounts' => 'ServiceAccount', 'services' => 'Service', 'buildconfigs' => 'BuildConfig', 'builds' => 'Build', 'clusternetworks' => 'ClusterNetwork', 'clusterpolicies' => 'ClusterPolicy', 'clusterpolicybindings' => 'ClusterPolicyBinding', 'clusterrolebindings' => 'ClusterRoleBinding', 'clusterroles' => 'ClusterRole', 'deploymentconfigrollbacks' => 'DeploymentConfigRollback', 'deploymentconfigs' => 'DeploymentConfig', 'generatedeploymentconfigs' => 'DeploymentConfig', 'groups' => 'Group', 'hostsubnets' => 'HostSubnet', 'identities' => 'Identity', 'images' => 'Image', 'imagestreamimages' => 'ImageStreamImage', 'imagestreammappings' => 'ImageStreamMapping', 'imagestreams' => 'ImageStream', 'imagestreamtags' => 'ImageStreamTag', 'localresourceaccessreviews' => 'LocalResourceAccessReview', 'localsubjectaccessreviews' => 'LocalSubjectAccessReview', 'netnamespaces' => 'NetNamespace', 'oauthaccesstokens' => 'OAuthAccessToken', 'oauthauthorizetokens' => 'OAuthAuthorizeToken', 'oauthclientauthorizations' => 'OAuthClientAuthorization', 'oauthclients' => 'OAuthClient', 'policies' => 'Policy', 'policybindings' => 'PolicyBinding', 'processedtemplates' => 'Template', 'projectrequests' => 'ProjectRequest', 'projects' => 'Project', 'resourceaccessreviews' => 'ResourceAccessReview', 'rolebindings' => 'RoleBinding', 'roles' => 'Role', 'routes' => 'Route', 'subjectaccessreviews' => 'SubjectAccessReview', 'templates' => 'Template', 'useridentitymappings' => 'UserIdentityMapping', 'users' => 'User' }.freeze def self.resource_kind(name) MAPPING[name] end end end end kubeclient-4.11.0/lib/kubeclient/oidc_auth_provider.rb000066400000000000000000000036151435070561000230210ustar00rootroot00000000000000# frozen_string_literal: true module Kubeclient # Uses OIDC id-tokens and refreshes them if they are stale. class OIDCAuthProvider class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException end class << self def token(provider_config) begin require 'openid_connect' rescue LoadError => e raise OpenIDConnectDependencyError, 'Error requiring openid_connect gem. Kubeclient itself does not include the ' \ 'openid_connect gem. To support auth-provider oidc, you must include it in your ' \ "calling application. Failed with: #{e.message}" end issuer_url = provider_config['idp-issuer-url'] discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url if provider_config.key? 'id-token' return provider_config['id-token'] unless expired?(provider_config['id-token'], discovery) end client = OpenIDConnect::Client.new( identifier: provider_config['client-id'], secret: provider_config['client-secret'], authorization_endpoint: discovery.authorization_endpoint, token_endpoint: discovery.token_endpoint, userinfo_endpoint: discovery.userinfo_endpoint ) client.refresh_token = provider_config['refresh-token'] client.access_token!.id_token end def expired?(id_token, discovery) decoded_token = OpenIDConnect::ResponseObject::IdToken.decode( id_token, discovery.jwks ) # If token expired or expiring within 60 seconds Time.now.to_i + 60 > decoded_token.exp.to_i rescue JSON::JWK::Set::KidNotFound # Token cannot be verified: the kid it was signed with is not available for discovery # Consider it expired and fetch a new one. true end end end end kubeclient-4.11.0/lib/kubeclient/resource.rb000066400000000000000000000004141435070561000207710ustar00rootroot00000000000000require 'recursive_open_struct' module Kubeclient # Represents all the objects returned by Kubeclient class Resource < RecursiveOpenStruct def initialize(hash = nil, args = {}) args[:recurse_over_arrays] = true super(hash, args) end end end kubeclient-4.11.0/lib/kubeclient/resource_not_found_error.rb000066400000000000000000000001061435070561000242530ustar00rootroot00000000000000module Kubeclient class ResourceNotFoundError < HttpError end end kubeclient-4.11.0/lib/kubeclient/version.rb000066400000000000000000000001171435070561000206270ustar00rootroot00000000000000# Kubernetes REST-API Client module Kubeclient VERSION = '4.11.0'.freeze end kubeclient-4.11.0/lib/kubeclient/watch_stream.rb000066400000000000000000000052621435070561000216310ustar00rootroot00000000000000require 'json' require 'http' module Kubeclient module Common # HTTP Stream used to watch changes on entities class WatchStream def initialize(uri, http_options, formatter:) @uri = uri @http_client = nil @http_options = http_options @http_options[:http_max_redirects] ||= Kubeclient::Client::DEFAULT_HTTP_MAX_REDIRECTS @formatter = formatter end def each @finished = false @http_client = build_client response = @http_client.request(:get, @uri, build_client_options) unless response.code < 300 raise Kubeclient::HttpError.new(response.code, response.reason, response) end buffer = '' response.body.each do |chunk| buffer << chunk while (line = buffer.slice!(/.+\n/)) yield @formatter.call(line.chomp) end end rescue StandardError raise unless @finished end def finish @finished = true @http_client.close unless @http_client.nil? end private def max_hops @http_options[:http_max_redirects] + 1 end def follow_option if max_hops > 1 { max_hops: max_hops } else # i.e. Do not follow redirects as we have set http_max_redirects to 0 # Setting `{ max_hops: 1 }` does not work FWIW false end end def build_client client = HTTP::Client.new(follow: follow_option) if @http_options[:basic_auth_user] && @http_options[:basic_auth_password] client = client.basic_auth( user: @http_options[:basic_auth_user], pass: @http_options[:basic_auth_password] ) end client end def using_proxy proxy = @http_options[:http_proxy_uri] return nil unless proxy p_uri = URI.parse(proxy) { proxy_address: p_uri.hostname, proxy_port: p_uri.port, proxy_username: p_uri.user, proxy_password: p_uri.password } end def build_client_options @http_options[:headers][:Authorization] = "Bearer #{File.read(@http_options[:bearer_token_file])}" if @http_options[:bearer_token_file] client_options = { headers: @http_options[:headers], proxy: using_proxy } if @http_options[:ssl] client_options[:ssl] = @http_options[:ssl] socket_option = :ssl_socket_class else socket_option = :socket_class end client_options[socket_option] = @http_options[socket_option] if @http_options[socket_option] client_options end end end end kubeclient-4.11.0/test/000077500000000000000000000000001435070561000147025ustar00rootroot00000000000000kubeclient-4.11.0/test/cassettes/000077500000000000000000000000001435070561000167005ustar00rootroot00000000000000kubeclient-4.11.0/test/cassettes/kubernetes_guestbook.yml000066400000000000000000001007201435070561000236540ustar00rootroot00000000000000--- http_interactions: - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '253' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "namespaces \"kubeclient-ns\" not found", "reason": "NotFound", "details": { "name": "kubeclient-ns", "kind": "namespaces" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/guestbook body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '239' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "service \"guestbook\" not found", "reason": "NotFound", "details": { "name": "guestbook", "kind": "service" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/redis-master body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '245' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "service \"redis-master\" not found", "reason": "NotFound", "details": { "name": "redis-master", "kind": "service" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/redis-slave body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '243' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "service \"redis-slave\" not found", "reason": "NotFound", "details": { "name": "redis-slave", "kind": "service" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/guestbook body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '269' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "replicationControllers \"guestbook\" not found", "reason": "NotFound", "details": { "name": "guestbook", "kind": "replicationControllers" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-master body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '275' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "replicationControllers \"redis-master\" not found", "reason": "NotFound", "details": { "name": "redis-master", "kind": "replicationControllers" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-slave body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 404 message: Not Found headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '273' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "replicationControllers \"redis-slave\" not found", "reason": "NotFound", "details": { "name": "redis-slave", "kind": "replicationControllers" }, "code": 404 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces body: encoding: UTF-8 string: '{"metadata":{"name":"kubeclient-ns"},"kind":"Namespace","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '74' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '297' body: encoding: UTF-8 string: '{"kind":"Namespace","apiVersion":"v1","metadata":{"name":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns","uid":"f41e6b27-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"534","creationTimestamp":"2015-08-09T10:03:59Z"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"name":"guestbook"},"name":"guestbook"},"spec":{"selector":{"app":"guestbook"},"ports":[{"port":3000,"targetPort":"http-server"}]},"type":"LoadBalancer","kind":"Service","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '239' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '521' body: encoding: UTF-8 string: '{"kind":"Service","apiVersion":"v1","metadata":{"name":"guestbook","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/guestbook","uid":"f42187e1-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"538","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"name":"guestbook"}},"spec":{"ports":[{"protocol":"TCP","port":3000,"targetPort":"http-server","nodePort":0}],"selector":{"app":"guestbook"},"clusterIP":"10.0.0.80","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"app":"redis","role":"master"},"name":"redis-master"},"spec":{"selector":{"app":"redis","role":"master"},"ports":[{"port":6379,"targetPort":"redis-server"}]},"kind":"Service","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '244' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '552' body: encoding: UTF-8 string: '{"kind":"Service","apiVersion":"v1","metadata":{"name":"redis-master","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/redis-master","uid":"f423bf8b-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"542","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"master"}},"spec":{"ports":[{"protocol":"TCP","port":6379,"targetPort":"redis-server","nodePort":0}],"selector":{"app":"redis","role":"master"},"clusterIP":"10.0.0.140","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"app":"redis","role":"slave"},"name":"redis-slave"},"spec":{"selector":{"app":"redis","role":"slave"},"ports":[{"port":6379,"targetPort":"redis-server"}]},"kind":"Service","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '241' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '548' body: encoding: UTF-8 string: '{"kind":"Service","apiVersion":"v1","metadata":{"name":"redis-slave","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/redis-slave","uid":"f4264678-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"545","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"slave"}},"spec":{"ports":[{"protocol":"TCP","port":6379,"targetPort":"redis-server","nodePort":0}],"selector":{"app":"redis","role":"slave"},"clusterIP":"10.0.0.154","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"app":"guestbook","role":"slave"},"name":"guestbook"},"spec":{"selector":{"app":"guestbook"},"template":{"metadata":{"labels":{"app":"guestbook"}},"spec":{"containers":[{"name":"guestbook","image":"kubernetes/guestbook:v2","ports":[{"name":"http-server","containerPort":3000}]}]}},"replicas":3},"kind":"ReplicationController","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '395' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '815' body: encoding: UTF-8 string: '{"kind":"ReplicationController","apiVersion":"v1","metadata":{"name":"guestbook","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/guestbook","uid":"f4287784-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"547","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"guestbook","role":"slave"}},"spec":{"replicas":3,"selector":{"app":"guestbook"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"guestbook"}},"spec":{"containers":[{"name":"guestbook","image":"kubernetes/guestbook:v2","ports":[{"name":"http-server","containerPort":3000,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":0}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"app":"redis","role":"master"},"name":"redis-master"},"spec":{"selector":{"app":"redis","role":"master"},"template":{"metadata":{"labels":{"app":"redis","role":"master"}},"spec":{"containers":[{"name":"redis-master","image":"redis","ports":[{"name":"redis-server","containerPort":6379}]}]}},"replicas":1},"kind":"ReplicationController","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '405' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '828' body: encoding: UTF-8 string: '{"kind":"ReplicationController","apiVersion":"v1","metadata":{"name":"redis-master","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-master","uid":"f42a9800-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"558","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"master"}},"spec":{"replicas":1,"selector":{"app":"redis","role":"master"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"redis","role":"master"}},"spec":{"containers":[{"name":"redis-master","image":"redis","ports":[{"name":"redis-server","containerPort":6379,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":0}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: post uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers body: encoding: UTF-8 string: '{"metadata":{"namespace":"kubeclient-ns","labels":{"app":"redis","role":"slave"},"name":"redis-slave"},"spec":{"selector":{"app":"redis","role":"slave"},"template":{"metadata":{"labels":{"app":"redis","role":"slave"}},"spec":{"containers":[{"name":"redis-slave","image":"kubernetes/redis-slave:v2","ports":[{"name":"redis-server","containerPort":6379}]}]}},"replicas":2},"kind":"ReplicationController","apiVersion":"v1"}' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate Content-Length: - '420' User-Agent: - Ruby response: status: code: 201 message: Created headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '842' body: encoding: UTF-8 string: '{"kind":"ReplicationController","apiVersion":"v1","metadata":{"name":"redis-slave","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-slave","uid":"f42e1d09-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"567","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"slave"}},"spec":{"replicas":2,"selector":{"app":"redis","role":"slave"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"redis","role":"slave"}},"spec":{"containers":[{"name":"redis-slave","image":"kubernetes/redis-slave:v2","ports":[{"name":"redis-server","containerPort":6379,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":0}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: get uri: http://10.35.0.23:8080/api/v1/namespaces body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '629' body: encoding: UTF-8 string: '{"kind":"NamespaceList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces","resourceVersion":"570"},"items":[{"metadata":{"name":"default","selfLink":"/api/v1/namespaces/default","uid":"37360c82-3e77-11e5-a75a-18037327aaeb","resourceVersion":"6","creationTimestamp":"2015-08-09T09:15:45Z"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}},{"metadata":{"name":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns","uid":"f41e6b27-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"534","creationTimestamp":"2015-08-09T10:03:59Z"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}}]}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: get uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '1661' body: encoding: UTF-8 string: '{"kind":"ServiceList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/kubeclient-ns/services","resourceVersion":"571"},"items":[{"metadata":{"name":"guestbook","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/guestbook","uid":"f42187e1-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"538","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"name":"guestbook"}},"spec":{"ports":[{"protocol":"TCP","port":3000,"targetPort":"http-server","nodePort":0}],"selector":{"app":"guestbook"},"clusterIP":"10.0.0.80","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}},{"metadata":{"name":"redis-master","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/redis-master","uid":"f423bf8b-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"542","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"master"}},"spec":{"ports":[{"protocol":"TCP","port":6379,"targetPort":"redis-server","nodePort":0}],"selector":{"app":"redis","role":"master"},"clusterIP":"10.0.0.140","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}},{"metadata":{"name":"redis-slave","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/services/redis-slave","uid":"f4264678-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"545","creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"slave"}},"spec":{"ports":[{"protocol":"TCP","port":6379,"targetPort":"redis-server","nodePort":0}],"selector":{"app":"redis","role":"slave"},"clusterIP":"10.0.0.154","type":"ClusterIP","sessionAffinity":"None"},"status":{"loadBalancer":{}}}]}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: get uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Transfer-Encoding: - chunked body: encoding: UTF-8 string: '{"kind":"ReplicationControllerList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers","resourceVersion":"571"},"items":[{"metadata":{"name":"guestbook","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/guestbook","uid":"f4287784-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"557","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"guestbook","role":"slave"}},"spec":{"replicas":3,"selector":{"app":"guestbook"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"guestbook"}},"spec":{"containers":[{"name":"guestbook","image":"kubernetes/guestbook:v2","ports":[{"name":"http-server","containerPort":3000,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":3,"observedGeneration":1}},{"metadata":{"name":"redis-master","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-master","uid":"f42a9800-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"565","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"master"}},"spec":{"replicas":1,"selector":{"app":"redis","role":"master"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"redis","role":"master"}},"spec":{"containers":[{"name":"redis-master","image":"redis","ports":[{"name":"redis-server","containerPort":6379,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":0,"observedGeneration":1}},{"metadata":{"name":"redis-slave","namespace":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-slave","uid":"f42e1d09-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"567","generation":1,"creationTimestamp":"2015-08-09T10:03:59Z","labels":{"app":"redis","role":"slave"}},"spec":{"replicas":2,"selector":{"app":"redis","role":"slave"},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"redis","role":"slave"}},"spec":{"containers":[{"name":"redis-slave","image":"kubernetes/redis-slave:v2","ports":[{"name":"redis-server","containerPort":6379,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Always","dnsPolicy":"ClusterFirst"}}},"status":{"replicas":0}}]}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/guestbook body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/redis-master body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/services/redis-slave body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/guestbook body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-master body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns/replicationcontrollers/redis-slave body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '100' body: encoding: UTF-8 string: |- { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Success", "code": 200 } http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: delete uri: http://10.35.0.23:8080/api/v1/namespaces/kubeclient-ns body: encoding: US-ASCII string: '' headers: Accept: - '*/*; q=0.5, application/xml' Accept-Encoding: - gzip, deflate User-Agent: - Ruby response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Sun, 09 Aug 2015 10:03:59 GMT Content-Length: - '345' body: encoding: UTF-8 string: '{"kind":"Namespace","apiVersion":"v1","metadata":{"name":"kubeclient-ns","selfLink":"/api/v1/namespaces/kubeclient-ns","uid":"f41e6b27-3e7d-11e5-a75a-18037327aaeb","resourceVersion":"584","creationTimestamp":"2015-08-09T10:03:59Z","deletionTimestamp":"2015-08-09T10:03:59Z"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Terminating"}}' http_version: recorded_at: Sun, 09 Aug 2015 10:00:02 GMT - request: method: get uri: http://10.35.0.23:8080/api/v1 body: encoding: US-ASCII string: '' headers: Accept: - "*/*" Accept-Encoding: - gzip, deflate User-Agent: - rest-client/2.0.0 (linux-gnu x86_64) ruby/2.3.0p0 Host: - localhost:8080 response: status: code: 200 message: OK headers: Content-Type: - application/json Date: - Mon, 29 Aug 2016 15:51:30 GMT Transfer-Encoding: - chunked body: encoding: UTF-8 string: '{"kind":"APIResourceList","groupVersion":"v1","resources":[{"name":"bindings","namespaced":true,"kind":"Binding"},{"name":"componentstatuses","namespaced":false,"kind":"ComponentStatus"},{"name":"configmaps","namespaced":true,"kind":"ConfigMap"},{"name":"endpoints","namespaced":true,"kind":"Endpoints"},{"name":"events","namespaced":true,"kind":"Event"},{"name":"limitranges","namespaced":true,"kind":"LimitRange"},{"name":"namespaces","namespaced":false,"kind":"Namespace"},{"name":"namespaces/finalize","namespaced":false,"kind":"Namespace"},{"name":"namespaces/status","namespaced":false,"kind":"Namespace"},{"name":"nodes","namespaced":false,"kind":"Node"},{"name":"nodes/proxy","namespaced":false,"kind":"Node"},{"name":"nodes/status","namespaced":false,"kind":"Node"},{"name":"persistentvolumeclaims","namespaced":true,"kind":"PersistentVolumeClaim"},{"name":"persistentvolumeclaims/status","namespaced":true,"kind":"PersistentVolumeClaim"},{"name":"persistentvolumes","namespaced":false,"kind":"PersistentVolume"},{"name":"persistentvolumes/status","namespaced":false,"kind":"PersistentVolume"},{"name":"pods","namespaced":true,"kind":"Pod"},{"name":"pods/attach","namespaced":true,"kind":"Pod"},{"name":"pods/binding","namespaced":true,"kind":"Binding"},{"name":"pods/exec","namespaced":true,"kind":"Pod"},{"name":"pods/log","namespaced":true,"kind":"Pod"},{"name":"pods/portforward","namespaced":true,"kind":"Pod"},{"name":"pods/proxy","namespaced":true,"kind":"Pod"},{"name":"pods/status","namespaced":true,"kind":"Pod"},{"name":"podtemplates","namespaced":true,"kind":"PodTemplate"},{"name":"replicationcontrollers","namespaced":true,"kind":"ReplicationController"},{"name":"replicationcontrollers/scale","namespaced":true,"kind":"Scale"},{"name":"replicationcontrollers/status","namespaced":true,"kind":"ReplicationController"},{"name":"resourcequotas","namespaced":true,"kind":"ResourceQuota"},{"name":"resourcequotas/status","namespaced":true,"kind":"ResourceQuota"},{"name":"secrets","namespaced":true,"kind":"Secret"},{"name":"serviceaccounts","namespaced":true,"kind":"ServiceAccount"},{"name":"services","namespaced":true,"kind":"Service"},{"name":"services/proxy","namespaced":true,"kind":"Service"},{"name":"services/status","namespaced":true,"kind":"Service"}]} ' http_version: recorded_at: Mon, 29 Aug 2016 15:51:30 GMT recorded_with: VCR 3.0.3 kubeclient-4.11.0/test/config/000077500000000000000000000000001435070561000161475ustar00rootroot00000000000000kubeclient-4.11.0/test/config/allinone.kubeconfig000066400000000000000000000130631435070561000220110ustar00rootroot00000000000000 apiVersion: v1 clusters: - cluster: server: https://localhost:6443 certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvekNDQWVlZ0F3SUJBZ0lUUGJmcHkyOWFCRzY3Q2hSZEI2bEplZ1RrbURBTkJna3Foa2lHOXcwQkFRc0YKQURBWU1SWXdGQVlEVlFRREV3MXJkV0psY201bGRHVnpMV05oTUI0WERUSXlNREl5TVRBNU1ESXdNRm9YRFRNeQpNREl4T1RBNU1ESXdNRm93R0RFV01CUUdBMVVFQXhNTmEzVmlaWEp1WlhSbGN5MWpZVENDQVNJd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUxqWjJjRkJhbHpvaW1hRWNwVDlKejJKbWRnc0hNT2FnVmQKSXQ3T1FwVHdEWjNucElJQ1ZwZ3VFaDl4dG92UjhtOC9IWU0rL2E0dk1RSFQrM3A4SFBqaURhUllHZzdPWjlMKwpGcC85emhCdWlhSXZnOForQmJ5czlROVV1ajZWRXdmRkpCY05INlRtemRpRGdRVXM1L2srNi92dHVKNHlzM3NECktrQU94cVBYRGFCb0FObkxwSXhkSU1RRGNXU0xGQTB3bUZoZFpKcTNLRUFvSnBFTDBXWW8xWlJCVjNpSDc3eWYKc0RiTjFPQnUydk5uUlorRHJWMFpKNUFwbWJGWFBYOGk0S0phVzlsQ0I2MkZOMGo1WHNORG95VGVBVnBlc2ZOcwp6WXVmVnBCZHFOWkZrT0tnOWRpTXVUTWlrYTJhWWZEdWlWemRlYkRnY3A5YU1sb0t0YkVDQXdFQUFhTkNNRUF3CkRnWURWUjBQQVFIL0JBUURBZ0VHTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkJkT2l5Z0MKTGN1SnJxOHJOYTF4QURyNVNwN0NNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUURDeTRJbGhBU2g2QnI1WEVjSQpUcFA1VGhEMU95UnpRbnNQZTZQMXFnV1Aza0JYSy9BY3NTbCtWR3RhWnAyb0VoSm9VbnN6N2tFOHlXM2dLK1BBCjUxelk0YUhUaUY5eGt5ZDV6T0NBR0IrY2ZwOVlzK3N6V3p5dTBRUTlJQmpKNCtlRGpnN1cwL1MrQk0yUW4xaUwKalRGSWUyQmRmK1EvSjI0L3Eza3NUWEsxN1VOdW4xNHZEUnNKZ3NOY3JGdC9ydW1mSFB4MXl0d3NpcUt5RUtWNwprRnhTd2EzZDgvQXZoR2dGcFBtZlJqVTdnQUpDRmNIejUwMXpoaTJhNkw1VFlCVGVjVlJicVpvZUhpWjBZTldJCmlzNWc0Vm1WQitCeE1BTTJXRWQyOXY0bC8zb0kxUGV5OXJ2dDdOSnFTZTFpbTl1cVpnVkRlZy92UDh6S3MvZEYKWll3OAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURXVENDQWtHZ0F3SUJBZ0lVS08xZVJtWG1DYyt2dk0rTG9CeVpFMElNR0Q4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERVdNQlFHQTFVRUF4TU5hM1ZpWlhKdVpYUmxjeTFqWVRBZUZ3MHlNakF5TWpFd09UQXlNREJhRncweQpNekF5TWpFd09UQXlNREJhTURReEZ6QVZCZ05WQkFvVERuTjVjM1JsYlRwdFlYTjBaWEp6TVJrd0Z3WURWUVFECkV4QnJkV0psY201bGRHVnpMV0ZrYldsdU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXdodzF6SmFBWVcwa0ljbjVtV3lYTm03SVlEYStRTXpIM0hWWnJnd1l5Y0NaWlA5MGJZSmo0VEYrKyszbgpQTVoydU1mWkVYY21OcEdYQ0lxek5ZSlF6dlQrYTF6Q011VVQ5K3YxZzM0My9WYVlweFJ6R3VlQ1ZGK3B2Qi9nCmpJMFU0bDFZbUpJRWs1WGNETTl4RStob1Vrd3FWYWhNbXJ2c3E4aHluWWp6V0V0bDBtSitUTzg5LzFaandBdVEKdDRvaE5PMXBRdTNyNEhDdTNIR2JGU1RQSjZFNG94UmdkTXFIbHFzd3BHMCtaTFJYVmtvZ1VidGxJVUx0Y3RvVQpUNjdmaHNUaFJDWXUyQUFpbitMMjBncjNUY3A0VXRNV3RUY1o2NnZsTDJFcDZUa1dpdXRiMDRML0JvNDZWZnhPCnNLQkNkTjFqeXNPZVRQZ1RXS2gwODh2TTB3SURBUUFCbzM4d2ZUQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0hRWUQKVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUF3R0ExVWRFd0VCL3dRQ01BQXdIUVlEVlIwTwpCQllFRko2NzV6N0dDSXVKY2JHeEo3YkxJM0pBTDNCZU1COEdBMVVkSXdRWU1CYUFGQmRPaXlnQ0xjdUpycThyCk5hMXhBRHI1U3A3Q01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ1hHQ2hlbytWVXhsRlJQRVhSU3B3NDgwbGIKMm12ZHpCNE8vck9JRzJIOXZ6Z2poeGJkQ0pOVmVka0d6bUhaNzVlRUh6djFFa0luZWJTR0xGejUvUDNCUVVYdwp4eXU4NGpOSmVyOVJlMmdlbVYwdDJtd3pIV0NmUUdMS0c0QUppN00rY3haNDlYaHVtWlJWa3c3aU9SRitxWU9qCmhTbG9CLzRrZnlxM1VhcHJLQ3ZocWpFc0tKcURuZHdsT3dLTFZNaWMzbFFETExYb0thNVRpdjZpaEJpQ1ZXQUkKWFV5NWwyZG9EdlpoemdFakxlUTBDR0hiNTl2Q3Zkb3RUYzBiN0hKNFp3MjFBTHoyQ0Y3eGt3WGpQNXVVSXE1Ngo2UG1RK2dwSFhrbGJOSklPRHRrZi85STRyTWxIVXplSzVEVllZNWtkeEpUemgybng0UjBpY0Z4MWpuYzAKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBd2h3MXpKYUFZVzBrSWNuNW1XeVhObTdJWURhK1FNekgzSFZacmd3WXljQ1paUDkwCmJZSmo0VEYrKyszblBNWjJ1TWZaRVhjbU5wR1hDSXF6TllKUXp2VCthMXpDTXVVVDkrdjFnMzQzL1ZhWXB4UnoKR3VlQ1ZGK3B2Qi9nakkwVTRsMVltSklFazVYY0RNOXhFK2hvVWt3cVZhaE1tcnZzcThoeW5ZanpXRXRsMG1KKwpUTzg5LzFaandBdVF0NG9oTk8xcFF1M3I0SEN1M0hHYkZTVFBKNkU0b3hSZ2RNcUhscXN3cEcwK1pMUlhWa29nClVidGxJVUx0Y3RvVVQ2N2Zoc1RoUkNZdTJBQWluK0wyMGdyM1RjcDRVdE1XdFRjWjY2dmxMMkVwNlRrV2l1dGIKMDRML0JvNDZWZnhPc0tCQ2ROMWp5c09lVFBnVFdLaDA4OHZNMHdJREFRQUJBb0lCQVFEQlhHQ3JRSEQ2bkVJVgo5cURSR0w4NDFmcDgvWXRmK1o1T0dnZ1B2TFVrcE5zcEpOL1NCc1dBR2xJb204QnhaakgxdC82ZnkxVWhucjRaCklER002QmVmVWFYdlJTT2VsMXZnNkFoVnlISDF4MEdoamxsclA1c3dlV3NYbjVtTDZTNFlvR3dVNzcvblZLMHoKaGFGYTkzU1VKcE0xYU1XR2poVWd1amlTZlU0TGNMRFJWS0RFUk80OFhQOEZpQ0E2UEIySUpYUDBTOUJhTVZLUQpIQVJDV05HWDV6aU5hbzFyb0l2UFFORU1EVHRuV2JiNHo0U3ZSbXBjRjYwRS9mNEpwMDZTSDAwUU5lVHY3bjU3CkR6U1hZZGNxbGZMUjlRU1RkVmt0U094UThjSGFsVGN5NXNVMnh5RndjMVM5YVpaOG56UnBiRnUxVllGQ0lWY2YKRTRRVnYxVUJBb0dCQU1XZzRuWlRIaUd0aGY2S3l6T0draDFyMmtCU21WeTlCY1lqQjhUYnpRYnJOVXk3WUFsVAppN2VPbkYrdVRLY2FWYzV2blVyVjBsN3pEM1dTV3UxQVJiOUs2WnBWTDV3allObW0venVyUittSmM4TEtsN2Q2CjU0ejFjMTExVDVpaXZNMWJZUm5VME91akhEb1g1WkQ1V0dzeE1LYUh0TGFHV1gvZ0s0OWhQYmZ6QW9HQkFQdHgKVFdRSGlPcS91VnNrMFFuWWtLWkV3ZXpmZEMyZGFWSm9BeURBVjVnY1U0TnNONjJVS2ZnVHJhRCtDaG15SjVSNwpqT1NMOGJVWDMzUVFFdm1LOVdZWE1WQnd6MjhIclp5cHVCeVFFRWM4ZTl5eTRZUStSWVoxTDJrbUdkYWVaOE16CmlEZmZMclhieTdOWnA0eGNmTVpXWW1Md2RHRVNZbzhqenVjaTFLK2hBb0dCQUpESXprQmJvbTZQM3VQZHNRTGQKcXZ4TkFJY3hQRlA1MDFvV1hlRzJHaDNnZ1pybWgzUXR0ZVZUWUhLa2tsbTE3SGtod2oyS0t1WU84aHR6anBQVQpDNFVhajh2V2J0dlgrMk5aZWhHdjZTNUoyZm95VERaS240cmdZNVZybFZYQW04dGpEOTlKejRsaVpSS1dZVVAxCnVQWkhBbHB1ZjFGZFdnSmFLKytPRVJaTEFvR0FLSmQ5K3V3TWVubEJIeW11Wlh5RXZaTFVDNzEzTC9YOWpzUWoKM1NHd0FtcHdRUU16YWQ1RmVEc1ZDS3g2VFBPcDJCcXFBQ3RuZGVqSXRoL3lNRDd5cHV5UGxZRGd1L2Z0V3lFNwpDOEZtSDFud1ZReTd3M0dhSDc3RFRLSk9BWXZKRElaQk0yUGdVcE9OS3dNS1BXcWc2aFFBQmlEemFNaGpDT0NyCkFqMXBRSUVDZ1lFQWw2THZncFl6enlUVThtRmFYazUrQXVaVHB1dlZzNXNOVEUwQmdyUWJBN0haL0hjT3hLaWQKYkxPU1diVzNaZ05aV0xXOFNhdWlURCt4L2lmVGR6UUJHQnZOaGFJaklGNnltdFlwTzNaWCs2MG15L1hUZmJGUQordUJ3UDducU1JUnNuQjUzRlc0S3VWcWljMDVEbGdvRWJ5NWM1eTlPQjlyWUQzUnJTT01EbGFzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= kubeclient-4.11.0/test/config/another-ca1.pem000066400000000000000000000021171435070561000207550ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIUQZjM/5qoAF78qIDyc+rKi4qBdOIwDQYJKoZIhvcNAQEL BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQzMDBaFw0z MjAzMTkxNDQzMDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGkG7g+UjpDhZ7A4Pm7Hme+RWs5IHz4I2X IclvtO3LuJ26yzz2S8VaXFFeUqzEPb2G1RxFGvoAVN7qrTw0n5MQJCFLAA4dI7oY 8XLRJ7KgTBBIw1jYpgKb2zyHPIJE6VmslliKUiX+QDovdRU/dsbdup2EucrnGw4+ QNNAc3XMbXgm6lubA6znYZlSpcQ8BKer3tq75q4KUZicIjS6gKQyZjk9a6fcOuCS ybtlAKp9lYzcwxZkNrx+V1PJMQ1qaJWPnMAVi7Oj5Dm3Jmf1WHBcNEh52Q/0vYlt 4WSaeM5t/Py/m/7c4Ve97f5m2X6EhYyUbzov4qeZOnIJI3MnU1FxAgMBAAGjQjBA MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSl1qyt jd96WstRE8h9x5qkCvZUvjANBgkqhkiG9w0BAQsFAAOCAQEAJt55qYvBaniAwvgO tbO79g1FcQGrxpMX45TuoCE/K+MWDjrr6bp+FbLOqT8MwOsbGwwJIRTHGvkEkVso 5AWI5aSNs3hWnltOdz27ZSHeX77WB4daK1tLK6ggZrp3v9iIpbBwWBFdmAqsPvEs H17K2BgAzdh6xRKPQd0BGTUpJBfk50R2gDMj7FKyIzBN69IOGytBfAXBhHzEGy4+ MvtTEIMUjR//KgCrpNeyDuaWHttR5FdnuRxFO7O3BAfyNSaNmd/IEHQf7DIGgzOy +xWLyH/HRHj5C70qAqjbnrgBODI99BsA9U7oXTuyPLdIboAcFt2zD5DIYgZET52X 53w4jA== -----END CERTIFICATE----- kubeclient-4.11.0/test/config/another-ca2.pem000066400000000000000000000021171435070561000207560ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIUHW3OPnmuTquJ0YgbGpmm/blsY2QwDQYJKoZIhvcNAQEL BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQ0MDBaFw0z MjAzMTkxNDQ0MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLMEJs5agS0hNQBxPTtsI6dIhIi/pY8liI sNukbi5KwKf80FYNyRXqE8ufDVyTFzOc+MG96jnHjDaBWjrVN9On0PgUBo4nPyd4 DtyvYx2jMzwToSEIo/Z1aroMx1oGywCgdS4/3FWAbhlSbyXKJmhfh6gX0TxWz+dV zqNuqQq9EWuRhOMg9vgzjfp3mjiPE10lW8pT0j5JT3PI/eGO+C2Z7z33LJXb6GM2 nXvhGFMGY+7XG65pqJ3L8g1mk+LjPiwyIItw8wPtrnrZ2VXMklMd5Mn+jgCTNe1B om0nPpPIiTblCr6gcNcVjy5WGN37OKlqrT0JTuSPHcxSUp05LFjDAgMBAAGjQjBA MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQvV/sB wbR3UwjkLAMN+6P3fZ/3OjANBgkqhkiG9w0BAQsFAAOCAQEACAk4EQwCkw2EBsSR 2SKoa1SjYFkZzIr/0/TB2YcMUvHF+RpvlD5vQ8/RJjeAl1kc6/niZ9TWCemjBLqI hPoFe49zr49DyQjC2ZfsXVJvFCr6g7o4q4DtQ6ltyBuTJbkn1hI+aB8zgvpofG44 mKj18Y7tPvgXtRua4SaeBq777+22AOvKxPied9p4PTrMN4RKTP6+yIbLflej7dBD zQDjfmmYsH0T2ZRtBpE1dYrUbU3tkizcMZRJBgreoxoff+r5coibMIm/7gh+YoSb BCItCaeuGSKQ8CJb8DElcPUd6nKUjmeiQL68ztsG/+CXLiL/TZb914VaaCXvPInw 49jJ7w== -----END CERTIFICATE----- kubeclient-4.11.0/test/config/concatenated-ca.kubeconfig000066400000000000000000000005771435070561000232270ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: certificate-authority: concatenated-ca.pem server: https://localhost:6443 name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/concatenated-ca.pem000066400000000000000000000063511435070561000216700ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIUQZjM/5qoAF78qIDyc+rKi4qBdOIwDQYJKoZIhvcNAQEL BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQzMDBaFw0z MjAzMTkxNDQzMDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGkG7g+UjpDhZ7A4Pm7Hme+RWs5IHz4I2X IclvtO3LuJ26yzz2S8VaXFFeUqzEPb2G1RxFGvoAVN7qrTw0n5MQJCFLAA4dI7oY 8XLRJ7KgTBBIw1jYpgKb2zyHPIJE6VmslliKUiX+QDovdRU/dsbdup2EucrnGw4+ QNNAc3XMbXgm6lubA6znYZlSpcQ8BKer3tq75q4KUZicIjS6gKQyZjk9a6fcOuCS ybtlAKp9lYzcwxZkNrx+V1PJMQ1qaJWPnMAVi7Oj5Dm3Jmf1WHBcNEh52Q/0vYlt 4WSaeM5t/Py/m/7c4Ve97f5m2X6EhYyUbzov4qeZOnIJI3MnU1FxAgMBAAGjQjBA MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSl1qyt jd96WstRE8h9x5qkCvZUvjANBgkqhkiG9w0BAQsFAAOCAQEAJt55qYvBaniAwvgO tbO79g1FcQGrxpMX45TuoCE/K+MWDjrr6bp+FbLOqT8MwOsbGwwJIRTHGvkEkVso 5AWI5aSNs3hWnltOdz27ZSHeX77WB4daK1tLK6ggZrp3v9iIpbBwWBFdmAqsPvEs H17K2BgAzdh6xRKPQd0BGTUpJBfk50R2gDMj7FKyIzBN69IOGytBfAXBhHzEGy4+ MvtTEIMUjR//KgCrpNeyDuaWHttR5FdnuRxFO7O3BAfyNSaNmd/IEHQf7DIGgzOy +xWLyH/HRHj5C70qAqjbnrgBODI99BsA9U7oXTuyPLdIboAcFt2zD5DIYgZET52X 53w4jA== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIC/zCCAeegAwIBAgITPbfpy29aBG67ChRdB6lJegTkmDANBgkqhkiG9w0BAQsF ADAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMB4XDTIyMDIyMTA5MDIwMFoXDTMy MDIxOTA5MDIwMFowGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMLjZ2cFBalzoimaEcpT9Jz2JmdgsHMOagVd It7OQpTwDZ3npIICVpguEh9xtovR8m8/HYM+/a4vMQHT+3p8HPjiDaRYGg7OZ9L+ Fp/9zhBuiaIvg8Z+Bbys9Q9Uuj6VEwfFJBcNH6TmzdiDgQUs5/k+6/vtuJ4ys3sD KkAOxqPXDaBoANnLpIxdIMQDcWSLFA0wmFhdZJq3KEAoJpEL0WYo1ZRBV3iH77yf sDbN1OBu2vNnRZ+DrV0ZJ5ApmbFXPX8i4KJaW9lCB62FN0j5XsNDoyTeAVpesfNs zYufVpBdqNZFkOKg9diMuTMika2aYfDuiVzdebDgcp9aMloKtbECAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBdOiygC LcuJrq8rNa1xADr5Sp7CMA0GCSqGSIb3DQEBCwUAA4IBAQDCy4IlhASh6Br5XEcI TpP5ThD1OyRzQnsPe6P1qgWP3kBXK/AcsSl+VGtaZp2oEhJoUnsz7kE8yW3gK+PA 51zY4aHTiF9xkyd5zOCAGB+cfp9Ys+szWzyu0QQ9IBjJ4+eDjg7W0/S+BM2Qn1iL jTFIe2Bdf+Q/J24/q3ksTXK17UNun14vDRsJgsNcrFt/rumfHPx1ytwsiqKyEKV7 kFxSwa3d8/AvhGgFpPmfRjU7gAJCFcHz501zhi2a6L5TYBTecVRbqZoeHiZ0YNWI is5g4VmVB+BxMAM2WEd29v4l/3oI1Pey9rvt7NJqSe1im9uqZgVDeg/vP8zKs/dF ZYw8 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDADCCAeigAwIBAgIUHW3OPnmuTquJ0YgbGpmm/blsY2QwDQYJKoZIhvcNAQEL BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQ0MDBaFw0z MjAzMTkxNDQ0MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLMEJs5agS0hNQBxPTtsI6dIhIi/pY8liI sNukbi5KwKf80FYNyRXqE8ufDVyTFzOc+MG96jnHjDaBWjrVN9On0PgUBo4nPyd4 DtyvYx2jMzwToSEIo/Z1aroMx1oGywCgdS4/3FWAbhlSbyXKJmhfh6gX0TxWz+dV zqNuqQq9EWuRhOMg9vgzjfp3mjiPE10lW8pT0j5JT3PI/eGO+C2Z7z33LJXb6GM2 nXvhGFMGY+7XG65pqJ3L8g1mk+LjPiwyIItw8wPtrnrZ2VXMklMd5Mn+jgCTNe1B om0nPpPIiTblCr6gcNcVjy5WGN37OKlqrT0JTuSPHcxSUp05LFjDAgMBAAGjQjBA MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQvV/sB wbR3UwjkLAMN+6P3fZ/3OjANBgkqhkiG9w0BAQsFAAOCAQEACAk4EQwCkw2EBsSR 2SKoa1SjYFkZzIr/0/TB2YcMUvHF+RpvlD5vQ8/RJjeAl1kc6/niZ9TWCemjBLqI hPoFe49zr49DyQjC2ZfsXVJvFCr6g7o4q4DtQ6ltyBuTJbkn1hI+aB8zgvpofG44 mKj18Y7tPvgXtRua4SaeBq777+22AOvKxPied9p4PTrMN4RKTP6+yIbLflej7dBD zQDjfmmYsH0T2ZRtBpE1dYrUbU3tkizcMZRJBgreoxoff+r5coibMIm/7gh+YoSb BCItCaeuGSKQ8CJb8DElcPUd6nKUjmeiQL68ztsG/+CXLiL/TZb914VaaCXvPInw 49jJ7w== -----END CERTIFICATE----- kubeclient-4.11.0/test/config/execauth.kubeconfig000066400000000000000000000034461435070561000220220ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:6443 name: localhost:6443 contexts: - context: cluster: localhost:6443 namespace: default user: system:admin:exec-search-path name: localhost/system:admin:exec-search-path - context: cluster: localhost:6443 namespace: default user: system:admin:exec-relative-path name: localhost/system:admin:exec-relative-path - context: cluster: localhost:6443 namespace: default user: system:admin:exec-absolute-path name: localhost/system:admin:exec-absolute-path kind: Config preferences: {} users: - name: system:admin:exec-search-path user: exec: # Command to execute. Required. command: "example-exec-plugin" # API version to use when decoding the ExecCredentials resource. Required. # # The API version returned by the plugin MUST match the version listed here. # # To integrate with tools that support multiple versions (such as client.authentication.k8s.io/v1alpha1), # set an environment variable or pass an argument to the tool that indicates which version the exec plugin expects. apiVersion: "client.authentication.k8s.io/v1beta1" # Environment variables to set when executing the plugin. Optional. env: - name: "FOO" value: "bar" # Arguments to pass when executing the plugin. Optional. args: - "arg1" - "arg2" - name: system:admin:exec-relative-path user: exec: # Command to execute. Required. command: "dir/example-exec-plugin" apiVersion: "client.authentication.k8s.io/v1beta1" - name: system:admin:exec-absolute-path user: exec: # Command to execute. Required. command: "/abs/path/example-exec-plugin" apiVersion: "client.authentication.k8s.io/v1beta1" kubeclient-4.11.0/test/config/external-ca.pem000066400000000000000000000021131435070561000210520ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC/zCCAeegAwIBAgITPbfpy29aBG67ChRdB6lJegTkmDANBgkqhkiG9w0BAQsF ADAYMRYwFAYDVQQDEw1rdWJlcm5ldGVzLWNhMB4XDTIyMDIyMTA5MDIwMFoXDTMy MDIxOTA5MDIwMFowGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMLjZ2cFBalzoimaEcpT9Jz2JmdgsHMOagVd It7OQpTwDZ3npIICVpguEh9xtovR8m8/HYM+/a4vMQHT+3p8HPjiDaRYGg7OZ9L+ Fp/9zhBuiaIvg8Z+Bbys9Q9Uuj6VEwfFJBcNH6TmzdiDgQUs5/k+6/vtuJ4ys3sD KkAOxqPXDaBoANnLpIxdIMQDcWSLFA0wmFhdZJq3KEAoJpEL0WYo1ZRBV3iH77yf sDbN1OBu2vNnRZ+DrV0ZJ5ApmbFXPX8i4KJaW9lCB62FN0j5XsNDoyTeAVpesfNs zYufVpBdqNZFkOKg9diMuTMika2aYfDuiVzdebDgcp9aMloKtbECAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBdOiygC LcuJrq8rNa1xADr5Sp7CMA0GCSqGSIb3DQEBCwUAA4IBAQDCy4IlhASh6Br5XEcI TpP5ThD1OyRzQnsPe6P1qgWP3kBXK/AcsSl+VGtaZp2oEhJoUnsz7kE8yW3gK+PA 51zY4aHTiF9xkyd5zOCAGB+cfp9Ys+szWzyu0QQ9IBjJ4+eDjg7W0/S+BM2Qn1iL jTFIe2Bdf+Q/J24/q3ksTXK17UNun14vDRsJgsNcrFt/rumfHPx1ytwsiqKyEKV7 kFxSwa3d8/AvhGgFpPmfRjU7gAJCFcHz501zhi2a6L5TYBTecVRbqZoeHiZ0YNWI is5g4VmVB+BxMAM2WEd29v4l/3oI1Pey9rvt7NJqSe1im9uqZgVDeg/vP8zKs/dF ZYw8 -----END CERTIFICATE----- kubeclient-4.11.0/test/config/external-cert.pem000066400000000000000000000023041435070561000214260ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDWTCCAkGgAwIBAgIUKO1eRmXmCc+vvM+LoByZE0IMGD8wDQYJKoZIhvcNAQEL BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAyMjEwOTAyMDBaFw0y MzAyMjEwOTAyMDBaMDQxFzAVBgNVBAoTDnN5c3RlbTptYXN0ZXJzMRkwFwYDVQQD ExBrdWJlcm5ldGVzLWFkbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAwhw1zJaAYW0kIcn5mWyXNm7IYDa+QMzH3HVZrgwYycCZZP90bYJj4TF+++3n PMZ2uMfZEXcmNpGXCIqzNYJQzvT+a1zCMuUT9+v1g343/VaYpxRzGueCVF+pvB/g jI0U4l1YmJIEk5XcDM9xE+hoUkwqVahMmrvsq8hynYjzWEtl0mJ+TO89/1ZjwAuQ t4ohNO1pQu3r4HCu3HGbFSTPJ6E4oxRgdMqHlqswpG0+ZLRXVkogUbtlIULtctoU T67fhsThRCYu2AAin+L20gr3Tcp4UtMWtTcZ66vlL2Ep6TkWiutb04L/Bo46VfxO sKBCdN1jysOeTPgTWKh088vM0wIDAQABo38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYD VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0O BBYEFJ675z7GCIuJcbGxJ7bLI3JAL3BeMB8GA1UdIwQYMBaAFBdOiygCLcuJrq8r Na1xADr5Sp7CMA0GCSqGSIb3DQEBCwUAA4IBAQCXGCheo+VUxlFRPEXRSpw480lb 2mvdzB4O/rOIG2H9vzgjhxbdCJNVedkGzmHZ75eEHzv1EkInebSGLFz5/P3BQUXw xyu84jNJer9Re2gemV0t2mwzHWCfQGLKG4AJi7M+cxZ49XhumZRVkw7iORF+qYOj hSloB/4kfyq3UaprKCvhqjEsKJqDndwlOwKLVMic3lQDLLXoKa5Tiv6ihBiCVWAI XUy5l2doDvZhzgEjLeQ0CGHb59vCvdotTc0b7HJ4Zw21ALz2CF7xkwXjP5uUIq56 6PmQ+gpHXklbNJIODtkf/9I4rMlHUzeK5DVYY5kdxJTzh2nx4R0icFx1jnc0 -----END CERTIFICATE----- kubeclient-4.11.0/test/config/external-key.rsa000066400000000000000000000032171435070561000212710ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAwhw1zJaAYW0kIcn5mWyXNm7IYDa+QMzH3HVZrgwYycCZZP90 bYJj4TF+++3nPMZ2uMfZEXcmNpGXCIqzNYJQzvT+a1zCMuUT9+v1g343/VaYpxRz GueCVF+pvB/gjI0U4l1YmJIEk5XcDM9xE+hoUkwqVahMmrvsq8hynYjzWEtl0mJ+ TO89/1ZjwAuQt4ohNO1pQu3r4HCu3HGbFSTPJ6E4oxRgdMqHlqswpG0+ZLRXVkog UbtlIULtctoUT67fhsThRCYu2AAin+L20gr3Tcp4UtMWtTcZ66vlL2Ep6TkWiutb 04L/Bo46VfxOsKBCdN1jysOeTPgTWKh088vM0wIDAQABAoIBAQDBXGCrQHD6nEIV 9qDRGL841fp8/Ytf+Z5OGggPvLUkpNspJN/SBsWAGlIom8BxZjH1t/6fy1Uhnr4Z IDGM6BefUaXvRSOel1vg6AhVyHH1x0GhjllrP5sweWsXn5mL6S4YoGwU77/nVK0z haFa93SUJpM1aMWGjhUgujiSfU4LcLDRVKDERO48XP8FiCA6PB2IJXP0S9BaMVKQ HARCWNGX5ziNao1roIvPQNEMDTtnWbb4z4SvRmpcF60E/f4Jp06SH00QNeTv7n57 DzSXYdcqlfLR9QSTdVktSOxQ8cHalTcy5sU2xyFwc1S9aZZ8nzRpbFu1VYFCIVcf E4QVv1UBAoGBAMWg4nZTHiGthf6KyzOGkh1r2kBSmVy9BcYjB8TbzQbrNUy7YAlT i7eOnF+uTKcaVc5vnUrV0l7zD3WSWu1ARb9K6ZpVL5wjYNmm/zurR+mJc8LKl7d6 54z1c111T5iivM1bYRnU0OujHDoX5ZD5WGsxMKaHtLaGWX/gK49hPbfzAoGBAPtx TWQHiOq/uVsk0QnYkKZEwezfdC2daVJoAyDAV5gcU4NsN62UKfgTraD+ChmyJ5R7 jOSL8bUX33QQEvmK9WYXMVBwz28HrZypuByQEEc8e9yy4YQ+RYZ1L2kmGdaeZ8Mz iDffLrXby7NZp4xcfMZWYmLwdGESYo8jzuci1K+hAoGBAJDIzkBbom6P3uPdsQLd qvxNAIcxPFP501oWXeG2Gh3ggZrmh3QtteVTYHKkklm17Hkhwj2KKuYO8htzjpPU C4Uaj8vWbtvX+2NZehGv6S5J2foyTDZKn4rgY5VrlVXAm8tjD99Jz4liZRKWYUP1 uPZHAlpuf1FdWgJaK++OERZLAoGAKJd9+uwMenlBHymuZXyEvZLUC713L/X9jsQj 3SGwAmpwQQMzad5FeDsVCKx6TPOp2BqqACtndejIth/yMD7ypuyPlYDgu/ftWyE7 C8FmH1nwVQy7w3GaH77DTKJOAYvJDIZBM2PgUpONKwMKPWqg6hQABiDzaMhjCOCr Aj1pQIECgYEAl6LvgpYzzyTU8mFaXk5+AuZTpuvVs5sNTE0BgrQbA7HZ/HcOxKid bLOSWbW3ZgNZWLW8SauiTD+x/ifTdzQBGBvNhaIjIF6ymtYpO3ZX+60my/XTfbFQ +uBwP7nqMIRsnB53FW4KuVqic05DlgoEby5c5y9OB9rYD3RrSOMDlas= -----END RSA PRIVATE KEY----- kubeclient-4.11.0/test/config/external-without-ca.kubeconfig000066400000000000000000000006741435070561000241200ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: # Not defining custom `certificate-authority`. # Without it, the localhost cert should be rejected. server: https://localhost:6443 name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/external.kubeconfig000066400000000000000000000005731435070561000220340ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: certificate-authority: external-ca.pem server: https://localhost:6443 name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/gcpauth.kubeconfig000066400000000000000000000007331435070561000216430ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:8443 name: localhost:8443 contexts: - context: cluster: localhost:8443 namespace: default user: application-default-credentials name: localhost/application-default-credentials kind: Config preferences: {} users: - name: application-default-credentials user: auth-provider: config: access-token: expiry: 2019-02-19T11:07:29.827352-05:00 name: gcp kubeclient-4.11.0/test/config/gcpcmdauth.kubeconfig000066400000000000000000000012071435070561000223240ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:8443 name: localhost:8443 contexts: - context: cluster: localhost:8443 namespace: default user: application-default-credentials name: localhost/application-default-credentials kind: Config preferences: {} users: - name: application-default-credentials user: auth-provider: config: access-token: cmd-args: config config-helper --format=json cmd-path: /path/to/gcloud expiry: 2019-04-09T19:26:18Z expiry-key: '{.credential.token_expiry}' token-key: '{.credential.access_token}' name: gcp kubeclient-4.11.0/test/config/insecure-custom-ca.kubeconfig000066400000000000000000000010101435070561000237030ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: # This is a silly configuration, skip-tls-verify makes CA data useless, but testing for completeness. certificate-authority: external-ca.pem server: https://localhost:6443 insecure-skip-tls-verify: true name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/insecure.kubeconfig000066400000000000000000000013101435070561000220150ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:6443 insecure-skip-tls-verify: true name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: # Providing ANY credentials in `insecure-skip-tls-verify` mode is unwise due to MITM risk. # At least client certs are not as catastrophic as bearer tokens. # # This combination of insecure + client certs was once broken in kubernetes but # is meaningful since 2015 (https://github.com/kubernetes/kubernetes/pull/15430). client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/nouser.kubeconfig000066400000000000000000000004571435070561000215260ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:6443 name: localhost:6443 contexts: - context: cluster: localhost:6443 namespace: default user: "" name: default/localhost:6443/nouser current-context: default/localhost:6443/nouser kind: Config preferences: {} users: [] kubeclient-4.11.0/test/config/oidcauth.kubeconfig000066400000000000000000000010531435070561000220040ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:8443 name: localhost:8443 contexts: - context: cluster: localhost:8443 namespace: default user: oidc-auth-provider name: localhost/oidc-auth-provider kind: Config preferences: {} users: - name: oidc-auth-provider user: auth-provider: config: client-id: fake-client-id client-secret: fake-client-secret id-token: fake-id-token idp-issuer-url: https://accounts.google.com refresh-token: fake-refresh-token name: oidc kubeclient-4.11.0/test/config/secure-without-ca.kubeconfig000066400000000000000000000010501435070561000235510ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: # Not defining custom `certificate-authority`. # Without it, the localhost cert should be rejected. server: https://localhost:6443 insecure-skip-tls-verify: false # Same as external-without-ca.kubeconfig but with explicit false here. name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/secure.kubeconfig000066400000000000000000000007341435070561000214770ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: certificate-authority: external-ca.pem server: https://localhost:6443 insecure-skip-tls-verify: false # Same as external.kubeconfig but with explicit false here. name: local contexts: - context: cluster: local namespace: default user: user name: Default current-context: Default kind: Config preferences: {} users: - name: user user: client-certificate: external-cert.pem client-key: external-key.rsa kubeclient-4.11.0/test/config/timestamps.kubeconfig000066400000000000000000000014051435070561000223730ustar00rootroot00000000000000apiVersion: v1 users: - name: gke_username user: auth-provider: config: access-token: REDACTED cmd-args: config config-helper --format=json cmd-path: /Users/tannerbruce/opt/google-cloud-sdk/bin/gcloud expiry: 2018-07-07T18:25:36Z expiry-key: '{.credential.token_expiry}' token-key: '{.credential.access_token}' name: gcp # More syntaxes from go-yaml tests, hopefully covering all types possible in kubeconfig IPv4: 1.2.3.4 Duration: 3s date_only: 2015-01-01 rfc3339: 2015-02-24T18:19:39Z longer: 2015-02-24T18:19:39.123456789-03:00 shorter: 2015-2-3T3:4:5Z iso_lower_t: 2015-02-24t18:19:39Z space_no_tz: 2015-02-24 18:19:39 space_tz: 2001-12-14 21:59:43.10 -5 timestamp_like_string: "2015-02-24T18:19:39Z" kubeclient-4.11.0/test/config/update_certs_k0s.rb000077500000000000000000000043441435070561000217430ustar00rootroot00000000000000#!/usr/bin/env ruby # https://docs.k0sproject.io/latest/k0s-in-docker/ # Runs in --prividged mode, only run this if you trust the k0s distribution. require 'English' # Like Kernel#system, returns true iff exit status == 0 def sh?(*cmd) puts("+ #{cmd.join(' ')}") system(*cmd) end # Raises if exit status != 0 def sh!(*cmd) sh?(*cmd) || raise("returned #{$CHILD_STATUS}") end # allow DOCKER='sudo docker', DOCKER=podman etc. DOCKER = ENV['DOCKER'] || 'docker' CONTAINER = 'k0s'.freeze sh! "#{DOCKER} container inspect #{CONTAINER} --format='exists' || #{DOCKER} run -d --name #{CONTAINER} --hostname k0s --privileged -v /var/lib/k0s -p 6443:6443 \ ghcr.io/k0sproject/k0s/k0s:v1.23.3-k0s.1" # sh! "#{DOCKER} exec #{CONTAINER} kubectl config view --raw" # is another way to dump kubeconfig but succeeds with dummy output even before admin.conf exists; # so accessing the file is better way as it lets us poll until ready: sleep(1) until sh?("#{DOCKER} exec #{CONTAINER} ls -l /var/lib/k0s/pki/admin.conf") sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.conf > test/config/allinone.kubeconfig" # The rest could easily be extracted from allinone.kubeconfig, but the test is more robust # if we don't reuse YAML and/or Kubeclient::Config parsing to construct test data. sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/ca.crt > test/config/external-ca.pem" sh! 'cat test/config/another-ca1.pem test/config/external-ca.pem '\ ' test/config/another-ca2.pem > test/config/concatenated-ca.pem' sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.crt > test/config/external-cert.pem" sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.key > test/config/external-key.rsa" # Wait for apiserver to be up. To speed startup, this only retries connection errors; # without `--fail-with-body` curl still returns 0 for well-formed 4xx or 5xx responses. sleep(1) until sh?( 'curl --cacert test/config/external-ca.pem ' \ '--key test/config/external-key.rsa ' \ '--cert test/config/external-cert.pem https://127.0.0.1:6443/healthz' ) sh! 'env KUBECLIENT_TEST_REAL_CLUSTER=true bundle exec rake test' sh! "#{DOCKER} rm -f #{CONTAINER}" puts 'If you run this only for tests, cleanup by running: git restore test/config/' kubeclient-4.11.0/test/config/userauth.kubeconfig000066400000000000000000000011431435070561000220440ustar00rootroot00000000000000apiVersion: v1 clusters: - cluster: server: https://localhost:6443 name: localhost:6443 contexts: - context: cluster: localhost:6443 namespace: default user: system:admin:token name: localhost/system:admin:token - context: cluster: localhost:6443 namespace: default user: system:admin:userpass name: localhost/system:admin:userpass current-context: localhost/system:admin:token kind: Config preferences: {} users: - name: system:admin:token user: token: 0123456789ABCDEF0123456789ABCDEF - name: system:admin:userpass user: username: admin password: pAssw0rd123 kubeclient-4.11.0/test/json/000077500000000000000000000000001435070561000156535ustar00rootroot00000000000000kubeclient-4.11.0/test/json/bindings_list.json000066400000000000000000000003161435070561000213760ustar00rootroot00000000000000{ "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "the server could not find the requested resource", "reason": "NotFound", "details": {}, "code": 404 }kubeclient-4.11.0/test/json/component_status.json000066400000000000000000000005211435070561000221510ustar00rootroot00000000000000{ "kind": "ComponentStatus", "apiVersion": "v1", "metadata": { "name": "etcd-0", "selfLink": "/api/v1/namespaces/componentstatuses/etcd-0", "creationTimestamp": null }, "conditions": [ { "type": "Healthy", "status": "True", "message": "{\"health\": \"true\"}", "error": "nil" } ] }kubeclient-4.11.0/test/json/component_status_list.json000066400000000000000000000023771435070561000232170ustar00rootroot00000000000000{ "kind": "ComponentStatusList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/componentstatuses" }, "items": [ { "metadata": { "name": "controller-manager", "selfLink": "/api/v1/namespaces/componentstatuses/controller-manager", "creationTimestamp": null }, "conditions": [ { "type": "Healthy", "status": "Unknown", "error": "Get http://127.0.0.1:10252/healthz: dial tcp 127.0.0.1:10252: connection refused" } ] }, { "metadata": { "name": "scheduler", "selfLink": "/api/v1/namespaces/componentstatuses/scheduler", "creationTimestamp": null }, "conditions": [ { "type": "Healthy", "status": "Unknown", "error": "Get http://127.0.0.1:10251/healthz: dial tcp 127.0.0.1:10251: connection refused" } ] }, { "metadata": { "name": "etcd-0", "selfLink": "/api/v1/namespaces/componentstatuses/etcd-0", "creationTimestamp": null }, "conditions": [ { "type": "Healthy", "status": "True", "message": "{\"health\": \"true\"}", "error": "nil" } ] } ] }kubeclient-4.11.0/test/json/config.istio.io_api_resource_list.json000066400000000000000000000307071435070561000253510ustar00rootroot00000000000000{ "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "config.istio.io/v1alpha2", "resources": [ { "name": "metrics", "singularName": "metric", "namespaced": true, "kind": "metric", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "servicecontrolreports", "singularName": "servicecontrolreport", "namespaced": true, "kind": "servicecontrolreport", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "opas", "singularName": "opa", "namespaced": true, "kind": "opa", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "redisquotas", "singularName": "redisquota", "namespaced": true, "kind": "redisquota", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "authorizations", "singularName": "authorization", "namespaced": true, "kind": "authorization", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "listentries", "singularName": "listentry", "namespaced": true, "kind": "listentry", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "logentries", "singularName": "logentry", "namespaced": true, "kind": "logentry", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "tracespans", "singularName": "tracespan", "namespaced": true, "kind": "tracespan", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "quotaspecs", "singularName": "quotaspec", "namespaced": true, "kind": "QuotaSpec", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "fluentds", "singularName": "fluentd", "namespaced": true, "kind": "fluentd", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "rbacs", "singularName": "rbac", "namespaced": true, "kind": "rbac", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "checknothings", "singularName": "checknothing", "namespaced": true, "kind": "checknothing", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "edges", "singularName": "edge", "namespaced": true, "kind": "edge", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "apikeys", "singularName": "apikey", "namespaced": true, "kind": "apikey", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "kuberneteses", "singularName": "kubernetes", "namespaced": true, "kind": "kubernetes", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "httpapispecs", "singularName": "httpapispec", "namespaced": true, "kind": "HTTPAPISpec", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "attributemanifests", "singularName": "attributemanifest", "namespaced": true, "kind": "attributemanifest", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "kubernetesenvs", "singularName": "kubernetesenv", "namespaced": true, "kind": "kubernetesenv", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "listcheckers", "singularName": "listchecker", "namespaced": true, "kind": "listchecker", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "quotas", "singularName": "quota", "namespaced": true, "kind": "quota", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "instances", "singularName": "instance", "namespaced": true, "kind": "instance", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "memquotas", "singularName": "memquota", "namespaced": true, "kind": "memquota", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "noops", "singularName": "noop", "namespaced": true, "kind": "noop", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "prometheuses", "singularName": "prometheus", "namespaced": true, "kind": "prometheus", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "solarwindses", "singularName": "solarwinds", "namespaced": true, "kind": "solarwinds", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "cloudwatches", "singularName": "cloudwatch", "namespaced": true, "kind": "cloudwatch", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "reportnothings", "singularName": "reportnothing", "namespaced": true, "kind": "reportnothing", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "stackdrivers", "singularName": "stackdriver", "namespaced": true, "kind": "stackdriver", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "statsds", "singularName": "statsd", "namespaced": true, "kind": "statsd", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "httpapispecbindings", "singularName": "httpapispecbinding", "namespaced": true, "kind": "HTTPAPISpecBinding", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "quotaspecbindings", "singularName": "quotaspecbinding", "namespaced": true, "kind": "QuotaSpecBinding", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "bypasses", "singularName": "bypass", "namespaced": true, "kind": "bypass", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "circonuses", "singularName": "circonus", "namespaced": true, "kind": "circonus", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "deniers", "singularName": "denier", "namespaced": true, "kind": "denier", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "signalfxs", "singularName": "signalfx", "namespaced": true, "kind": "signalfx", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "adapters", "singularName": "adapter", "namespaced": true, "kind": "adapter", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "servicecontrols", "singularName": "servicecontrol", "namespaced": true, "kind": "servicecontrol", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "templates", "singularName": "template", "namespaced": true, "kind": "template", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "handlers", "singularName": "handler", "namespaced": true, "kind": "handler", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "rules", "singularName": "rule", "namespaced": true, "kind": "rule", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "dogstatsds", "singularName": "dogstatsd", "namespaced": true, "kind": "dogstatsd", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] }, { "name": "stdios", "singularName": "stdio", "namespaced": true, "kind": "stdio", "verbs": [ "delete", "deletecollection", "get", "list", "patch", "create", "update", "watch" ] } ] }kubeclient-4.11.0/test/json/config_map_list.json000066400000000000000000000002321435070561000217000ustar00rootroot00000000000000{ "kind": "ConfigMapList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/configmaps", "resourceVersion": "665" }, "items": [] }kubeclient-4.11.0/test/json/core_api_resource_list.json000066400000000000000000000066421435070561000233010ustar00rootroot00000000000000{ "kind": "APIResourceList", "groupVersion": "v1", "resources": [ { "name": "bindings", "namespaced": true, "kind": "Binding" }, { "name": "componentstatuses", "namespaced": false, "kind": "ComponentStatus" }, { "name": "configmaps", "namespaced": true, "kind": "ConfigMap" }, { "name": "endpoints", "namespaced": true, "kind": "Endpoints" }, { "name": "events", "namespaced": true, "kind": "Event" }, { "name": "limitranges", "namespaced": true, "kind": "LimitRange" }, { "name": "namespaces", "namespaced": false, "kind": "Namespace" }, { "name": "namespaces/finalize", "namespaced": false, "kind": "Namespace" }, { "name": "namespaces/status", "namespaced": false, "kind": "Namespace" }, { "name": "nodes", "namespaced": false, "kind": "Node" }, { "name": "nodes/proxy", "namespaced": false, "kind": "Node" }, { "name": "nodes/status", "namespaced": false, "kind": "Node" }, { "name": "persistentvolumeclaims", "namespaced": true, "kind": "PersistentVolumeClaim" }, { "name": "persistentvolumeclaims/status", "namespaced": true, "kind": "PersistentVolumeClaim" }, { "name": "persistentvolumes", "namespaced": false, "kind": "PersistentVolume" }, { "name": "persistentvolumes/status", "namespaced": false, "kind": "PersistentVolume" }, { "name": "pods", "namespaced": true, "kind": "Pod" }, { "name": "pods/attach", "namespaced": true, "kind": "Pod" }, { "name": "pods/binding", "namespaced": true, "kind": "Binding" }, { "name": "pods/exec", "namespaced": true, "kind": "Pod" }, { "name": "pods/log", "namespaced": true, "kind": "Pod" }, { "name": "pods/portforward", "namespaced": true, "kind": "Pod" }, { "name": "pods/proxy", "namespaced": true, "kind": "Pod" }, { "name": "pods/status", "namespaced": true, "kind": "Pod" }, { "name": "podtemplates", "namespaced": true, "kind": "PodTemplate" }, { "name": "replicationcontrollers", "namespaced": true, "kind": "ReplicationController" }, { "name": "replicationcontrollers/scale", "namespaced": true, "kind": "Scale" }, { "name": "replicationcontrollers/status", "namespaced": true, "kind": "ReplicationController" }, { "name": "resourcequotas", "namespaced": true, "kind": "ResourceQuota" }, { "name": "resourcequotas/status", "namespaced": true, "kind": "ResourceQuota" }, { "name": "secrets", "namespaced": true, "kind": "Secret" }, { "name": "serviceaccounts", "namespaced": true, "kind": "ServiceAccount" }, { "name": "services", "namespaced": true, "kind": "Service" }, { "name": "services/proxy", "namespaced": true, "kind": "Service" }, { "name": "services/status", "namespaced": true, "kind": "Service" } ] } kubeclient-4.11.0/test/json/core_api_resource_list_without_kind.json000066400000000000000000000042711435070561000260650ustar00rootroot00000000000000{ "groupVersion": "v1", "resources": [ { "name": "bindings", "namespaced": true }, { "name": "componentstatuses", "namespaced": true }, { "name": "endpoints", "namespaced": true }, { "name": "events", "namespaced": true }, { "name": "limitranges", "namespaced": true }, { "name": "namespaces", "namespaced": false }, { "name": "namespaces/finalize", "namespaced": false }, { "name": "namespaces/status", "namespaced": false }, { "name": "nodes", "namespaced": false }, { "name": "nodes/status", "namespaced": false }, { "name": "persistentvolumeclaims", "namespaced": true }, { "name": "persistentvolumeclaims/status", "namespaced": true }, { "name": "persistentvolumes", "namespaced": false }, { "name": "persistentvolumes/status", "namespaced": false }, { "name": "pods", "namespaced": true }, { "name": "pods/attach", "namespaced": true }, { "name": "pods/binding", "namespaced": true }, { "name": "pods/exec", "namespaced": true }, { "name": "pods/log", "namespaced": true }, { "name": "pods/portforward", "namespaced": true }, { "name": "pods/proxy", "namespaced": true }, { "name": "pods/status", "namespaced": true }, { "name": "podtemplates", "namespaced": true }, { "name": "replicationcontrollers", "namespaced": true }, { "name": "replicationcontrollers/status", "namespaced": true }, { "name": "resourcequotas", "namespaced": true }, { "name": "resourcequotas/status", "namespaced": true }, { "name": "secrets", "namespaced": true }, { "name": "securitycontextconstraints", "namespaced": false }, { "name": "serviceaccounts", "namespaced": true }, { "name": "services", "namespaced": true } ] } kubeclient-4.11.0/test/json/core_oapi_resource_list_without_kind.json000066400000000000000000000066571435070561000262560ustar00rootroot00000000000000{ "groupVersion": "v1", "resources": [ { "name": "buildconfigs", "namespaced": true }, { "name": "buildconfigs/instantiate", "namespaced": true }, { "name": "buildconfigs/instantiatebinary", "namespaced": true }, { "name": "buildconfigs/webhooks", "namespaced": true }, { "name": "builds", "namespaced": true }, { "name": "builds/clone", "namespaced": true }, { "name": "builds/details", "namespaced": true }, { "name": "builds/log", "namespaced": true }, { "name": "clusternetworks", "namespaced": false }, { "name": "clusterpolicies", "namespaced": false }, { "name": "clusterpolicybindings", "namespaced": false }, { "name": "clusterrolebindings", "namespaced": false }, { "name": "clusterroles", "namespaced": false }, { "name": "deploymentconfigrollbacks", "namespaced": true }, { "name": "deploymentconfigs", "namespaced": true }, { "name": "deploymentconfigs/log", "namespaced": true }, { "name": "deploymentconfigs/scale", "namespaced": true }, { "name": "generatedeploymentconfigs", "namespaced": true }, { "name": "groups", "namespaced": false }, { "name": "hostsubnets", "namespaced": false }, { "name": "identities", "namespaced": false }, { "name": "images", "namespaced": false }, { "name": "imagestreamimages", "namespaced": true }, { "name": "imagestreammappings", "namespaced": true }, { "name": "imagestreams", "namespaced": true }, { "name": "imagestreams/status", "namespaced": true }, { "name": "imagestreamtags", "namespaced": true }, { "name": "localresourceaccessreviews", "namespaced": true }, { "name": "localsubjectaccessreviews", "namespaced": true }, { "name": "netnamespaces", "namespaced": false }, { "name": "oauthaccesstokens", "namespaced": false }, { "name": "oauthauthorizetokens", "namespaced": false }, { "name": "oauthclientauthorizations", "namespaced": false }, { "name": "oauthclients", "namespaced": false }, { "name": "policies", "namespaced": true }, { "name": "policybindings", "namespaced": true }, { "name": "processedtemplates", "namespaced": true }, { "name": "projectrequests", "namespaced": false }, { "name": "projects", "namespaced": false }, { "name": "resourceaccessreviews", "namespaced": true }, { "name": "rolebindings", "namespaced": true }, { "name": "roles", "namespaced": true }, { "name": "routes", "namespaced": true }, { "name": "routes/status", "namespaced": true }, { "name": "subjectaccessreviews", "namespaced": true }, { "name": "templates", "namespaced": true }, { "name": "useridentitymappings", "namespaced": false }, { "name": "users", "namespaced": false } ] } kubeclient-4.11.0/test/json/created_endpoint.json000066400000000000000000000010521435070561000220530ustar00rootroot00000000000000{ "kind": "Endpoints", "apiVersion": "v1", "metadata": { "name": "myendpoint", "namespace": "default", "selfLink": "/api/v1/namespaces/default/endpoints/myendpoint", "uid": "59d05b48-dadb-11e5-937e-18037327aaeb", "resourceVersion": "393", "creationTimestamp": "2016-02-24T09:45:34Z" }, "subsets": [ { "addresses": [ { "ip": "172.17.0.25" } ], "ports": [ { "name": "https", "port": 6443, "protocol": "TCP" } ] } ] }kubeclient-4.11.0/test/json/created_namespace.json000066400000000000000000000005061435070561000221720ustar00rootroot00000000000000{ "kind": "Namespace", "apiVersion": "v1", "metadata": { "name": "development", "selfLink": "/api/v1/namespaces/development", "uid": "13d820d6-df5b-11e4-bd42-f8b156af4ae1", "resourceVersion": "2533", "creationTimestamp": "2015-04-10T08:24:59Z" }, "spec": { "finalizers": [ "kubernetes" ] }, "status": { "phase": "Active" } } kubeclient-4.11.0/test/json/created_secret.json000066400000000000000000000006131435070561000215220ustar00rootroot00000000000000{ "kind": "Secret", "apiVersion": "v1", "metadata": { "name": "test-secret", "namespace": "dev", "selfLink": "/api/v1/namespaces/dev/secrets/test-secret", "uid": "4e38a198-2bcb-11e5-a483-0e840567604d", "resourceVersion": "245569", "creationTimestamp": "2015-07-16T14:59:49Z" }, "data": { "super-secret": "Y2F0J3MgYXJlIGF3ZXNvbWUK" }, "type": "Opaque" } kubeclient-4.11.0/test/json/created_security_context_constraint.json000066400000000000000000000031151435070561000261140ustar00rootroot00000000000000{ "allowHostDirVolumePlugin": false, "allowHostIPC": false, "allowHostNetwork": false, "allowHostPID": false, "allowHostPorts": false, "allowPrivilegedContainer": false, "allowedCapabilities": null, "allowedFlexVolumes": null, "apiVersion": "security.openshift.io/v1", "defaultAddCapabilities": null, "fsGroup": { "type": "RunAsAny" }, "groups": [], "kind": "SecurityContextConstraints", "metadata": { "creationTimestamp": "2018-11-23T10:01:42Z", "name": "teleportation", "resourceVersion": "5274", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/teleportation", "uid": "c6e8e2ec-ef06-11e8-b4c0-68f728fac3ab" }, "priority": null, "readOnlyRootFilesystem": false, "requiredDropCapabilities": null, "runAsUser": { "type": "MustRunAs" }, "seLinuxContext": { "type": "MustRunAs" }, "supplementalGroups": { "type": "RunAsAny" }, "users": [], "volumes": [ "awsElasticBlockStore", "azureDisk", "azureFile", "cephFS", "cinder", "configMap", "downwardAPI", "emptyDir", "fc", "flexVolume", "flocker", "gcePersistentDisk", "gitRepo", "glusterfs", "iscsi", "nfs", "persistentVolumeClaim", "photonPersistentDisk", "portworxVolume", "projected", "quobyte", "rbd", "scaleIO", "secret", "storageOS", "vsphere" ] } kubeclient-4.11.0/test/json/created_service.json000066400000000000000000000014101435070561000216710ustar00rootroot00000000000000{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "guestbook", "namespace": "staging", "selfLink": "/api/v1/namespaces/staging/services/guestbook", "uid": "29885239-df58-11e4-bd42-f8b156af4ae1", "resourceVersion": "1908", "creationTimestamp": "2015-04-10T08:04:07Z", "labels": { "name": "guestbook" } }, "spec": { "ports": [ { "name": "", "protocol": "TCP", "port": 3000, "targetPort": "http-server" } ], "selector": { "name": "guestbook" }, "clusterIP": "10.0.0.99", "sessionAffinity": "None" }, "status": {} }kubeclient-4.11.0/test/json/empty_pod_list.json000066400000000000000000000002221435070561000215750ustar00rootroot00000000000000{ "kind": "PodList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/pods", "resourceVersion": "565" }, "items": [] }kubeclient-4.11.0/test/json/endpoint_list.json000066400000000000000000000022141435070561000214200ustar00rootroot00000000000000{ "kind": "EndpointsList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/namespaces/endpoints", "resourceVersion": "39" }, "items": [ { "metadata": { "name": "example", "namespace": "default", "selfLink": "/api/v1/namespaces/default/endpoints/example", "uid": "db467530-b6aa-11e4-974a-525400c903c1", "resourceVersion": "38", "creationTimestamp": "2015-02-17T08:42:46-05:00" }, "endpoints": [ "172.17.0.63:80", "172.17.0.64:80" ] }, { "metadata": { "name": "kubernetes", "namespace": "default", "selfLink": "/api/v1/namespaces/default/endpoints/kubernetes", "resourceVersion": "8", "creationTimestamp": null }, "endpoints": [ "192.168.122.4:6443" ] }, { "metadata": { "name": "kubernetes-ro", "namespace": "default", "selfLink": "/api/v1/namespaces/default/endpoints/kubernetes-ro", "resourceVersion": "7", "creationTimestamp": null }, "endpoints": [ "192.168.122.4:7080" ] } ] } kubeclient-4.11.0/test/json/entity_list.json000066400000000000000000000033641435070561000211230ustar00rootroot00000000000000{ "kind": "ServiceList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/services", "resourceVersion": "59" }, "items": [ { "metadata": { "name": "kubernetes", "namespace": "default", "selfLink": "/api/v1/services/kubernetes?namespace=default", "uid": "016e9dcd-ce39-11e4-ac24-3c970e4a436a", "resourceVersion": "6", "creationTimestamp": "2015-03-19T15:08:16+02:00", "labels": { "component": "apiserver", "provider": "kubernetes" } }, "spec": { "port": 443, "protocol": "TCP", "selector": null, "clusterIP": "10.0.0.2", "containerPort": 0, "sessionAffinity": "None" }, "status": {} }, { "metadata": { "name": "kubernetes-ro", "namespace": "default", "selfLink": "/api/v1/services/kubernetes-ro?namespace=default", "uid": "015b78bf-ce39-11e4-ac24-3c970e4a436a", "resourceVersion": "5", "creationTimestamp": "2015-03-19T15:08:15+02:00", "labels": { "component": "apiserver", "provider": "kubernetes" } }, "spec": { "port": 80, "protocol": "TCP", "selector": null, "clusterIP": "10.0.0.1", "containerPort": 0, "sessionAffinity": "None" }, "status": {} } ] }kubeclient-4.11.0/test/json/event_list.json000066400000000000000000000022611435070561000207230ustar00rootroot00000000000000{ "kind": "EventList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/events", "resourceVersion": "152" }, "items": [ { "metadata": { "name": "php.13c130f78da4641e", "namespace": "default", "selfLink": "/api/v1/events/php.13c130f78da4641e?namespace=default", "uid": "f3a454d2-b03a-11e4-89e4-525400c903c1", "resourceVersion": "178", "creationTimestamp": "2015-02-09T04:06:37-05:00" }, "involvedObject": { "kind": "BoundPod", "namespace": "default", "name": "php", "uid": "64273d20-b03a-11e4-89e4-525400c903c1", "apiVersion": "v1beta1", "fieldPath": "spec.containers{nginx}" }, "reason": "created", "message": "Created with docker id 9ba2a714411d2d0dd1e826b2fe5c3222b5cbfd9dd9133c841585cbb96b8c2c0f", "source": { "component": "kubelet", "host": "127.0.0.1" }, "timestamp": "2015-02-09T04:06:37-05:00" } ] } kubeclient-4.11.0/test/json/extensions_v1beta1_api_resource_list.json000066400000000000000000000076511435070561000260740ustar00rootroot00000000000000{ "kind": "APIResourceList", "groupVersion": "extensions/v1beta1", "resources": [ { "name": "daemonsets", "singularName": "", "namespaced": true, "kind": "DaemonSet", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "ds" ] }, { "name": "daemonsets/status", "singularName": "", "namespaced": true, "kind": "DaemonSet", "verbs": [ "get", "patch", "update" ] }, { "name": "deployments", "singularName": "", "namespaced": true, "kind": "Deployment", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "deploy" ] }, { "name": "deployments/rollback", "singularName": "", "namespaced": true, "kind": "DeploymentRollback", "verbs": [ "create" ] }, { "name": "deployments/scale", "singularName": "", "namespaced": true, "group": "extensions", "version": "v1beta1", "kind": "Scale", "verbs": [ "get", "patch", "update" ] }, { "name": "deployments/status", "singularName": "", "namespaced": true, "kind": "Deployment", "verbs": [ "get", "patch", "update" ] }, { "name": "ingresses", "singularName": "", "namespaced": true, "kind": "Ingress", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "ing" ] }, { "name": "ingresses/status", "singularName": "", "namespaced": true, "kind": "Ingress", "verbs": [ "get", "patch", "update" ] }, { "name": "networkpolicies", "singularName": "", "namespaced": true, "kind": "NetworkPolicy", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "netpol" ] }, { "name": "podsecuritypolicies", "singularName": "", "namespaced": false, "kind": "PodSecurityPolicy", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "psp" ] }, { "name": "replicasets", "singularName": "", "namespaced": true, "kind": "ReplicaSet", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "rs" ] }, { "name": "replicasets/scale", "singularName": "", "namespaced": true, "group": "extensions", "version": "v1beta1", "kind": "Scale", "verbs": [ "get", "patch", "update" ] }, { "name": "replicasets/status", "singularName": "", "namespaced": true, "kind": "ReplicaSet", "verbs": [ "get", "patch", "update" ] }, { "name": "replicationcontrollers", "singularName": "", "namespaced": true, "kind": "ReplicationControllerDummy", "verbs": [] }, { "name": "replicationcontrollers/scale", "singularName": "", "namespaced": true, "kind": "Scale", "verbs": [ "get", "patch", "update" ] } ] }kubeclient-4.11.0/test/json/limit_range.json000066400000000000000000000007601435070561000210430ustar00rootroot00000000000000{ "kind": "LimitRange", "apiVersion": "v1", "metadata": { "name": "limits", "namespace": "quota-example", "selfLink": "/api/v1/namespaces/quota-example/limitranges/limits", "uid": "7a76a44c-3e9d-11e5-8214-0aaeec44370e", "resourceVersion": "103384", "creationTimestamp": "2015-08-09T13:49:39Z" }, "spec": { "limits": [ { "type": "Container", "default": { "cpu": "100m", "memory": "512Mi" } } ] } }kubeclient-4.11.0/test/json/limit_range_list.json000066400000000000000000000013231435070561000220720ustar00rootroot00000000000000{ "kind": "LimitRangeList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/namespaces/quota-example/limitranges/", "resourceVersion": "103421" }, "items": [ { "metadata": { "name": "limits", "namespace": "quota-example", "selfLink": "/api/v1/namespaces/quota-example/limitranges/limits", "uid": "7a76a44c-3e9d-11e5-8214-0aaeec44370e", "resourceVersion": "103384", "creationTimestamp": "2015-08-09T13:49:39Z" }, "spec": { "limits": [ { "type": "Container", "default": { "cpu": "100m", "memory": "512Mi" } } ] } } ] }kubeclient-4.11.0/test/json/namespace.json000066400000000000000000000005161435070561000205040ustar00rootroot00000000000000{ "kind": "Namespace", "apiVersion": "v1", "metadata": { "name": "staging", "selfLink": "/api/v1/namespaces/staging", "uid": "e388bc10-c021-11e4-a514-3c970e4a436a", "resourceVersion": "1168", "creationTimestamp": "2015-03-01T16:47:31+02:00" }, "spec": {}, "status": {} }kubeclient-4.11.0/test/json/namespace_exception.json000066400000000000000000000002711435070561000225600ustar00rootroot00000000000000{ "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "converting to : type names don't match (Pod, Namespace)", "code": 500 }kubeclient-4.11.0/test/json/namespace_list.json000066400000000000000000000016341435070561000215410ustar00rootroot00000000000000{ "kind": "NamespaceList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/namespaces", "resourceVersion": "1707" }, "items": [ { "metadata": { "name": "default", "selfLink": "/api/v1/namespaces/default", "uid": "56c3eb7c-c009-11e4-a514-3c970e4a436a", "resourceVersion": "4", "creationTimestamp": "2015-03-01T13:51:47+02:00" }, "spec": {}, "status": {} }, { "metadata": { "name": "staging", "selfLink": "/api/v1/namespaces/staging", "uid": "e388bc10-c021-11e4-a514-3c970e4a436a", "resourceVersion": "1168", "creationTimestamp": "2015-03-01T16:47:31+02:00" }, "spec": {}, "status": {} } ] }kubeclient-4.11.0/test/json/node.json000066400000000000000000000014461435070561000175000ustar00rootroot00000000000000{ "kind": "Node", "apiVersion": "v1", "metadata": { "name": "127.0.0.1", "selfLink": "/api/v1/nodes/127.0.0.1", "uid": "041143c5-ce39-11e4-ac24-3c970e4a436a", "resourceVersion": "1724", "creationTimestamp": "2015-03-19T15:08:20+02:00" }, "spec": { "capacity": { "cpu": "1", "memory": "3Gi" } }, "status": { "hostIP": "127.0.0.1", "conditions": [ { "kind": "Ready", "status": "None", "lastProbeTime": "2015-03-20T14:16:52+02:00", "lastTransitionTime": "2015-03-19T15:08:20+02:00", "reason": "Node health check failed: kubelet /healthz endpoint returns not ok" } ] } }kubeclient-4.11.0/test/json/node_list.json000066400000000000000000000021711435070561000205270ustar00rootroot00000000000000{ "kind": "NodeList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/nodes", "resourceVersion": "137" }, "items": [ { "metadata": { "name": "127.0.0.1", "selfLink": "/api/v1/nodes/127.0.0.1", "uid": "041143c5-ce39-11e4-ac24-3c970e4a436a", "resourceVersion": "137", "creationTimestamp": "2015-03-19T15:08:20+02:00" }, "spec": { "capacity": { "cpu": "1", "memory": "3Gi" } }, "status": { "hostIP": "127.0.0.1", "conditions": [ { "kind": "Ready", "status": "None", "lastProbeTime": "2015-03-19T15:29:33+02:00", "lastTransitionTime": "2015-03-19T15:08:20+02:00", "reason": "Node health check failed: kubelet /healthz endpoint returns not ok" } ] } } ] }kubeclient-4.11.0/test/json/node_notice.json000066400000000000000000000120651435070561000210400ustar00rootroot00000000000000{ "type": "ADDED", "object": { "apiVersion": "v1", "kind": "Node", "metadata": { "annotations": { "volumes.kubernetes.io/controller-managed-attach-detach": "true" }, "creationTimestamp": "2017-12-11T12:00:13Z", "labels": { "beta.kubernetes.io/arch": "amd64", "beta.kubernetes.io/os": "linux", "kubernetes.io/hostname": "openshift.local", "openshift-infra": "apiserver" }, "name": "openshift.local", "resourceVersion": "367410", "selfLink": "/api/v1/nodes/openshift.local", "uid": "d88c7af6-de6a-11e7-8725-52540080f1d2" }, "spec": { "externalID": "openshift.local" }, "status": { "addresses": [ { "address": "192.168.122.40", "type": "InternalIP" }, { "address": "openshift.local", "type": "Hostname" } ], "allocatable": { "cpu": "2", "memory": "8072896Ki", "pods": "20" }, "capacity": { "cpu": "2", "memory": "8175296Ki", "pods": "20" }, "conditions": [ { "lastHeartbeatTime": "2017-12-15T00:36:13Z", "lastTransitionTime": "2017-12-11T12:00:13Z", "message": "kubelet has sufficient disk space available", "reason": "KubeletHasSufficientDisk", "status": "False", "type": "OutOfDisk" }, { "lastHeartbeatTime": "2017-12-15T00:36:13Z", "lastTransitionTime": "2017-12-11T12:00:13Z", "message": "kubelet has sufficient memory available", "reason": "KubeletHasSufficientMemory", "status": "False", "type": "MemoryPressure" }, { "lastHeartbeatTime": "2017-12-15T00:36:13Z", "lastTransitionTime": "2017-12-11T12:00:13Z", "message": "kubelet has no disk pressure", "reason": "KubeletHasNoDiskPressure", "status": "False", "type": "DiskPressure" }, { "lastHeartbeatTime": "2017-12-15T00:36:13Z", "lastTransitionTime": "2017-12-14T15:43:39Z", "message": "kubelet is posting ready status", "reason": "KubeletReady", "status": "True", "type": "Ready" } ], "daemonEndpoints": { "kubeletEndpoint": { "Port": 10250 } }, "images": [ { "names": [ "docker.io/openshift/origin@sha256:908c6c9ccf0e0feefe2658899656c6e73d2854777fa340738fb903f0a40c328d", "docker.io/openshift/origin:latest" ], "sizeBytes": 1222636603 }, { "names": [ "docker.io/openshift/origin-deployer@sha256:3d324bce1870047edc418041cefdec88e0a5bbb5b3b9f6fd35b43f14919a656c", "docker.io/openshift/origin-deployer:v3.7.0" ], "sizeBytes": 1098951248 }, { "names": [ "docker.io/cockpit/kubernetes@sha256:a8e58cd5e6f5a4d12d1e2dfd339686b74f3c22586952ca7aa184dc254ab49714", "docker.io/cockpit/kubernetes:latest" ], "sizeBytes": 375926556 }, { "names": [ "docker.io/cockpit/kubernetes@sha256:0745b3823efc57e03a5ef378614dfcb6c2b1e3964220bbf908fb3046a91cef70" ], "sizeBytes": 350062743 }, { "names": [ "docker.io/openshift/origin-service-catalog@sha256:ef851e06276af96838a93320d0e4be51cc8de6e5afb2fb0efd4e56cec114b937" ], "sizeBytes": 284732029 }, { "names": [ "docker.io/openshift/origin-service-catalog@sha256:8addfd742d92d8da819b091d6bda40edc45e88d1446ffd1ad658b6d21b3c36fd" ], "sizeBytes": 284731998 }, { "names": [ "docker.io/openshift/origin-service-catalog@sha256:b3a737cc346b3cae85ef2f5d020b607781a1cac38fe70678cb78fee2c2a3bf8a" ], "sizeBytes": 284731943 }, { "names": [ "docker.io/openshift/origin-service-catalog@sha256:957934537721da33362693d4f1590dc79dc5da7438799bf14d645165768e53ef", "docker.io/openshift/origin-service-catalog:latest" ], "sizeBytes": 283929631 }, { "names": [ "docker.io/openshift/origin-pod@sha256:2c257d83a01607b229ef5e3dca09f52c3a2a2788c09dc33f0444ec4e572a9e1d", "docker.io/openshift/origin-pod:v3.7.0" ], "sizeBytes": 218423400 } ], "nodeInfo": { "architecture": "amd64", "bootID": "75be791d-88a2-4f56-a588-c071a80bf7cf", "containerRuntimeVersion": "docker://1.12.6", "kernelVersion": "3.10.0-693.11.1.el7.x86_64", "kubeProxyVersion": "v1.7.6+a08f5eeb62", "kubeletVersion": "v1.7.6+a08f5eeb62", "machineID": "adf09ffc2de2624aa5ed335727c7400d", "operatingSystem": "linux", "osImage": "CentOS Linux 7 (Core)", "systemUUID": "FC9FF0AD-E22D-4A62-A5ED-335727C7400D" } } } } kubeclient-4.11.0/test/json/persistent_volume.json000066400000000000000000000014541435070561000223410ustar00rootroot00000000000000{ "kind": "PersistentVolume", "apiVersion": "v1", "metadata": { "name": "pv0001", "selfLink": "/api/v1/persistentvolumes/pv0001", "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe", "resourceVersion": "1585", "creationTimestamp": "2015-08-25T14:51:35Z", "labels": { "type": "local" } }, "spec": { "capacity": { "storage": "10Gi" }, "hostPath": { "path": "/tmp/data01" }, "accessModes": [ "ReadWriteOnce" ], "claimRef": { "kind": "PersistentVolumeClaim", "namespace": "default", "name": "myclaim-1", "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", "apiVersion": "v1", "resourceVersion": "1582" }, "persistentVolumeReclaimPolicy": "Retain" }, "status": { "phase": "Bound" } } kubeclient-4.11.0/test/json/persistent_volume_claim.json000066400000000000000000000012171435070561000235030ustar00rootroot00000000000000{ "kind": "PersistentVolumeClaim", "apiVersion": "v1", "metadata": { "name": "myclaim-1", "namespace": "default", "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1", "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", "resourceVersion": "1584", "creationTimestamp": "2015-08-25T14:51:55Z" }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "3Gi" } }, "volumeName": "pv0001" }, "status": { "phase": "Bound", "accessModes": [ "ReadWriteOnce" ], "capacity": { "storage": "10Gi" } } } kubeclient-4.11.0/test/json/persistent_volume_claim_list.json000066400000000000000000000016051435070561000245370ustar00rootroot00000000000000{ "kind": "PersistentVolumeClaimList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/persistentvolumeclaims", "resourceVersion": "3188" }, "items": [ { "metadata": { "name": "myclaim-1", "namespace": "default", "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1", "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", "resourceVersion": "1584", "creationTimestamp": "2015-08-25T14:51:55Z" }, "spec": { "accessModes": [ "ReadWriteOnce" ], "resources": { "requests": { "storage": "3Gi" } }, "volumeName": "pv0001" }, "status": { "phase": "Bound", "accessModes": [ "ReadWriteOnce" ], "capacity": { "storage": "10Gi" } } } ] } kubeclient-4.11.0/test/json/persistent_volume_claims_nil_items.json000066400000000000000000000002471435070561000257330ustar00rootroot00000000000000{ "kind": "PersistentVolumeClaimList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/persistentvolumeclaims", "resourceVersion": "1089012" } }kubeclient-4.11.0/test/json/persistent_volume_list.json000066400000000000000000000020611435070561000233670ustar00rootroot00000000000000{ "kind": "PersistentVolumeList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/persistentvolumes", "resourceVersion": "2999" }, "items": [ { "metadata": { "name": "pv0001", "selfLink": "/api/v1/persistentvolumes/pv0001", "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe", "resourceVersion": "1585", "creationTimestamp": "2015-08-25T14:51:35Z", "labels": { "type": "local" } }, "spec": { "capacity": { "storage": "10Gi" }, "hostPath": { "path": "/tmp/data01" }, "accessModes": [ "ReadWriteOnce" ], "claimRef": { "kind": "PersistentVolumeClaim", "namespace": "default", "name": "myclaim-1", "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", "apiVersion": "v1", "resourceVersion": "1582" }, "persistentVolumeReclaimPolicy": "Retain" }, "status": { "phase": "Bound" } } ] } kubeclient-4.11.0/test/json/pod.json000066400000000000000000000054561435070561000173420ustar00rootroot00000000000000{ "kind": "Pod", "apiVersion": "v1", "metadata": { "name": "redis-master3", "namespace": "default", "selfLink": "/api/v1/pods/redis-master3?namespace=default", "uid": "a344023f-a23c-11e4-a36b-3c970e4a436a", "resourceVersion": "9", "creationTimestamp": "2015-01-22T15:43:24+02:00", "labels": { "name": "redis-master" } }, "spec": { "volumes": null, "containers": [ { "name": "master", "image": "dockerfile/redis", "ports": [ { "hostPort": 6379, "containerPort": 6379, "protocol": "TCP" } ], "memory": "0", "cpu": "100m", "imagePullPolicy": "" }, { "name": "php-redis", "image": "kubernetes/example-guestbook-php-redis", "ports": [ { "hostPort": 8000, "containerPort": 80, "protocol": "TCP" } ], "memory": "50000000", "cpu": "100m", "imagePullPolicy": "" } ], "restartPolicy": { "always": { } }, "dnsPolicy": "ClusterFirst" }, "status": { "phase": "Running", "host": "127.0.0.1", "podIP": "172.17.0.2", "info": { "master": { "state": { "running": { "startedAt": "2015-01-22T13:43:29Z" } }, "restartCount": 0, "containerID": "docker://87458d9a12f9dc9a01b52c1eee5f09cf48939380271c0eaf31af298ce67b125e", "image": "dockerfile/redis" }, "net": { "state": { "running": { "startedAt": "2015-01-22T13:43:27Z" } }, "restartCount": 0, "containerID": "docker://3bb5ced1f831322d370f70b58137e1dd41216c2960b7a99394542b5230cbd259", "podIP": "172.17.0.2", "image": "kubernetes/pause:latest" }, "php-redis": { "state": { "running": { "startedAt": "2015-01-22T13:43:31Z" } }, "restartCount": 0, "containerID": "docker://5f08685c0a7a5c974d438a52c6560d72bb0aae7e805d2a34302b9b460f1297c7", "image": "kubernetes/example-guestbook-php-redis" } } } } kubeclient-4.11.0/test/json/pod_list.json000066400000000000000000000053401435070561000203650ustar00rootroot00000000000000{ "kind": "PodList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/pods", "resourceVersion": "1315" }, "items": [ { "metadata": { "name": "redis-master3", "namespace": "default", "selfLink": "/api/v1/pods/redis-master3?namespace=default", "uid": "1da148b4-cef5-11e4-ac24-3c970e4a436a", "resourceVersion": "1301", "creationTimestamp": "2015-03-20T13:34:48+02:00", "labels": { "mylabel": "mylabelvalue", "role": "pod" } }, "spec": { "volumes": null, "containers": [ { "name": "master", "image": "dockerfile/redis", "ports": [ { "hostPort": 6379, "containerPort": 6379, "protocol": "TCP" } ], "resources": { "limits": { "cpu": "100m" } }, "terminationMessagePath": "/dev/termination-log", "imagePullPolicy": "IfNotPresent", "securityContext": { "capabilities": {} } }, { "name": "php-redis", "image": "kubernetes/example-guestbook-php-redis", "ports": [ { "hostPort": 8000, "containerPort": 80, "protocol": "TCP" } ], "resources": { "limits": { "cpu": "100m", "memory": "50000000" } }, "terminationMessagePath": "/dev/termination-log", "imagePullPolicy": "IfNotPresent", "securityContext": { "capabilities": {} } } ], "restartPolicy": { "always": {} }, "dnsPolicy": "ClusterFirst" }, "status": { "phase": "Pending" } } ] }kubeclient-4.11.0/test/json/pod_template_list.json000066400000000000000000000002361435070561000222570ustar00rootroot00000000000000{ "kind": "PodTemplateList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/podtemplates", "resourceVersion": "672" }, "items": [] }kubeclient-4.11.0/test/json/pods_1.json000066400000000000000000000163171435070561000177430ustar00rootroot00000000000000{ "kind":"PodList", "apiVersion":"v1", "metadata":{ "selfLink":"/api/v1/pods", "resourceVersion":"53225946", "continue":"eyJ2IjoibWV0YS5rOHMua" }, "items":[ { "metadata":{ "name":"my-ruby-project-2-build", "namespace":"my-project", "selfLink":"/api/v1/namespaces/my-project/pods/my-ruby-project-2-build", "uid":"0c478b50-babb-11e8-ba7e-d094660d31fb", "resourceVersion":"42398462", "creationTimestamp":"2018-09-17T20:48:36Z", "deletionTimestamp":"2018-09-19T13:22:50Z", "deletionGracePeriodSeconds":0, "labels":{ "openshift.io/build.name":"my-ruby-project-2" }, "annotations":{ "openshift.io/build.name":"my-ruby-project-2", "openshift.io/scc":"privileged" }, "ownerReferences":[ { "apiVersion":"build.openshift.io/v1", "kind":"Build", "name":"my-ruby-project-2", "uid":"0c450e6d-babb-11e8-ba7e-d094660d31fb", "controller":true } ], "finalizers":[ "foregroundDeletion" ] }, "spec":{ "volumes":[ { "name":"buildworkdir", "emptyDir":{ } }, { "name":"docker-socket", "hostPath":{ "path":"/var/run/docker.sock", "type":"" } }, { "name":"crio-socket", "hostPath":{ "path":"/var/run/crio/crio.sock", "type":"" } }, { "name":"builder-dockercfg-rjgnm-push", "secret":{ "secretName":"builder-dockercfg-rjgnm", "defaultMode":384 } }, { "name":"builder-token-zkpb6", "secret":{ "secretName":"builder-token-zkpb6", "defaultMode":420 } } ], "containers":[ { "name":"sti-build", "image":"openshift3/ose-sti-builder:v3.9.25", "command":[ "openshift-sti-build" ] } ], "restartPolicy":"Never", "terminationGracePeriodSeconds":30, "dnsPolicy":"ClusterFirst", "nodeSelector":{ "node-role.kubernetes.io/compute":"true" }, "serviceAccountName":"builder", "serviceAccount":"builder", "nodeName":"dell-r430-20.example.com", "securityContext":{ }, "imagePullSecrets":[ { "name":"builder-dockercfg-rjgnm" } ], "schedulerName":"default-scheduler" }, "status":{ "phase":"Failed", "conditions":[ { "type":"Initialized", "status":"True", "lastProbeTime":null, "lastTransitionTime":"2018-09-17T20:48:49Z" }, { "type":"Ready", "status":"False", "lastProbeTime":null, "lastTransitionTime":"2018-09-17T20:49:08Z", "reason":"ContainersNotReady", "message":"containers with unready status: [sti-build]" }, { "type":"PodScheduled", "status":"True", "lastProbeTime":null, "lastTransitionTime":"2018-09-17T20:48:36Z" } ], "hostIP":"10.8.96.55", "podIP":"10.129.0.207", "startTime":"2018-09-17T20:48:36Z", "qosClass":"BestEffort" } }, { "metadata":{ "name":"redis-1-94zxb", "generateName":"redis-1-", "namespace":"customer-logging", "selfLink":"/api/v1/namespaces/customer-logging/pods/redis-1-94zxb", "uid":"a8aea5f4-5f91-11e8-ba7e-d094660d31fb", "resourceVersion":"47622190", "creationTimestamp":"2018-05-24T20:33:03Z", "labels":{ "app":"elastic-log-ripper", "deployment":"redis-1", "deploymentconfig":"redis", "name":"redis" }, "annotations":{ "openshift.io/deployment-config.latest-version":"1", "openshift.io/deployment-config.name":"redis", "openshift.io/deployment.name":"redis-1", "openshift.io/generated-by":"OpenShiftNewApp", "openshift.io/scc":"restricted" }, "ownerReferences":[ { "apiVersion":"v1", "kind":"ReplicationController", "name":"redis-1", "uid":"9e2e46b3-5f91-11e8-ba7e-d094660d31fb", "controller":true, "blockOwnerDeletion":true } ] }, "spec":{ "volumes":[ { "name":"default-token-n2wzs", "secret":{ "secretName":"default-token-n2wzs", "defaultMode":420 } } ], "containers":[ { "name":"redis", "image":"manageiq/redis:latest", "ports":[ { "containerPort":6379, "protocol":"TCP" } ], "resources":{ }, "volumeMounts":[ { "name":"default-token-n2wzs", "readOnly":true, "mountPath":"/var/run/secrets/kubernetes.io/serviceaccount" } ], "terminationMessagePath":"/dev/termination-log", "terminationMessagePolicy":"File", "imagePullPolicy":"Always", "securityContext":{ "capabilities":{ "drop":[ "KILL", "MKNOD", "SETGID", "SETUID" ] }, "runAsUser":1000260000 } } ], "restartPolicy":"Always", "terminationGracePeriodSeconds":30, "dnsPolicy":"ClusterFirst", "nodeSelector":{ "node-role.kubernetes.io/compute":"true" }, "serviceAccountName":"default", "serviceAccount":"default", "nodeName":"dell-r430-20.example.com", "securityContext":{ "seLinuxOptions":{ "level":"s0:c16,c10" }, "fsGroup":1000260000 }, "imagePullSecrets":[ { "name":"default-dockercfg-ck286" } ], "schedulerName":"default-scheduler" }, "status":{ "phase":"Running", "conditions":[ { "type":"Initialized", "status":"True", "lastProbeTime":null, "lastTransitionTime":"2018-05-24T20:33:04Z" }, { "type":"Ready", "status":"True", "lastProbeTime":null, "lastTransitionTime":"2018-09-18T12:06:18Z" }, { "type":"PodScheduled", "status":"True", "lastProbeTime":null, "lastTransitionTime":"2018-05-24T20:33:03Z" } ], "hostIP":"10.8.96.55", "podIP":"10.129.0.222", "startTime":"2018-05-24T20:33:04Z", "qosClass":"BestEffort" } } ] } kubeclient-4.11.0/test/json/pods_2.json000066400000000000000000000063561435070561000177460ustar00rootroot00000000000000{ "kind":"PodList", "apiVersion":"v1", "metadata":{ "selfLink":"/api/v1/pods", "resourceVersion":"53226147" }, "items":[ { "metadata":{ "name":"topological-inventory-persister-9-hznds", "generateName":"topological-inventory-persister-9-", "namespace":"topological-inventory-ci", "selfLink":"/api/v1/namespaces/topological-inventory-ci/pods/topological-inventory-persister-9-hznds", "uid":"0c114dde-d865-11e8-ba7e-d094660d31fb", "resourceVersion":"51987342", "creationTimestamp":"2018-10-25T14:48:34Z", "labels":{ "name":"topological-inventory-persister" } }, "spec":{ "volumes":[ { "name":"default-token-5pdjl", "secret":{ "secretName":"default-token-5pdjl", "defaultMode":420 } } ], "containers":[ { "name":"topological-inventory-persister", "image":"docker-registry.default.svc:5000/topological-inventory-ci/topological-inventory-persister@sha256:0f654ea09e749019cf3bcc4b8ee43b8dd813fcbf487843b917cf190213741927", "resources":{ } } ], "restartPolicy":"Always", "terminationGracePeriodSeconds":30, "dnsPolicy":"ClusterFirst", "nodeSelector":{ "node-role.kubernetes.io/compute":"true" }, "serviceAccountName":"default", "serviceAccount":"default", "nodeName":"dell-r430-20.example.com", "schedulerName":"default-scheduler" }, "status":{ "phase":"Running", "hostIP":"10.8.96.55", "podIP":"10.129.1.108", "startTime":"2018-10-25T14:48:34Z", "qosClass":"BestEffort" } }, { "metadata":{ "name":"topological-inventory-persister-9-vzr6h", "generateName":"topological-inventory-persister-9-", "namespace":"topological-inventory-ci", "selfLink":"/api/v1/namespaces/topological-inventory-ci/pods/topological-inventory-persister-9-vzr6h", "uid":"3065d8ce-d86a-11e8-ba7e-d094660d31fb", "resourceVersion":"51996115", "creationTimestamp":"2018-10-25T15:25:22Z", "labels":{ "name":"topological-inventory-persister" } }, "spec":{ "volumes":null, "containers":[ { "name":"topological-inventory-persister", "image":"docker-registry.default.svc:5000/topological-inventory-ci/topological-inventory-persister@sha256:0f654ea09e749019cf3bcc4b8ee43b8dd813fcbf487843b917cf190213741927", "resources":{ } } ], "restartPolicy":"Always", "terminationGracePeriodSeconds":30, "dnsPolicy":"ClusterFirst", "nodeSelector":{ "node-role.kubernetes.io/compute":"true" }, "serviceAccountName":"default", "serviceAccount":"default", "nodeName":"dell-r430-20.example.com", "schedulerName":"default-scheduler" }, "status":{ "phase":"Running", "hostIP":"10.8.96.55", "podIP":"10.129.1.168", "startTime":"2018-10-25T15:25:22Z", "qosClass":"BestEffort" } } ] } kubeclient-4.11.0/test/json/pods_410.json000066400000000000000000000003661435070561000201040ustar00rootroot00000000000000{ "kind":"Status", "apiVersion":"v1", "metadata":{}, "status":"Failure", "message":"The provided from parameter is too old to display a consistent list result. You must start a new list without the from.", "reason":"Expired", "code":410 } kubeclient-4.11.0/test/json/processed_template.json000066400000000000000000000010631435070561000224300ustar00rootroot00000000000000{ "kind": "Template", "apiVersion": "v1", "metadata": { "name": "my-templtae", "namespace": "default", "selfLink": "/oapi/v1/namespaces/default/processedtemplates/my-templtae", "uid": "2240c61c-8f70-11e5-a806-001a4a231290", "resourceVersion": "1399", "creationTimestamp": "2015-11-20T10:19:07Z" }, "objects": [ { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "test/my-service" } } ], "parameters": [ { "name": "NAME_PREFIX", "value": "test/" } ] } kubeclient-4.11.0/test/json/replication_controller.json000066400000000000000000000031501435070561000233210ustar00rootroot00000000000000{ "kind": "ReplicationController", "apiVersion": "v1", "metadata": { "name": "guestbook-controller", "namespace": "default", "selfLink": "/api/v1/replicationcontrollers/guestbook-controller?namespace=default", "uid": "c71aa4c0-a240-11e4-a265-3c970e4a436a", "resourceVersion": "8", "creationTimestamp": "2015-01-22T16:13:02+02:00", "labels": { "name": "guestbook" } }, "spec": { "replicas": 3, "selector": { "name": "guestbook" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "name": "guestbook" } }, "spec": { "volumes": null, "containers": [ { "name": "guestbook", "image": "kubernetes/guestbook", "ports": [ { "name": "http-server", "containerPort": 3000, "protocol": "TCP" } ], "memory": "0", "cpu": "0m", "imagePullPolicy": "" } ], "restartPolicy": { "always": { } }, "dnsPolicy": "ClusterFirst" } } }, "status": { "replicas": 3 } } kubeclient-4.11.0/test/json/replication_controller_list.json000066400000000000000000000045551435070561000243660ustar00rootroot00000000000000{ "kind": "ReplicationControllerList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/namespaces/default/replicationcontrollers", "resourceVersion": "1636" }, "items": [ { "metadata": { "name": "redis-master-controller", "namespace": "default", "selfLink": "/api/v1/namespaces/default/replicationcontrollers/redis-master-controller?namespace=default", "uid": "108eb547-cefa-11e4-ac24-3c970e4a436a", "resourceVersion": "1631", "creationTimestamp": "2015-03-20T14:10:14+02:00", "labels": { "name": "redis-master" } }, "spec": { "replicas": 1, "selector": { "name": "redis-master" }, "template": { "metadata": { "creationTimestamp": null, "labels": { "app": "redis", "name": "redis-master" } }, "spec": { "volumes": null, "containers": [ { "name": "redis-master", "image": "dockerfile/redis", "ports": [ { "containerPort": 6379, "protocol": "TCP" } ], "resources": {}, "terminationMessagePath": "/dev/termination-log", "imagePullPolicy": "IfNotPresent", "securityContext": { "capabilities": {} } } ], "restartPolicy": { "always": {} }, "dnsPolicy": "ClusterFirst" } } }, "status": { "replicas": 1 } } ] }kubeclient-4.11.0/test/json/resource_quota.json000066400000000000000000000020411435070561000216030ustar00rootroot00000000000000{ "kind": "ResourceQuota", "apiVersion": "v1", "metadata": { "name": "quota", "namespace": "quota-example", "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/quota", "uid": "ab9f24a4-3c43-11e5-8214-0aaeec44370e", "resourceVersion": "12919", "creationTimestamp": "2015-08-06T14:01:44Z" }, "spec": { "hard": { "cpu": "20", "memory": "1Gi", "persistentvolumeclaims": "10", "pods": "10", "replicationcontrollers": "20", "resourcequotas": "1", "secrets": "10", "services": "5" } }, "status": { "hard": { "cpu": "20", "memory": "1Gi", "persistentvolumeclaims": "10", "pods": "10", "replicationcontrollers": "20", "resourcequotas": "1", "secrets": "10", "services": "5" }, "used": { "cpu": "0", "memory": "0", "persistentvolumeclaims": "0", "pods": "0", "replicationcontrollers": "1", "resourcequotas": "1", "secrets": "9", "services": "0" } } }kubeclient-4.11.0/test/json/resource_quota_list.json000066400000000000000000000025431435070561000226450ustar00rootroot00000000000000{ "kind": "ResourceQuotaList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/", "resourceVersion": "102452" }, "items": [ { "metadata": { "name": "quota", "namespace": "quota-example", "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/quota", "uid": "ab9f24a4-3c43-11e5-8214-0aaeec44370e", "resourceVersion": "12919", "creationTimestamp": "2015-08-06T14:01:44Z" }, "spec": { "hard": { "cpu": "20", "memory": "1Gi", "persistentvolumeclaims": "10", "pods": "10", "replicationcontrollers": "20", "resourcequotas": "1", "secrets": "10", "services": "5" } }, "status": { "hard": { "cpu": "20", "memory": "1Gi", "persistentvolumeclaims": "10", "pods": "10", "replicationcontrollers": "20", "resourcequotas": "1", "secrets": "10", "services": "5" }, "used": { "cpu": "0", "memory": "0", "persistentvolumeclaims": "0", "pods": "0", "replicationcontrollers": "1", "resourcequotas": "1", "secrets": "9", "services": "0" } } } ] }kubeclient-4.11.0/test/json/secret_list.json000066400000000000000000000022121435070561000210630ustar00rootroot00000000000000{ "kind": "SecretList", "apiVersion":"v1", "metadata":{ "selfLink":"/api/v1/secrets", "resourceVersion":"256788" }, "items": [ { "metadata": { "name":"default-token-my2pi", "namespace":"default", "selfLink":"/api/v1/namespaces/default/secrets/default-token-my2pi", "uid":"07b60654-2a65-11e5-a483-0e840567604d", "resourceVersion":"11", "creationTimestamp":"2015-07-14T20:15:11Z", "annotations": { "kubernetes.io/service-account.name":"default", "kubernetes.io/service-account.uid":"07b350a0-2a65-11e5-a483-0e840567604d" } }, "data":{ "ca.crt":"Y2F0J3MgYXJlIGF3ZXNvbWUK", "token":"Y2F0J3MgYXJlIGF3ZXNvbWUK" }, "type":"kubernetes.io/service-account-token" }, { "metadata": { "name": "test-secret", "namespace": "dev", "selfLink": "/api/v1/namespaces/dev/secrets/test-secret", "uid": "4e38a198-2bcb-11e5-a483-0e840567604d", "resourceVersion": "245569", "creationTimestamp": "2015-07-16T14:59:49Z" }, "data": { "super-secret": "Y2F0J3MgYXJlIGF3ZXNvbWUK" }, "type": "Opaque" } ] } kubeclient-4.11.0/test/json/security.openshift.io_api_resource_list.json000066400000000000000000000025701435070561000266200ustar00rootroot00000000000000{ "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "security.openshift.io/v1", "resources": [ { "name": "podsecuritypolicyreviews", "singularName": "", "namespaced": true, "kind": "PodSecurityPolicyReview", "verbs": [ "create" ] }, { "name": "podsecuritypolicyselfsubjectreviews", "singularName": "", "namespaced": true, "kind": "PodSecurityPolicySelfSubjectReview", "verbs": [ "create" ] }, { "name": "podsecuritypolicysubjectreviews", "singularName": "", "namespaced": true, "kind": "PodSecurityPolicySubjectReview", "verbs": [ "create" ] }, { "name": "rangeallocations", "singularName": "", "namespaced": false, "kind": "RangeAllocation", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ] }, { "name": "securitycontextconstraints", "singularName": "", "namespaced": false, "kind": "SecurityContextConstraints", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ], "shortNames": [ "scc" ] } ] }kubeclient-4.11.0/test/json/security_context_constraint_list.json000066400000000000000000000257471435070561000254770ustar00rootroot00000000000000{ "kind": "SecurityContextConstraintsList", "apiVersion": "security.openshift.io/v1", "metadata": { "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints", "resourceVersion": "5751" }, "items": [ { "metadata": { "name": "anyuid", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/anyuid", "uid": "12ba8540-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "71", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "anyuid provides all features of the restricted SCC but allows users to run with any UID and any GID." } }, "priority": 10, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "MKNOD" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": false, "volumes": [ "configMap", "downwardAPI", "emptyDir", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": false, "allowHostPorts": false, "allowHostPID": false, "allowHostIPC": false, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "RunAsAny" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "RunAsAny" }, "readOnlyRootFilesystem": false, "users": [], "groups": [ "system:cluster-admins" ] }, { "metadata": { "name": "hostaccess", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/hostaccess", "uid": "12b5b3a2-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "69", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "hostaccess allows access to all host namespaces but still requires pods to be run with a UID and SELinux context that are allocated to the namespace. WARNING: this SCC allows host access to namespaces, file systems, and PIDS. It should only be used by trusted pods. Grant with caution." } }, "priority": null, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "KILL", "MKNOD", "SETUID", "SETGID" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": true, "volumes": [ "configMap", "downwardAPI", "emptyDir", "hostPath", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": true, "allowHostPorts": true, "allowHostPID": true, "allowHostIPC": true, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "MustRunAsRange" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "MustRunAs" }, "readOnlyRootFilesystem": false, "users": [], "groups": [] }, { "metadata": { "name": "hostmount-anyuid", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/hostmount-anyuid", "uid": "12b512c0-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "68", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "hostmount-anyuid provides all the features of the restricted SCC but allows host mounts and any UID by a pod. This is primarily used by the persistent volume recycler. WARNING: this SCC allows host file system access as any UID, including UID 0. Grant with caution." } }, "priority": null, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "MKNOD" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": true, "volumes": [ "configMap", "downwardAPI", "emptyDir", "hostPath", "nfs", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": false, "allowHostPorts": false, "allowHostPID": false, "allowHostIPC": false, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "RunAsAny" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "RunAsAny" }, "readOnlyRootFilesystem": false, "users": [ "system:serviceaccount:openshift-infra:pv-recycler-controller" ], "groups": [] }, { "metadata": { "name": "hostnetwork", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/hostnetwork", "uid": "12bb0984-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "72", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "hostnetwork allows using host networking and host ports but still requires pods to be run with a UID and SELinux context that are allocated to the namespace." } }, "priority": null, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "KILL", "MKNOD", "SETUID", "SETGID" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": false, "volumes": [ "configMap", "downwardAPI", "emptyDir", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": true, "allowHostPorts": true, "allowHostPID": false, "allowHostIPC": false, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "MustRunAsRange" }, "supplementalGroups": { "type": "MustRunAs" }, "fsGroup": { "type": "MustRunAs" }, "readOnlyRootFilesystem": false, "users": [], "groups": [] }, { "metadata": { "name": "nonroot", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/nonroot", "uid": "12b37c59-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "67", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "nonroot provides all features of the restricted SCC but allows users to run with any non-root UID. The user must specify the UID or it must be specified on the by the manifest of the container runtime." } }, "priority": null, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "KILL", "MKNOD", "SETUID", "SETGID" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": false, "volumes": [ "configMap", "downwardAPI", "emptyDir", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": false, "allowHostPorts": false, "allowHostPID": false, "allowHostIPC": false, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "MustRunAsNonRoot" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "RunAsAny" }, "readOnlyRootFilesystem": false, "users": [], "groups": [] }, { "metadata": { "name": "privileged", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/privileged", "uid": "12b18f4a-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "300", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "privileged allows access to all privileged and host features and the ability to run as any user, any group, any fsGroup, and with any SELinux context. WARNING: this is the most relaxed SCC and should be used only for cluster administration. Grant with caution." } }, "priority": null, "allowPrivilegedContainer": true, "defaultAddCapabilities": null, "requiredDropCapabilities": null, "allowedCapabilities": [ "*" ], "allowHostDirVolumePlugin": true, "volumes": [ "*" ], "allowedFlexVolumes": null, "allowHostNetwork": true, "allowHostPorts": true, "allowHostPID": true, "allowHostIPC": true, "seLinuxContext": { "type": "RunAsAny" }, "runAsUser": { "type": "RunAsAny" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "RunAsAny" }, "readOnlyRootFilesystem": false, "users": [ "system:admin", "system:serviceaccount:openshift-infra:build-controller", "system:serviceaccount:default:pvinstaller", "system:serviceaccount:default:registry", "system:serviceaccount:default:router" ], "groups": [ "system:cluster-admins", "system:nodes", "system:masters" ], "seccompProfiles": [ "*" ] }, { "metadata": { "name": "restricted", "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/restricted", "uid": "12b9a842-ef00-11e8-b4c0-68f728fac3ab", "resourceVersion": "70", "creationTimestamp": "2018-11-23T09:13:42Z", "annotations": { "kubernetes.io/description": "restricted denies access to all host features and requires pods to be run with a UID, and SELinux context that are allocated to the namespace. This is the most restrictive SCC and it is used by default for authenticated users." } }, "priority": null, "allowPrivilegedContainer": false, "defaultAddCapabilities": null, "requiredDropCapabilities": [ "KILL", "MKNOD", "SETUID", "SETGID" ], "allowedCapabilities": null, "allowHostDirVolumePlugin": false, "volumes": [ "configMap", "downwardAPI", "emptyDir", "persistentVolumeClaim", "projected", "secret" ], "allowedFlexVolumes": null, "allowHostNetwork": false, "allowHostPorts": false, "allowHostPID": false, "allowHostIPC": false, "seLinuxContext": { "type": "MustRunAs" }, "runAsUser": { "type": "MustRunAsRange" }, "supplementalGroups": { "type": "RunAsAny" }, "fsGroup": { "type": "MustRunAs" }, "readOnlyRootFilesystem": false, "users": [], "groups": [ "system:authenticated" ] } ] } kubeclient-4.11.0/test/json/service.json000066400000000000000000000015101435070561000202030ustar00rootroot00000000000000{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "redis-slave", "namespace": "development", "selfLink": "/api/v1/namespaces/development/services/redis-slave", "uid": "bdb80a8f-db93-11e4-b293-f8b156af4ae1", "resourceVersion": "2815", "creationTimestamp": "2015-04-05T13:00:31Z", "labels": { "name": "redis", "role": "slave" } }, "spec": { "ports": [ { "name": "", "protocol": "TCP", "port": 6379, "targetPort": "redis-server" } ], "selector": { "name": "redis", "role": "slave" }, "clusterIP": "10.0.0.140", "sessionAffinity": "None" }, "status": {} }kubeclient-4.11.0/test/json/service_account.json000066400000000000000000000011371435070561000217240ustar00rootroot00000000000000{ "kind": "ServiceAccount", "apiVersion": "v1", "metadata": { "name": "default", "namespace": "default", "selfLink": "/api/v1/namespaces/default/serviceaccounts/default", "uid": "d3d773f4-6bf0-11e5-843a-525400f8b93e", "resourceVersion": "94", "creationTimestamp": "2015-10-06T06:09:39Z" }, "secrets": [ { "name": "default-token-6s23q" }, { "name": "default-dockercfg-62tf3" } ], "imagePullSecrets": [ { "name": "default-dockercfg-62tf3" } ] } kubeclient-4.11.0/test/json/service_account_list.json000066400000000000000000000047271435070561000227670ustar00rootroot00000000000000{ "kind": "List", "apiVersion": "v1", "metadata": {}, "items": [ { "kind": "ServiceAccount", "apiVersion": "v1", "metadata": { "name": "builder", "namespace": "default", "selfLink": "/api/v1/namespaces/default/serviceaccounts/builder", "uid": "d40655f6-6bf0-11e5-843a-525400f8b93e", "resourceVersion": "133", "creationTimestamp": "2015-10-06T06:09:39Z" }, "secrets": [ { "name": "builder-token-5v6z2" }, { "name": "builder-dockercfg-qe2re" } ], "imagePullSecrets": [ { "name": "builder-dockercfg-qe2re" } ] }, { "kind": "ServiceAccount", "apiVersion": "v1", "metadata": { "name": "default", "namespace": "default", "selfLink": "/api/v1/namespaces/default/serviceaccounts/default", "uid": "d3d773f4-6bf0-11e5-843a-525400f8b93e", "resourceVersion": "94", "creationTimestamp": "2015-10-06T06:09:39Z" }, "secrets": [ { "name": "default-token-6s23q" }, { "name": "default-dockercfg-62tf3" } ], "imagePullSecrets": [ { "name": "default-dockercfg-62tf3" } ] }, { "kind": "ServiceAccount", "apiVersion": "v1", "metadata": { "name": "deployer", "namespace": "default", "selfLink": "/api/v1/namespaces/default/serviceaccounts/deployer", "uid": "d41d385e-6bf0-11e5-843a-525400f8b93e", "resourceVersion": "137", "creationTimestamp": "2015-10-06T06:09:39Z" }, "secrets": [ { "name": "deployer-token-h3i57" }, { "name": "deployer-dockercfg-qgjjj" } ], "imagePullSecrets": [ { "name": "deployer-dockercfg-qgjjj" } ] } ] } kubeclient-4.11.0/test/json/service_illegal_json_404.json000066400000000000000000000000231435070561000233120ustar00rootroot00000000000000404: Page Not Foundkubeclient-4.11.0/test/json/service_json_patch.json000066400000000000000000000007261435070561000224230ustar00rootroot00000000000000{ "status" : {}, "kind" : "Service", "apiVersion" : "v1", "spec" : { "ports" : [ { "targetPort" : 80, "nodePort" : 0, "port" : 80, "protocol" : "TCP" } ], "clusterIP" : "1.2.3.4", "type": "LoadBalancer" }, "metadata" : { "name" : "my-service", "creationTimestamp" : null, "namespace" : "development", "resourceVersion" : "2", "annotations" : { "key" : "value" } } } kubeclient-4.11.0/test/json/service_list.json000066400000000000000000000060511435070561000212430ustar00rootroot00000000000000{ "kind": "ServiceList", "apiVersion": "v1", "metadata": { "selfLink": "/api/v1/services", "resourceVersion": "36727" }, "items": [ { "metadata": { "name": "kubernetes", "namespace": "default", "selfLink": "/api/v1/namespaces/default/services/kubernetes", "uid": "b6606490-db86-11e4-b293-f8b156af4ae1", "resourceVersion": "6", "creationTimestamp": "2015-04-05T11:27:15Z", "labels": { "component": "apiserver", "provider": "kubernetes" } }, "spec": { "ports": [ { "name": "", "protocol": "TCP", "port": 443, "targetPort": 443 } ], "selector": null, "clusterIP": "10.0.0.2", "sessionAffinity": "None" }, "status": {} }, { "metadata": { "name": "kubernetes-ro", "namespace": "default", "selfLink": "/api/v1/namespaces/default/services/kubernetes-ro", "uid": "b6606694-db86-11e4-b293-f8b156af4ae1", "resourceVersion": "5", "creationTimestamp": "2015-04-05T11:27:15Z", "labels": { "component": "apiserver", "provider": "kubernetes" } }, "spec": { "ports": [ { "name": "", "protocol": "TCP", "port": 80, "targetPort": 80 } ], "selector": null, "clusterIP": "10.0.0.1", "sessionAffinity": "None" }, "status": {} }, { "metadata": { "name": "redis-slave", "namespace": "development", "selfLink": "/api/v1/namespaces/development/services/redis-slave", "uid": "bdb80a8f-db93-11e4-b293-f8b156af4ae1", "resourceVersion": "2815", "creationTimestamp": "2015-04-05T13:00:31Z", "labels": { "name": "redis", "role": "slave" } }, "spec": { "ports": [ { "name": "", "protocol": "TCP", "port": 6379, "targetPort": "redis-server" } ], "selector": { "name": "redis", "role": "slave" }, "clusterIP": "10.0.0.140", "sessionAffinity": "None" }, "status": {} } ] }kubeclient-4.11.0/test/json/service_merge_patch.json000066400000000000000000000007221435070561000225450ustar00rootroot00000000000000{ "status" : {}, "kind" : "Service", "apiVersion" : "v1", "spec" : { "ports" : [ { "targetPort" : 80, "nodePort" : 0, "port" : 80, "protocol" : "TCP" } ], "clusterIP" : "1.2.3.4", "type": "NodePort" }, "metadata" : { "name" : "my-service", "creationTimestamp" : null, "namespace" : "development", "resourceVersion" : "2", "annotations" : { "key" : "value" } } } kubeclient-4.11.0/test/json/service_patch.json000066400000000000000000000006721435070561000213720ustar00rootroot00000000000000{ "status" : {}, "kind" : "Service", "apiVersion" : "v1", "spec" : { "ports" : [ { "targetPort" : 80, "nodePort" : 0, "port" : 80, "protocol" : "TCP" } ], "clusterIP" : "1.2.3.4" }, "metadata" : { "name" : "my_service", "creationTimestamp" : null, "namespace" : "development", "resourceVersion" : "2", "annotations" : { "key" : "value" } } } kubeclient-4.11.0/test/json/service_update.json000066400000000000000000000006561435070561000215570ustar00rootroot00000000000000{ "status" : {}, "kind" : "Service", "apiVersion" : "v1", "spec" : { "ports" : [ { "targetPort" : 80, "nodePort" : 0, "port" : 80, "protocol" : "TCP" } ], "clusterIP" : "1.2.3.4" }, "metadata" : { "name" : "my_service", "creationTimestamp" : null, "namespace" : "default", "resourceVersion" : "2" } } kubeclient-4.11.0/test/json/template.json000066400000000000000000000013251435070561000203620ustar00rootroot00000000000000{ "apiVersion": "template.openshift.io/v1", "kind": "Template", "metadata": { "creationTimestamp": "2018-12-17T16:11:36Z", "name": "my-template", "namespace": "default", "resourceVersion": "21954", "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates/my-template", "uid": "6e03e3e6-0216-11e9-b1e0-68f728fac3ab" }, "objects": [ { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "${NAME_PREFIX}my-service" } } ], "parameters": [ { "description": "Prefix for names", "name": "NAME_PREFIX" } ] } kubeclient-4.11.0/test/json/template.openshift.io_api_resource_list.json000066400000000000000000000026101435070561000265570ustar00rootroot00000000000000{ "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "template.openshift.io/v1", "resources": [ { "name": "brokertemplateinstances", "singularName": "", "namespaced": false, "kind": "BrokerTemplateInstance", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ] }, { "name": "processedtemplates", "singularName": "", "namespaced": true, "kind": "Template", "verbs": [ "create" ] }, { "name": "templateinstances", "singularName": "", "namespaced": true, "kind": "TemplateInstance", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ] }, { "name": "templateinstances/status", "singularName": "", "namespaced": true, "kind": "TemplateInstance", "verbs": [ "get", "patch", "update" ] }, { "name": "templates", "singularName": "", "namespaced": true, "kind": "Template", "verbs": [ "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch" ] } ] }kubeclient-4.11.0/test/json/template_list.json000066400000000000000000000015611435070561000214170ustar00rootroot00000000000000{ "kind": "TemplateList", "apiVersion": "template.openshift.io/v1", "metadata": { "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates", "resourceVersion": "22758" }, "items": [ { "metadata": { "name": "my-template", "namespace": "default", "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates/my-template", "uid": "6e03e3e6-0216-11e9-b1e0-68f728fac3ab", "resourceVersion": "21954", "creationTimestamp": "2018-12-17T16:11:36Z" }, "objects": [ { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "${NAME_PREFIX}my-service" } } ], "parameters": [ { "name": "NAME_PREFIX", "description": "Prefix for names" } ] } ] } kubeclient-4.11.0/test/json/versions_list.json000066400000000000000000000000741435070561000214520ustar00rootroot00000000000000{ "versions": [ "v1beta3", "v1" ] } kubeclient-4.11.0/test/json/watch_stream.json000066400000000000000000000043131435070561000212300ustar00rootroot00000000000000{"type":"ADDED","object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"php","namespace":"default","selfLink":"/api/v1/pods/php","uid":"e75f2c07-b047-11e4-89e4-525400c903c1","resourceVersion":"1389","creationTimestamp":"2015-02-09T05:39:19-05:00","labels":{"name":"foo"}},"spec":{"volumes":null,"containers":[{"name":"nginx","image":"dockerfile/nginx","ports":[{"hostPort":9090,"containerPort":80,"protocol":"TCP"}],"resources":{},"livenessProbe":{"httpGet":{"path":"/index.html","port":"9090"},"initialDelaySeconds":30},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent","securityContext":{"capabilities":{}}}],"restartPolicy":{"always":{}},"dnsPolicy":"ClusterFirst"},"status":{"phase":"Pending"}}} {"type":"MODIFIED","object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"php","namespace":"default","selfLink":"/api/v1/pods/php","uid":"e75f2c07-b047-11e4-89e4-525400c903c1","resourceVersion":"1390","creationTimestamp":"2015-02-09T05:39:19-05:00","labels":{"name":"foo"}},"spec":{"volumes":null,"containers":[{"name":"nginx","image":"dockerfile/nginx","ports":[{"hostPort":9090,"containerPort":80,"protocol":"TCP"}],"resources":{},"livenessProbe":{"httpGet":{"path":"/index.html","port":"9090"},"initialDelaySeconds":30},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent","securityContext":{"capabilities":{}}}],"restartPolicy":{"always":{}},"dnsPolicy":"ClusterFirst"},"status":{"phase":"Pending","host":"127.0.0.1"}}} {"type":"DELETED","object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"php","namespace":"default","selfLink":"/api/v1/pods/php","uid":"e75f2c07-b047-11e4-89e4-525400c903c1","resourceVersion":"1398","creationTimestamp":"2015-02-09T05:39:19-05:00","labels":{"name":"foo"}},"spec":{"volumes":null,"containers":[{"name":"nginx","image":"dockerfile/nginx","ports":[{"hostPort":9090,"containerPort":80,"protocol":"TCP"}],"resources":{},"livenessProbe":{"httpGet":{"path":"/index.html","port":"9090"},"initialDelaySeconds":30},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"IfNotPresent","securityContext":{"capabilities":{}}}],"restartPolicy":{"always":{}},"dnsPolicy":"ClusterFirst"},"status":{"phase":"Pending","host":"127.0.0.1"}}} kubeclient-4.11.0/test/test_common.rb000066400000000000000000000063351435070561000175650ustar00rootroot00000000000000 require_relative 'test_helper' # Unit tests for the common module class CommonTest < MiniTest::Test class ClientStub include Kubeclient::ClientMixin end def client @client ||= ClientStub.new end def test_underscore_entity %w[ Pod pod Service service ReplicationController replication_controller Node node Event event Endpoint endpoint Namespace namespace Secret secret ResourceQuota resource_quota LimitRange limit_range PersistentVolume persistent_volume PersistentVolumeClaim persistent_volume_claim ComponentStatus component_status ServiceAccount service_account Project project Route route ClusterRoleBinding cluster_role_binding Build build BuildConfig build_config Image image ImageStream image_stream dogstatsd dogstatsd lowerCamelUPPERCase lower_camel_uppercase HTTPAPISpecBinding httpapispec_binding APIGroup apigroup APIGroupList apigroup_list APIResourceList apiresource_list APIService apiservice APIServiceList apiservice_list APIVersions apiversions OAuthAccessToken oauth_access_token OAuthAccessTokenList oauth_access_token_list OAuthAuthorizeToken oauth_authorize_token OAuthAuthorizeTokenList oauth_authorize_token_list OAuthClient oauth_client OAuthClientAuthorization oauth_client_authorization OAuthClientAuthorizationList oauth_client_authorization_list OAuthClientList oauth_client_list ].each_slice(2) do |kind, expected_underscore| underscore = Kubeclient::ClientMixin.underscore_entity(kind) assert_equal(underscore, expected_underscore) end end def test_format_datetime_with_string value = '2018-04-27T18:30:17.480321984Z' formatted = client.send(:format_datetime, value) assert_equal(formatted, value) end def test_format_datetime_with_datetime value = DateTime.new(2018, 4, 30, 19, 20, 33) formatted = client.send(:format_datetime, value) assert_equal(formatted, '2018-04-30T19:20:33.000000000+00:00') end def test_format_datetime_with_time value = Time.new(2018, 4, 30, 19, 20, 33, 0) formatted = client.send(:format_datetime, value) assert_equal(formatted, '2018-04-30T19:20:33.000000000+00:00') end def test_parse_definition_with_unconventional_names %w[ PluralPolicy pluralpolicies plural_policy plural_policies LatinDatum latindata latin_datum latin_data Noseparator noseparators noseparator noseparators lowercase lowercases lowercase lowercases TestWithDash test-with-dashes test_with_dash test_with_dashes TestUnderscore test_underscores test_underscore test_underscores TestMismatch other-odd-name testmismatch otheroddname MixedDashMinus mixed-dash_minuses mixed_dash_minus mixed_dash_minuses SameUptoWordboundary sameup-toword-boundarys sameuptowordboundary sameuptowordboundarys ].each_slice(4) do |kind, plural, expected_single, expected_plural| method_names = Kubeclient::ClientMixin.parse_definition(kind, plural).method_names assert_equal(method_names[0], expected_single) assert_equal(method_names[1], expected_plural) end end end kubeclient-4.11.0/test/test_common_url_handling.rb000066400000000000000000000163111435070561000223060ustar00rootroot00000000000000require_relative 'test_helper' # URLHandling tests class TestCommonUrlHandling < MiniTest::Test def test_no_path_in_uri client = Kubeclient::Client.new('http://localhost:8080', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_with_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/api', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_with_api_path_in_uri_other_version client = Kubeclient::Client.new('http://localhost:8080/api', 'v2') rest_client = client.rest_client assert_equal('v2', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/api/v2', rest_client.url.to_s) end def test_with_api_group_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/apis/this_is_the_group', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/apis/this_is_the_group/v1', rest_client.url.to_s) end def test_with_api_group_path_in_uri_other_version client = Kubeclient::Client.new('http://localhost:8080/apis/this_is_the_group', 'v2') rest_client = client.rest_client assert_equal('v2', client.instance_variable_get(:@api_version)) assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/apis/this_is_the_group/v2', rest_client.url.to_s) end def test_with_api_path_in_uri_trailing_slash client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_with_api_path_in_api client = Kubeclient::Client.new('http://localhost:8080/api/but/I/want/a/hidden/k8s/api', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/api/but/I/want/a/hidden/k8s/api/v1', rest_client.url.to_s) end def test_with_api_group_path_in_api client = Kubeclient::Client.new( 'http://localhost:8080/api/but/I/want/a/hidden/k8s/apis/this_is_the_group', 'v1' ) rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group)) assert_equal( 'http://localhost:8080/api/but/I/want/a/hidden/k8s/apis/this_is_the_group/v1', rest_client.url.to_s ) end def test_rancher_with_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/api', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s) end def test_rancher_no_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s) end def test_rancher_no_api_path_in_uri_trailing_slash client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s) end def test_rancher_with_api_path_in_uri_trailing_slash client = Kubeclient::Client.new('http://localhost:8080/k8s/clusters/c-somerancherID/api/', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/k8s/clusters/c-somerancherID/api/v1', rest_client.url.to_s) end def test_rancher_with_api_group_in_uri_trailing_slash client = Kubeclient::Client.new( 'http://localhost:8080/k8s/clusters/c-somerancherID/apis/this_is_the_group', 'v1' ) rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('this_is_the_group/', client.instance_variable_get(:@api_group)) assert_equal( 'http://localhost:8080/k8s/clusters/c-somerancherID/apis/this_is_the_group/v1', rest_client.url.to_s ) end def test_with_openshift_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/oapi', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/oapi/v1', rest_client.url.to_s) end def test_arbitrary_path_with_openshift_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/foobarbaz/oapi', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/foobarbaz/oapi/v1', rest_client.url.to_s) end def test_with_openshift_api_path_in_uri_trailing_slash client = Kubeclient::Client.new('http://localhost:8080/oapi/', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/oapi/v1', rest_client.url.to_s) end def test_with_arbitrary_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/foobarbaz', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/foobarbaz/api/v1', rest_client.url.to_s) end def test_with_arbitrary_and_api_path_in_uri client = Kubeclient::Client.new('http://localhost:8080/foobarbaz/api', 'v1') rest_client = client.rest_client assert_equal('v1', client.instance_variable_get(:@api_version)) assert_equal('', client.instance_variable_get(:@api_group)) assert_equal('http://localhost:8080/foobarbaz/api/v1', rest_client.url.to_s) end end kubeclient-4.11.0/test/test_component_status.rb000066400000000000000000000016201435070561000216720ustar00rootroot00000000000000require_relative 'test_helper' # ComponentStatus tests class TestComponentStatus < MiniTest::Test def test_get_from_json_v3 stub_core_api_list stub_request(:get, %r{/componentstatuses}) .to_return(body: open_test_file('component_status.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') component_status = client.get_component_status('etcd-0', 'default') assert_instance_of(Kubeclient::Resource, component_status) assert_equal('etcd-0', component_status.metadata.name) assert_equal('Healthy', component_status.conditions[0].type) assert_equal('True', component_status.conditions[0].status) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/namespaces/default/componentstatuses/etcd-0', times: 1 ) end end kubeclient-4.11.0/test/test_config.rb000066400000000000000000000256241435070561000175440ustar00rootroot00000000000000require_relative 'test_helper' require 'yaml' require 'open3' # Testing Kubernetes client configuration class KubeclientConfigTest < MiniTest::Test def test_allinone config = Kubeclient::Config.read(config_file('allinone.kubeconfig')) assert_equal(['Default'], config.contexts) check_context(config.context, ssl: true, custom_ca: true, client_cert: true) end def test_external config = Kubeclient::Config.read(config_file('external.kubeconfig')) assert_equal(['Default'], config.contexts) check_context(config.context, ssl: true, custom_ca: true, client_cert: true) end def test_explicit_secure config = Kubeclient::Config.read(config_file('secure.kubeconfig')) assert_equal(['Default'], config.contexts) # Same as external.kubeconfig, but with explicit `insecure-skip-tls-verify: false` check_context(config.context, ssl: true, custom_ca: true, client_cert: true) end def test_external_public_ca config = Kubeclient::Config.read(config_file('external-without-ca.kubeconfig')) assert_equal(['Default'], config.contexts) # Same as external.kubeconfig, no custom CA data (cluster has a publicly trusted cert) check_context(config.context, ssl: true, custom_ca: false, client_cert: true) end def test_secure_public_ca config = Kubeclient::Config.read(config_file('secure-without-ca.kubeconfig')) assert_equal(['Default'], config.contexts) # no custom CA data + explicit `insecure-skip-tls-verify: false` check_context(config.context, ssl: true, custom_ca: false, client_cert: true) end def test_insecure config = Kubeclient::Config.read(config_file('insecure.kubeconfig')) assert_equal(['Default'], config.contexts) # Has explicit `insecure-skip-tls-verify: false` check_context(config.context, ssl: false, custom_ca: false, client_cert: true) end def test_insecure_custom_ca config = Kubeclient::Config.read(config_file('insecure-custom-ca.kubeconfig')) assert_equal(['Default'], config.contexts) # Has explicit `insecure-skip-tls-verify: false` check_context(config.context, ssl: false, custom_ca: true, client_cert: true) end def test_allinone_nopath yaml = File.read(config_file('allinone.kubeconfig')) # A self-contained config shouldn't depend on kcfg_path. config = Kubeclient::Config.new(YAML.safe_load(yaml), nil) assert_equal(['Default'], config.contexts) check_context(config.context, ssl: true, custom_ca: true, client_cert: true) end def test_external_nopath yaml = File.read(config_file('external.kubeconfig')) # kcfg_path = nil should prevent file access config = Kubeclient::Config.new(YAML.safe_load(yaml), nil) assert_raises(StandardError) do config.context end end def test_external_nopath_absolute yaml = File.read(config_file('external.kubeconfig')) # kcfg_path = nil should prevent file access, even if absolute path specified ca_absolute_path = File.absolute_path(config_file('external-')) yaml = yaml.gsub('external-', ca_absolute_path) config = Kubeclient::Config.new(YAML.safe_load(yaml), nil) assert_raises(StandardError) do config.context end end def test_concatenated_ca config = Kubeclient::Config.read(config_file('concatenated-ca.kubeconfig')) assert_equal(['Default'], config.contexts) check_context(config.context, ssl: true) end def test_nouser config = Kubeclient::Config.read(config_file('nouser.kubeconfig')) assert_equal(['default/localhost:6443/nouser'], config.contexts) check_context(config.context, ssl: true, custom_ca: false, client_cert: false) end def test_user_token config = Kubeclient::Config.read(config_file('userauth.kubeconfig')) assert_equal(['localhost/system:admin:token', 'localhost/system:admin:userpass'], config.contexts) context = config.context('localhost/system:admin:token') check_context(context, ssl: true, custom_ca: false, client_cert: false) assert_equal('0123456789ABCDEF0123456789ABCDEF', context.auth_options[:bearer_token]) end def test_user_password config = Kubeclient::Config.read(config_file('userauth.kubeconfig')) assert_equal(['localhost/system:admin:token', 'localhost/system:admin:userpass'], config.contexts) context = config.context('localhost/system:admin:userpass') check_context(context, ssl: true, custom_ca: false, client_cert: false) assert_equal('admin', context.auth_options[:username]) assert_equal('pAssw0rd123', context.auth_options[:password]) end def test_timestamps # Test YAML parsing doesn't crash on YAML timestamp syntax. Kubeclient::Config.read(config_file('timestamps.kubeconfig')) end def test_user_exec token = '0123456789ABCDEF0123456789ABCDEF' creds = { 'apiVersion': 'client.authentication.k8s.io/v1beta1', 'status': { 'token': token } } config = Kubeclient::Config.read(config_file('execauth.kubeconfig')) assert_equal(['localhost/system:admin:exec-search-path', 'localhost/system:admin:exec-relative-path', 'localhost/system:admin:exec-absolute-path'], config.contexts) # A bare command name in config means search PATH, so it's executed as bare command. stub_exec(%r{^example-exec-plugin$}, creds) do context = config.context('localhost/system:admin:exec-search-path') check_context(context, ssl: true, custom_ca: false, client_cert: false) assert_equal(token, context.auth_options[:bearer_token]) end # A relative path is taken relative to the dir of the kubeconfig. stub_exec(%r{.*config/dir/example-exec-plugin$}, creds) do context = config.context('localhost/system:admin:exec-relative-path') check_context(context, ssl: true, custom_ca: false, client_cert: false) assert_equal(token, context.auth_options[:bearer_token]) end # An absolute path is taken as-is. stub_exec(%r{^/abs/path/example-exec-plugin$}, creds) do context = config.context('localhost/system:admin:exec-absolute-path') check_context(context, ssl: true, custom_ca: false, client_cert: false) assert_equal(token, context.auth_options[:bearer_token]) end end def test_user_exec_nopath yaml = File.read(config_file('execauth.kubeconfig')) config = Kubeclient::Config.new(YAML.safe_load(yaml), nil) config.contexts.each do |context_name| Open3.stub(:capture3, proc { flunk 'should not execute command' }) do assert_raises(StandardError) do config.context(context_name) end end end end def test_gcp_default_auth Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once parsed = load_yaml(config_file('gcpauth.kubeconfig')) config = Kubeclient::Config.new(parsed, nil) config.context(config.contexts.first) end # Each call to .context() obtains a new token, calling .auth_options doesn't change anything. # NOTE: this is not a guarantee, may change, just testing current behavior. def test_gcp_default_auth_renew Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once parsed = load_yaml(config_file('gcpauth.kubeconfig')) config = Kubeclient::Config.new(parsed, nil) context = config.context(config.contexts.first) assert_equal({ bearer_token: 'token1' }, context.auth_options) assert_equal({ bearer_token: 'token1' }, context.auth_options) Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token2').once context2 = config.context(config.contexts.first) assert_equal({ bearer_token: 'token2' }, context2.auth_options) assert_equal({ bearer_token: 'token1' }, context.auth_options) end def test_gcp_command_auth Kubeclient::GCPCommandCredentials.expects(:token) .with('access-token' => '', 'cmd-args' => 'config config-helper --format=json', 'cmd-path' => '/path/to/gcloud', 'expiry' => '2019-04-09 19:26:18 UTC', 'expiry-key' => '{.credential.token_expiry}', 'token-key' => '{.credential.access_token}') .returns('token1') .once config = Kubeclient::Config.read(config_file('gcpcmdauth.kubeconfig')) config.context(config.contexts.first) end def test_oidc_auth_provider Kubeclient::OIDCAuthProvider.expects(:token) .with('client-id' => 'fake-client-id', 'client-secret' => 'fake-client-secret', 'id-token' => 'fake-id-token', 'idp-issuer-url' => 'https://accounts.google.com', 'refresh-token' => 'fake-refresh-token') .returns('token1') .once parsed = YAML.safe_load(File.read(config_file('oidcauth.kubeconfig'))) config = Kubeclient::Config.new(parsed, nil) config.context(config.contexts.first) end private def check_context(context, ssl: true, custom_ca: true, client_cert: true) assert_equal('https://localhost:6443', context.api_endpoint) assert_equal('v1', context.api_version) assert_equal('default', context.namespace) if custom_ca assert_kind_of(OpenSSL::X509::Store, context.ssl_options[:cert_store]) else assert_nil(context.ssl_options[:cert_store]) end if client_cert assert_kind_of(OpenSSL::X509::Certificate, context.ssl_options[:client_cert]) assert_kind_of(OpenSSL::PKey::RSA, context.ssl_options[:client_key]) if custom_ca # When certificates expire one way to recreate them is using a k0s single-node cluster: # test/config/update_certs_k0s.rb assert(context.ssl_options[:cert_store].verify(context.ssl_options[:client_cert])) end else assert_nil(context.ssl_options[:client_cert]) assert_nil(context.ssl_options[:client_key]) end if ssl assert_equal(OpenSSL::SSL::VERIFY_PEER, context.ssl_options[:verify_ssl], 'expected VERIFY_PEER') else assert_equal(OpenSSL::SSL::VERIFY_NONE, context.ssl_options[:verify_ssl], 'expected VERIFY_NONE') end end def stub_exec(command_regexp, creds) st = Minitest::Mock.new st.expect(:success?, true) capture3_stub = lambda do |_env, command, *_args| assert_match command_regexp, command [JSON.dump(creds), nil, st] end Open3.stub(:capture3, capture3_stub) do yield end end def load_yaml(file_name) if RUBY_VERSION >= '2.6' YAML.safe_load(File.read(file_name), permitted_classes: [Date, Time]) else YAML.safe_load(File.read(file_name), [Date, Time]) end end end kubeclient-4.11.0/test/test_endpoint.rb000066400000000000000000000042351435070561000201120ustar00rootroot00000000000000require_relative 'test_helper' # kind: 'Endpoints' entity tests. # This is one of the unusual `kind`s that are already plural (https://github.com/kubernetes/kubernetes/issues/8115). # We force singular in method names like 'create_endpoint', # but `kind` should remain plural as in kubernetes. class TestEndpoint < MiniTest::Test def test_create_endpoint stub_core_api_list testing_ep = Kubeclient::Resource.new testing_ep.metadata = {} testing_ep.metadata.name = 'myendpoint' testing_ep.metadata.namespace = 'default' testing_ep.subsets = [ { 'addresses' => [{ 'ip' => '172.17.0.25' }], 'ports' => [{ 'name' => 'https', 'port' => 6443, 'protocol' => 'TCP' }] } ] req_body = '{"metadata":{"name":"myendpoint","namespace":"default"},' \ '"subsets":[{"addresses":[{"ip":"172.17.0.25"}],"ports":[{"name":"https",' \ '"port":6443,"protocol":"TCP"}]}],"kind":"Endpoints","apiVersion":"v1"}' stub_request(:post, 'http://localhost:8080/api/v1/namespaces/default/endpoints') .with(body: req_body) .to_return(body: open_test_file('created_endpoint.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') created_ep = client.create_endpoint(testing_ep) assert_equal('Endpoints', created_ep.kind) assert_equal('v1', created_ep.apiVersion) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1', as: :parsed_symbolized) created_ep = client.create_endpoint(testing_ep) assert_equal('Endpoints', created_ep[:kind]) assert_equal('v1', created_ep[:apiVersion]) end def test_get_endpoints stub_core_api_list stub_request(:get, %r{/endpoints}) .to_return(body: open_test_file('endpoint_list.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') collection = client.get_endpoints(as: :parsed_symbolized) assert_equal('EndpointsList', collection[:kind]) assert_equal('v1', collection[:apiVersion]) # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic. collection = client.get_endpoints assert_equal('Endpoints', collection.kind) end end kubeclient-4.11.0/test/test_exec_credentials.rb000066400000000000000000000133251435070561000215730ustar00rootroot00000000000000require_relative 'test_helper' require 'open3' # Unit tests for the ExecCredentials provider class ExecCredentialsTest < MiniTest::Test def test_exec_opts_missing expected_msg = 'exec options are required' exception = assert_raises(ArgumentError) do Kubeclient::ExecCredentials.run(nil) end assert_equal(expected_msg, exception.message) end def test_exec_command_missing expected_msg = 'exec command is required' exception = assert_raises(KeyError) do Kubeclient::ExecCredentials.run({}) end assert_equal(expected_msg, exception.message) end def test_exec_command_failure err = 'Error' expected_msg = "exec command failed: #{err}" st = Minitest::Mock.new st.expect(:success?, false) opts = { 'command' => 'dummy' } Open3.stub(:capture3, [nil, err, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_run_with_token_credentials opts = { 'command' => 'dummy' } credentials = { 'token' => '0123456789ABCDEF0123456789ABCDEF' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => credentials ) st = Minitest::Mock.new st.expect(:success?, true) Open3.stub(:capture3, [creds, nil, st]) do assert_equal(credentials, Kubeclient::ExecCredentials.run(opts)) end end def test_run_with_client_credentials opts = { 'command' => 'dummy' } credentials = { 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF', 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => credentials ) st = Minitest::Mock.new st.expect(:success?, true) Open3.stub(:capture3, [creds, nil, st]) do assert_equal(credentials, Kubeclient::ExecCredentials.run(opts)) end end def test_run_with_missing_client_certificate_data opts = { 'command' => 'dummy' } credentials = { 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => credentials ) st = Minitest::Mock.new st.expect(:success?, true) expected_msg = 'exec plugin didn\'t return client certificate data' Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_run_with_missing_client_key_data opts = { 'command' => 'dummy' } credentials = { 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => credentials ) st = Minitest::Mock.new st.expect(:success?, true) expected_msg = 'exec plugin didn\'t return client key data' Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_run_with_client_data_and_token opts = { 'command' => 'dummy' } credentials = { 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF', 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF', 'token' => '0123456789ABCDEF0123456789ABCDEF' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => credentials ) st = Minitest::Mock.new st.expect(:success?, true) expected_msg = 'exec plugin returned both token and client data' Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_status_missing opts = { 'command' => 'dummy' } creds = JSON.dump('apiVersion' => 'client.authentication.k8s.io/v1alpha1') st = Minitest::Mock.new st.expect(:success?, true) expected_msg = 'exec plugin didn\'t return a status field' Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_credentials_missing opts = { 'command' => 'dummy' } creds = JSON.dump( 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 'status' => {} ) st = Minitest::Mock.new st.expect(:success?, true) expected_msg = 'exec plugin didn\'t return a token or client data' Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end def test_api_version_mismatch api_version = 'client.authentication.k8s.io/v1alpha1' expected_version = 'client.authentication.k8s.io/v1beta1' opts = { 'command' => 'dummy', 'apiVersion' => expected_version } creds = JSON.dump( 'apiVersion' => api_version ) st = Minitest::Mock.new st.expect(:success?, true) expected_msg = "exec plugin is configured to use API version #{expected_version}," \ " plugin returned version #{api_version}" Open3.stub(:capture3, [creds, nil, st]) do exception = assert_raises(RuntimeError) do Kubeclient::ExecCredentials.run(opts) end assert_equal(expected_msg, exception.message) end end end kubeclient-4.11.0/test/test_gcp_command_credentials.rb000066400000000000000000000015051435070561000231130ustar00rootroot00000000000000require_relative 'test_helper' require 'open3' # Unit tests for the GCPCommandCredentials token provider class GCPCommandCredentialsTest < MiniTest::Test def test_token opts = { 'cmd-args' => 'config config-helper --format=json', 'cmd-path' => '/path/to/gcloud', 'expiry-key' => '{.credential.token_expiry}', 'token-key' => '{.credential.access_token}' } creds = JSON.dump( 'credential' => { 'access_token' => '9A3A941836F2458175BE18AA1971EBBF47949B07', 'token_expiry' => '2019-04-12T15:02:51Z' } ) st = Minitest::Mock.new st.expect(:success?, true) Open3.stub(:capture3, [creds, nil, st]) do assert_equal('9A3A941836F2458175BE18AA1971EBBF47949B07', Kubeclient::GCPCommandCredentials.token(opts)) end end end kubeclient-4.11.0/test/test_google_application_default_credentials.rb000066400000000000000000000007331435070561000262110ustar00rootroot00000000000000require_relative 'test_helper' require 'googleauth' # Unit tests for the ApplicationDefaultCredentials token provider class GoogleApplicationDefaultCredentialsTest < MiniTest::Test def test_token auth = Minitest::Mock.new auth.expect(:apply, nil, [{}]) auth.expect(:access_token, 'valid_token') Google::Auth.stub(:get_application_default, auth) do assert_equal('valid_token', Kubeclient::GoogleApplicationDefaultCredentials.token) end end end kubeclient-4.11.0/test/test_guestbook_go.rb000066400000000000000000000166541435070561000207710ustar00rootroot00000000000000require_relative 'test_helper' require 'vcr' # creation of google's example of guest book class CreateGuestbookGo < MiniTest::Test def test_create_guestbook_entities VCR.configure do |c| c.cassette_library_dir = 'test/cassettes' c.hook_into(:webmock) end # WebMock.allow_net_connect! VCR.use_cassette('kubernetes_guestbook') do # , record: :new_episodes) do client = Kubeclient::Client.new('http://10.35.0.23:8080/api/', 'v1') testing_ns = Kubeclient::Resource.new testing_ns.metadata = {} testing_ns.metadata.name = 'kubeclient-ns' # delete in case they existed before so creation can be tested delete_namespace(client, testing_ns.metadata.name) delete_services( client, testing_ns.metadata.name, ['guestbook', 'redis-master', 'redis-slave'] ) delete_replication_controllers( client, testing_ns.metadata.name, ['guestbook', 'redis-master', 'redis-slave'] ) client.create_namespace(testing_ns) services = create_services(client, testing_ns.metadata.name) replicators = create_replication_controllers(client, testing_ns.metadata.name) get_namespaces(client) get_services(client, testing_ns.metadata.name) get_replication_controllers(client, testing_ns.metadata.name) delete_services(client, testing_ns.metadata.name, services) delete_replication_controllers(client, testing_ns.metadata.name, replicators) client.delete_namespace(testing_ns.metadata.name) end ensure VCR.turn_off! end def delete_namespace(client, namespace_name) client.delete_namespace(namespace_name) rescue Kubeclient::ResourceNotFoundError => exception assert_equal(404, exception.error_code) end def get_namespaces(client) namespaces = client.get_namespaces assert(true, namespaces.size > 2) end def get_services(client, ns) retrieved_services = client.get_services(namespace: ns) assert_equal(3, retrieved_services.size) end def get_replication_controllers(client, ns) retrieved_replicators = client.get_replication_controllers(namespace: ns) assert_equal(3, retrieved_replicators.size) end def create_services(client, ns) guestbook_service = client.create_service(guestbook_service(ns)) redis_service = client.create_service(redis_service(ns)) redis_slave_service = client.create_service(redis_slave_service(ns)) [guestbook_service, redis_service, redis_slave_service] end def create_replication_controllers(client, namespace) rc = client.create_replication_controller(guestbook_rc(namespace)) rc2 = client.create_replication_controller(redis_master_rc(namespace)) rc3 = client.create_replication_controller(redis_slave_rc(namespace)) [rc, rc2, rc3] end def delete_services(client, namespace, services) # if the entity is not found, no need to fail the test services.each do |service| begin if service.instance_of?(Kubeclient::Resource) client.delete_service(service.metadata.name, namespace) else # it's just a string - service name client.delete_service(service, namespace) end rescue Kubeclient::ResourceNotFoundError => exception assert_equal(404, exception.error_code) end end end def delete_replication_controllers(client, namespace, replication_controllers) # if the entity is not found, no need to fail the test replication_controllers.each do |rc| begin if rc.instance_of?(Kubeclient::Resource) client.delete_replication_controller(rc.metadata.name, namespace) else # it's just a string - rc name client.delete_replication_controller(rc, namespace) end rescue Kubeclient::ResourceNotFoundError => exception assert_equal(404, exception.error_code) end end end private def construct_base_rc(namespace) rc = Kubeclient::Resource.new rc.metadata = {} rc.metadata.namespace = namespace rc.metadata.labels = {} rc.spec = {} rc.spec.selector = {} rc.spec.template = {} rc.spec.template.metadata = {} rc.spec.template.spec = {} rc.spec.template.metadata.labels = {} rc end def redis_master_rc(namespace) rc = construct_base_rc(namespace) rc.metadata.name = 'redis-master' rc.metadata.labels.app = 'redis' rc.metadata.labels.role = 'master' rc.spec.replicas = 1 rc.spec.selector.app = 'redis' rc.spec.selector.role = 'master' rc.spec.template.metadata.labels.app = 'redis' rc.spec.template.metadata.labels.role = 'master' rc.spec.template.spec.containers = [{ 'name' => 'redis-master', 'image' => 'redis', 'ports' => [{ 'name' => 'redis-server', 'containerPort' => 6379 }] }] rc end def redis_slave_rc(namespace) rc = construct_base_rc(namespace) rc.metadata.name = 'redis-slave' rc.metadata.labels.app = 'redis' rc.metadata.labels.role = 'slave' rc.spec.replicas = 2 rc.spec.selector.app = 'redis' rc.spec.selector.role = 'slave' rc.spec.template.metadata.labels.app = 'redis' rc.spec.template.metadata.labels.role = 'slave' rc.spec.template.spec.containers = [{ 'name' => 'redis-slave', 'image' => 'kubernetes/redis-slave:v2', 'ports' => [{ 'name' => 'redis-server', 'containerPort' => 6379 }] }] rc end def guestbook_rc(namespace) rc = construct_base_rc(namespace) rc.metadata.name = 'guestbook' rc.metadata.labels.app = 'guestbook' rc.metadata.labels.role = 'slave' rc.spec.replicas = 3 rc.spec.selector.app = 'guestbook' rc.spec.template.metadata.labels.app = 'guestbook' rc.spec.template.spec.containers = [ { 'name' => 'guestbook', 'image' => 'kubernetes/guestbook:v2', 'ports' => [ { 'name' => 'http-server', 'containerPort' => 3000 } ] } ] rc end def base_service(namespace) our_service = Kubeclient::Resource.new our_service.metadata = {} our_service.metadata.namespace = namespace our_service.metadata.labels = {} our_service.spec = {} our_service.spec.selector = {} our_service end def redis_slave_service(namespace) our_service = base_service(namespace) our_service.metadata.name = 'redis-slave' our_service.metadata.labels.app = 'redis' our_service.metadata.labels.role = 'slave' our_service.spec.ports = [{ 'port' => 6379, 'targetPort' => 'redis-server' }] our_service.spec.selector.app = 'redis' our_service.spec.selector.role = 'slave' our_service end def redis_service(namespace) our_service = base_service(namespace) our_service.metadata.name = 'redis-master' our_service.metadata.labels.app = 'redis' our_service.metadata.labels.role = 'master' our_service.spec.ports = [{ 'port' => 6379, 'targetPort' => 'redis-server' }] our_service.spec.selector.app = 'redis' our_service.spec.selector.role = 'master' our_service end def guestbook_service(namespace) our_service = base_service(namespace) our_service.metadata.name = 'guestbook' our_service.metadata.labels.name = 'guestbook' our_service.spec.ports = [{ 'port' => 3000, 'targetPort' => 'http-server' }] our_service.spec.selector.app = 'guestbook' our_service.type = 'LoadBalancer' our_service end end kubeclient-4.11.0/test/test_helper.rb000066400000000000000000000015251435070561000175500ustar00rootroot00000000000000require 'bundler/setup' require 'minitest/autorun' require 'minitest/rg' require 'webmock/minitest' require 'mocha/minitest' require 'json' require 'kubeclient' MiniTest::Test.class_eval do # Assumes test files will be in a subdirectory with the same name as the # file suffix. e.g. a file named foo.json would be a "json" subdirectory. def open_test_file(name) File.new(File.join(File.dirname(__FILE__), name.split('.').last, name)) end # kubeconfig files deviate from above convention. # They link to relaved certs etc. with various extensions, all in same dir. def config_file(name) File.join(File.dirname(__FILE__), 'config', name) end def stub_core_api_list stub_request(:get, %r{/api/v1$}) .to_return(body: open_test_file('core_api_resource_list.json'), status: 200) end end WebMock.disable_net_connect! kubeclient-4.11.0/test/test_kubeclient.rb000066400000000000000000000733161435070561000204250ustar00rootroot00000000000000require_relative 'test_helper' # Kubernetes client entity tests class KubeclientTest < MiniTest::Test def test_json our_object = Kubeclient::Resource.new our_object.foo = 'bar' our_object.nested = {} our_object.nested.again = {} our_object.nested.again.again = {} our_object.nested.again.again.name = 'aaron' expected = { 'foo' => 'bar', 'nested' => { 'again' => { 'again' => { 'name' => 'aaron' } } } } assert_equal(expected, JSON.parse(JSON.dump(our_object.to_h))) end def test_pass_uri # URI::Generic#hostname= was added in ruby 1.9.3 and will automatically # wrap an ipv6 address in [] uri = URI::HTTP.build(port: 8080) uri.hostname = 'localhost' client = Kubeclient::Client.new(uri) rest_client = client.rest_client assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_no_path_in_uri client = Kubeclient::Client.new('http://localhost:8080', 'v1') rest_client = client.rest_client assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_no_version_passed client = Kubeclient::Client.new('http://localhost:8080') rest_client = client.rest_client assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s) end def test_pass_proxy uri = URI::HTTP.build(host: 'localhost', port: 8080) proxy_uri = URI::HTTP.build(host: 'myproxyhost', port: 8888) stub_core_api_list client = Kubeclient::Client.new(uri, http_proxy_uri: proxy_uri) rest_client = client.rest_client assert_equal(proxy_uri.to_s, rest_client.options[:proxy]) watch_client = client.watch_pods assert_equal(watch_client.send(:build_client_options)[:proxy][:proxy_address], proxy_uri.host) assert_equal(watch_client.send(:build_client_options)[:proxy][:proxy_port], proxy_uri.port) end def test_pass_max_redirects max_redirects = 0 client = Kubeclient::Client.new('http://localhost:8080/api/', http_max_redirects: max_redirects) rest_client = client.rest_client assert_equal(max_redirects, rest_client.options[:max_redirects]) stub_request(:get, 'http://localhost:8080/api') .to_return(status: 302, headers: { location: 'http://localhost:1234/api' }) exception = assert_raises(Kubeclient::HttpError) { client.api } assert_equal(302, exception.error_code) end def test_exception stub_core_api_list stub_request(:post, %r{/services}) .to_return(body: open_test_file('namespace_exception.json'), status: 409) service = Kubeclient::Resource.new service.metadata = {} service.metadata.name = 'redisslave' service.metadata.namespace = 'default' # service.port = 80 # service.container_port = 6379 # service.protocol = 'TCP' client = Kubeclient::Client.new('http://localhost:8080/api/') exception = assert_raises(Kubeclient::HttpError) do service = client.create_service(service) end assert_instance_of(Kubeclient::HttpError, exception) assert_equal("converting to : type names don't match (Pod, Namespace)", exception.message) assert_includes(exception.to_s, ' for POST http://localhost:8080/api') assert_equal(409, exception.error_code) end def test_deprecated_exception error_message = 'certificate verify failed' stub_request(:get, 'http://localhost:8080/api') .to_raise(OpenSSL::SSL::SSLError.new(error_message)) client = Kubeclient::Client.new('http://localhost:8080/api/') exception = assert_raises(KubeException) { client.api } assert_equal(error_message, exception.message) end def test_api stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: open_test_file('versions_list.json')) response = client.api assert_includes(response, 'versions') end def test_api_ssl_failure error_message = 'certificate verify failed' stub_request(:get, 'http://localhost:8080/api') .to_raise(OpenSSL::SSL::SSLError.new(error_message)) client = Kubeclient::Client.new('http://localhost:8080/api/') exception = assert_raises(Kubeclient::HttpError) { client.api } assert_equal(error_message, exception.message) end def test_api_timeout stub_request(:get, 'http://localhost:8080/api').to_timeout client = Kubeclient::Client.new('http://localhost:8080/api/') exception = assert_raises(Kubeclient::HttpError) { client.api } assert_match(/(timed out|timeout)/i, exception.message) end def test_api_valid stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: open_test_file('versions_list.json')) args = ['http://localhost:8080/api/'] [nil, 'v1beta3', 'v1'].each do |version| client = Kubeclient::Client.new(*(version ? args + [version] : args)) assert client.api_valid? end end def test_api_valid_with_invalid_version stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: open_test_file('versions_list.json')) client = Kubeclient::Client.new('http://localhost:8080/api/', 'foobar1') refute client.api_valid? end def test_api_valid_with_unreported_versions stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: '{}') client = Kubeclient::Client.new('http://localhost:8080/api/') refute client.api_valid? end def test_api_valid_with_invalid_json stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: '[]') client = Kubeclient::Client.new('http://localhost:8080/api/') refute client.api_valid? end def test_api_valid_with_bad_endpoint stub_request(:get, 'http://localhost:8080/api') .to_return(status: [404, 'Resource Not Found']) client = Kubeclient::Client.new('http://localhost:8080/api/') assert_raises(Kubeclient::HttpError) { client.api_valid? } end def test_api_valid_with_non_json stub_request(:get, 'http://localhost:8080/api') .to_return(status: 200, body: '') client = Kubeclient::Client.new('http://localhost:8080/api/') assert_raises(JSON::ParserError) { client.api_valid? } end def test_nonjson_exception stub_core_api_list stub_request(:get, %r{/servic}) .to_return(body: open_test_file('service_illegal_json_404.json'), status: 404) exception = assert_raises(Kubeclient::ResourceNotFoundError) do client.get_services end assert(exception.message.include?('Not Found')) assert_equal(404, exception.error_code) end def test_nonjson_exception_raw stub_core_api_list stub_request(:get, %r{/servic}) .to_return(body: open_test_file('service_illegal_json_404.json'), status: 404) exception = assert_raises(Kubeclient::ResourceNotFoundError) do client.get_services(as: :raw) end assert(exception.message.include?('Not Found')) assert_equal(404, exception.error_code) end def test_entity_list stub_core_api_list stub_get_services services = client.get_services refute_empty(services) assert_instance_of(Kubeclient::Common::EntityList, services) # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic. assert_equal('Service', services.kind) assert_equal(2, services.size) assert_instance_of(Kubeclient::Resource, services[0]) assert_instance_of(Kubeclient::Resource, services[1]) assert_requested(:get, 'http://localhost:8080/api/v1/services', times: 1) end def test_entity_list_raw stub_core_api_list stub_get_services response = client.get_services(as: :raw) refute_empty(response) assert_equal(open_test_file('entity_list.json').read, response) assert_requested(:get, 'http://localhost:8080/api/v1/services', times: 1) end def test_entity_list_parsed stub_core_api_list stub_get_services response = client.get_services(as: :parsed) assert_equal Hash, response.class assert_equal 'ServiceList', response['kind'] assert_equal %w[metadata spec status], response['items'].first.keys end def test_entity_list_parsed_symbolized stub_core_api_list stub_get_services response = client.get_services(as: :parsed_symbolized) assert_equal Hash, response.class assert_equal 'ServiceList', response[:kind] assert_equal %i[metadata spec status], response[:items].first.keys end def test_entity_list_unknown stub_core_api_list stub_get_services e = assert_raises(ArgumentError) { client.get_services(as: :whoops) } assert_equal 'Unsupported format :whoops', e.message end def test_entity_list_raw_failure stub_core_api_list stub_request(:get, %r{/services}) .to_return(body: open_test_file('entity_list.json'), status: 500) exception = assert_raises(Kubeclient::HttpError) { client.get_services(as: :raw) } assert_equal('500 Internal Server Error', exception.message) assert_equal(500, exception.error_code) end def test_entities_with_label_selector selector = 'component=apiserver' stub_core_api_list stub_get_services services = client.get_services(label_selector: selector) assert_instance_of(Kubeclient::Common::EntityList, services) assert_requested( :get, "http://localhost:8080/api/v1/services?labelSelector=#{selector}", times: 1 ) end def test_entities_with_resource_version version = '329' stub_core_api_list stub_get_services services = client.get_services(resource_version: version) assert_instance_of(Kubeclient::Common::EntityList, services) assert_requested( :get, "http://localhost:8080/api/v1/services?resourceVersion=#{version}", times: 1 ) end def test_entities_with_field_selector selector = 'involvedObject.name=redis-master' stub_core_api_list stub_get_services services = client.get_services(field_selector: selector) assert_instance_of(Kubeclient::Common::EntityList, services) assert_requested( :get, "http://localhost:8080/api/v1/services?fieldSelector=#{selector}", times: 1 ) end def test_empty_list stub_core_api_list stub_request(:get, %r{/pods}) .to_return(body: open_test_file('empty_pod_list.json'), status: 200) pods = client.get_pods assert_instance_of(Kubeclient::Common::EntityList, pods) assert_equal(0, pods.size) end def test_get_all stub_core_api_list stub_request(:get, %r{/bindings}) .to_return(body: open_test_file('bindings_list.json'), status: 404) stub_request(:get, %r{/configmaps}) .to_return(body: open_test_file('config_map_list.json'), status: 200) stub_request(:get, %r{/podtemplates}) .to_return(body: open_test_file('pod_template_list.json'), status: 200) stub_request(:get, %r{/services}) .to_return(body: open_test_file('service_list.json'), status: 200) stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pod_list.json'), status: 200) stub_request(:get, %r{/nodes}) .to_return(body: open_test_file('node_list.json'), status: 200) stub_request(:get, %r{/replicationcontrollers}) .to_return(body: open_test_file('replication_controller_list.json'), status: 200) stub_request(:get, %r{/events}) .to_return(body: open_test_file('event_list.json'), status: 200) stub_request(:get, %r{/endpoints}) .to_return(body: open_test_file('endpoint_list.json'), status: 200) stub_request(:get, %r{/namespaces}) .to_return(body: open_test_file('namespace_list.json'), status: 200) stub_request(:get, %r{/secrets}) .to_return(body: open_test_file('secret_list.json'), status: 200) stub_request(:get, %r{/resourcequotas}) .to_return(body: open_test_file('resource_quota_list.json'), status: 200) stub_request(:get, %r{/limitranges}) .to_return(body: open_test_file('limit_range_list.json'), status: 200) stub_request(:get, %r{/persistentvolumes}) .to_return(body: open_test_file('persistent_volume_list.json'), status: 200) stub_request(:get, %r{/persistentvolumeclaims}) .to_return(body: open_test_file('persistent_volume_claim_list.json'), status: 200) stub_request(:get, %r{/componentstatuses}) .to_return(body: open_test_file('component_status_list.json'), status: 200) stub_request(:get, %r{/serviceaccounts}) .to_return(body: open_test_file('service_account_list.json'), status: 200) result = client.all_entities assert_equal(16, result.keys.size) assert_instance_of(Kubeclient::Common::EntityList, result['node']) assert_instance_of(Kubeclient::Common::EntityList, result['service']) assert_instance_of(Kubeclient::Common::EntityList, result['replication_controller']) assert_instance_of(Kubeclient::Common::EntityList, result['pod']) assert_instance_of(Kubeclient::Common::EntityList, result['event']) assert_instance_of(Kubeclient::Common::EntityList, result['namespace']) assert_instance_of(Kubeclient::Common::EntityList, result['secret']) assert_instance_of(Kubeclient::Resource, result['service'][0]) assert_instance_of(Kubeclient::Resource, result['node'][0]) assert_instance_of(Kubeclient::Resource, result['event'][0]) assert_instance_of(Kubeclient::Resource, result['endpoint'][0]) assert_instance_of(Kubeclient::Resource, result['namespace'][0]) assert_instance_of(Kubeclient::Resource, result['secret'][0]) assert_instance_of(Kubeclient::Resource, result['resource_quota'][0]) assert_instance_of(Kubeclient::Resource, result['limit_range'][0]) assert_instance_of(Kubeclient::Resource, result['persistent_volume'][0]) assert_instance_of(Kubeclient::Resource, result['persistent_volume_claim'][0]) assert_instance_of(Kubeclient::Resource, result['component_status'][0]) assert_instance_of(Kubeclient::Resource, result['service_account'][0]) end def test_get_all_raw stub_core_api_list stub_request(:get, %r{/bindings}) .to_return(body: open_test_file('bindings_list.json'), status: 404) stub_request(:get, %r{/configmaps}) .to_return(body: open_test_file('config_map_list.json'), status: 200) stub_request(:get, %r{/podtemplates}) .to_return(body: open_test_file('pod_template_list.json'), status: 200) stub_request(:get, %r{/services}) .to_return(body: open_test_file('service_list.json'), status: 200) stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pod_list.json'), status: 200) stub_request(:get, %r{/nodes}) .to_return(body: open_test_file('node_list.json'), status: 200) stub_request(:get, %r{/replicationcontrollers}) .to_return(body: open_test_file('replication_controller_list.json'), status: 200) stub_request(:get, %r{/events}) .to_return(body: open_test_file('event_list.json'), status: 200) stub_request(:get, %r{/endpoints}) .to_return(body: open_test_file('endpoint_list.json'), status: 200) stub_request(:get, %r{/namespaces}) .to_return(body: open_test_file('namespace_list.json'), status: 200) stub_request(:get, %r{/secrets}) .to_return(body: open_test_file('secret_list.json'), status: 200) stub_request(:get, %r{/resourcequotas}) .to_return(body: open_test_file('resource_quota_list.json'), status: 200) stub_request(:get, %r{/limitranges}) .to_return(body: open_test_file('limit_range_list.json'), status: 200) stub_request(:get, %r{/persistentvolumes}) .to_return(body: open_test_file('persistent_volume_list.json'), status: 200) stub_request(:get, %r{/persistentvolumeclaims}) .to_return(body: open_test_file('persistent_volume_claim_list.json'), status: 200) stub_request(:get, %r{/componentstatuses}) .to_return(body: open_test_file('component_status_list.json'), status: 200) stub_request(:get, %r{/serviceaccounts}) .to_return(body: open_test_file('service_account_list.json'), status: 200) result = client.all_entities(as: :raw) assert_equal(16, result.keys.size) %w[ component_status config_map endpoint event limit_range namespace node persistent_volume persistent_volume_claim pod replication_controller resource_quota secret service service_account ].each do |entity| assert_equal(open_test_file("#{entity}_list.json").read, result[entity]) end end def test_api_bearer_token_with_params_success stub_request(:get, 'http://localhost:8080/api/v1/pods?labelSelector=name=redis-master') .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('pod_list.json'), status: 200) stub_request(:get, %r{/api/v1$}) .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('core_api_resource_list.json'), status: 200) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { bearer_token: 'valid_token' } ) pods = client.get_pods(label_selector: 'name=redis-master') assert_equal('Pod', pods.kind) assert_equal(1, pods.size) end def test_api_bearer_token_success stub_core_api_list stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(headers: { Authorization: 'Bearer valid_token' }) .to_return( body: open_test_file('pod_list.json'), status: 200 ) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { bearer_token: 'valid_token' } ) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) end def test_api_bearer_token_failure error_message = '"/api/v1" is forbidden because ' \ 'system:anonymous cannot list on pods in' response = OpenStruct.new(code: 401, message: error_message) stub_request(:get, 'http://localhost:8080/api/v1') .with(headers: { Authorization: 'Bearer invalid_token' }) .to_raise(Kubeclient::HttpError.new(403, error_message, response)) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { bearer_token: 'invalid_token' } ) exception = assert_raises(Kubeclient::HttpError) { client.get_pods } assert_equal(403, exception.error_code) assert_equal(error_message, exception.message) assert_equal(response, exception.response) end def test_api_bearer_token_failure_raw error_message = '"/api/v1" is forbidden because ' \ 'system:anonymous cannot list on pods in' response = OpenStruct.new(code: 401, message: error_message) stub_request(:get, 'http://localhost:8080/api/v1') .with(headers: { Authorization: 'Bearer invalid_token' }) .to_raise(Kubeclient::HttpError.new(403, error_message, response)) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { bearer_token: 'invalid_token' } ) exception = assert_raises(Kubeclient::HttpError) { client.get_pods(as: :raw) } assert_equal(403, exception.error_code) assert_equal(error_message, exception.message) assert_equal(response, exception.response) end def test_api_basic_auth_success stub_request(:get, 'http://localhost:8080/api/v1') .with(basic_auth: %w[username password]) .to_return(body: open_test_file('core_api_resource_list.json'), status: 200) stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(basic_auth: %w[username password]) .to_return(body: open_test_file('pod_list.json'), status: 200) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { username: 'username', password: 'password' } ) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) assert_requested( :get, 'http://localhost:8080/api/v1/pods', times: 1 ) end def test_api_basic_auth_back_comp_success stub_request(:get, 'http://localhost:8080/api/v1') .with(basic_auth: %w[username password]) .to_return(body: open_test_file('core_api_resource_list.json'), status: 200) stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(basic_auth: %w[username password]) .to_return(body: open_test_file('pod_list.json'), status: 200) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { user: 'username', password: 'password' } ) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) assert_requested(:get, 'http://localhost:8080/api/v1/pods', times: 1) end def test_api_basic_auth_failure error_message = 'HTTP status code 401, 401 Unauthorized' response = OpenStruct.new(code: 401, message: '401 Unauthorized') stub_request(:get, 'http://localhost:8080/api/v1') .with(basic_auth: %w[username password]) .to_raise(Kubeclient::HttpError.new(401, error_message, response)) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { username: 'username', password: 'password' } ) exception = assert_raises(Kubeclient::HttpError) { client.get_pods } assert_equal(401, exception.error_code) assert_equal(error_message, exception.message) assert_equal(response, exception.response) assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) end def test_api_basic_auth_failure_raw error_message = 'HTTP status code 401, 401 Unauthorized' response = OpenStruct.new(code: 401, message: '401 Unauthorized') stub_request(:get, 'http://localhost:8080/api/v1') .with(basic_auth: %w[username password]) .to_raise(Kubeclient::HttpError.new(401, error_message, response)) client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { username: 'username', password: 'password' } ) exception = assert_raises(Kubeclient::HttpError) { client.get_pods(as: :raw) } assert_equal(401, exception.error_code) assert_equal(error_message, exception.message) assert_equal(response, exception.response) assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) end def test_init_username_no_password expected_msg = 'Basic auth requires both username & password' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { username: 'username' } ) end assert_equal(expected_msg, exception.message) end def test_init_user_no_password expected_msg = 'Basic auth requires both username & password' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { user: 'username' } ) end assert_equal(expected_msg, exception.message) end def test_init_username_and_bearer_token expected_msg = 'Invalid auth options: specify only one of username/password,' \ ' bearer_token or bearer_token_file' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { username: 'username', bearer_token: 'token' } ) end assert_equal(expected_msg, exception.message) end def test_init_username_and_bearer_token_file expected_msg = 'Invalid auth options: specify only one of username/password,' \ ' bearer_token or bearer_token_file' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { username: 'username', bearer_token_file: 'token-file' } ) end assert_equal(expected_msg, exception.message) end def test_bearer_token_and_bearer_token_file expected_msg = 'Invalid auth options: specify only one of username/password,' \ ' bearer_token or bearer_token_file' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { bearer_token: 'token', bearer_token_file: 'token-file' } ) end assert_equal(expected_msg, exception.message) end def test_bearer_token_file_not_exist expected_msg = 'Token file token-file does not exist' exception = assert_raises(ArgumentError) do Kubeclient::Client.new( 'http://localhost:8080', auth_options: { bearer_token_file: 'token-file' } ) end assert_equal(expected_msg, exception.message) end def test_api_bearer_token_file_success stub_core_api_list stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('pod_list.json'), status: 200) file = File.join(File.dirname(__FILE__), 'valid_token_file') client = Kubeclient::Client.new( 'http://localhost:8080/api/', auth_options: { bearer_token_file: file } ) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) end def test_refresh_api_bearer_token_file stub_core_api_list file = Tempfile.new('token') begin file.write("valid_token") file.rewind client = Kubeclient::Client.new( 'http://localhost:8080/api/', 'v1', auth_options: { bearer_token_file: file.path } ) stub_token = stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('pod_list.json'), status: 200) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) remove_request_stub(stub_token) file.write("rotated_token") file.close stub_token = stub_request(:get, 'http://localhost:8080/api/v1/pods') .with(headers: { Authorization: 'Bearer rotated_token' }) .to_return(body: open_test_file('pod_list.json'), status: 200) pods = client.get_pods assert_equal('Pod', pods.kind) assert_equal(1, pods.size) ensure file.close file.unlink # deletes the temp file end end def test_proxy_url stub_core_api_list client = Kubeclient::Client.new('http://host:8080', 'v1') assert_equal( 'http://host:8080/api/v1/namespaces/ns/services/srvname:srvportname/proxy', client.proxy_url('service', 'srvname', 'srvportname', 'ns') ) assert_equal( 'http://host:8080/api/v1/namespaces/ns/services/srvname:srvportname/proxy', client.proxy_url('services', 'srvname', 'srvportname', 'ns') ) assert_equal( 'http://host:8080/api/v1/namespaces/ns/pods/srvname:srvportname/proxy', client.proxy_url('pod', 'srvname', 'srvportname', 'ns') ) assert_equal( 'http://host:8080/api/v1/namespaces/ns/pods/srvname:srvportname/proxy', client.proxy_url('pods', 'srvname', 'srvportname', 'ns') ) # Check no namespace provided assert_equal( 'http://host:8080/api/v1/nodes/srvname:srvportname/proxy', client.proxy_url('nodes', 'srvname', 'srvportname') ) assert_equal( 'http://host:8080/api/v1/nodes/srvname:srvportname/proxy', client.proxy_url('node', 'srvname', 'srvportname') ) # Check integer port assert_equal( 'http://host:8080/api/v1/nodes/srvname:5001/proxy', client.proxy_url('nodes', 'srvname', 5001) ) assert_equal( 'http://host:8080/api/v1/nodes/srvname:5001/proxy', client.proxy_url('node', 'srvname', 5001) ) end def test_attr_readers client = Kubeclient::Client.new( 'http://localhost:8080/api/', ssl_options: { client_key: 'secret' }, auth_options: { bearer_token: 'token' } ) assert_equal('/api', client.api_endpoint.path) assert_equal('secret', client.ssl_options[:client_key]) assert_equal('token', client.auth_options[:bearer_token]) assert_equal('Bearer token', client.headers[:Authorization]) end def test_nil_items # handle https://github.com/kubernetes/kubernetes/issues/13096 stub_core_api_list stub_request(:get, %r{/persistentvolumeclaims}) .to_return(body: open_test_file('persistent_volume_claims_nil_items.json'), status: 200) client.get_persistent_volume_claims end # Timeouts def test_timeouts_defaults client = Kubeclient::Client.new( 'http://localhost:8080/api/' ) rest_client = client.rest_client assert_default_open_timeout(rest_client.open_timeout) assert_equal(60, rest_client.read_timeout) end def test_timeouts_open client = Kubeclient::Client.new( 'http://localhost:8080/api/', timeouts: { open: 10 } ) rest_client = client.rest_client assert_equal(10, rest_client.open_timeout) assert_equal(60, rest_client.read_timeout) end def test_timeouts_read client = Kubeclient::Client.new( 'http://localhost:8080/api/', timeouts: { read: 300 } ) rest_client = client.rest_client assert_default_open_timeout(rest_client.open_timeout) assert_equal(300, rest_client.read_timeout) end def test_timeouts_both client = Kubeclient::Client.new( 'http://localhost:8080/api/', timeouts: { open: 10, read: 300 } ) rest_client = client.rest_client assert_equal(10, rest_client.open_timeout) assert_equal(300, rest_client.read_timeout) end def test_timeouts_infinite client = Kubeclient::Client.new( 'http://localhost:8080/api/', timeouts: { open: nil, read: nil } ) rest_client = client.rest_client assert_nil(rest_client.open_timeout) assert_nil(rest_client.read_timeout) end def assert_default_open_timeout(actual) if RUBY_VERSION >= '2.3' assert_equal(60, actual) else assert_nil(actual) end end private def stub_get_services stub_request(:get, %r{/services}) .to_return(body: open_test_file('entity_list.json'), status: 200) end def client @client ||= Kubeclient::Client.new('http://localhost:8080/api/', 'v1') end # dup method creates a shallow copy which is not good in this case # since rename_keys changes the input hash # hence need to create a deep_copy def deep_copy(hash) Marshal.load(Marshal.dump(hash)) end end kubeclient-4.11.0/test/test_limit_range.rb000066400000000000000000000015241435070561000205620ustar00rootroot00000000000000require_relative 'test_helper' # LimitRange tests class TestLimitRange < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/limitranges}) .to_return(body: open_test_file('limit_range.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') limit_range = client.get_limit_range('limits', 'quota-example') assert_instance_of(Kubeclient::Resource, limit_range) assert_equal('limits', limit_range.metadata.name) assert_equal('Container', limit_range.spec.limits[0].type) assert_equal('100m', limit_range.spec.limits[0].default.cpu) assert_equal('512Mi', limit_range.spec.limits[0].default.memory) assert_requested( :get, 'http://localhost:8080/api/v1/namespaces/quota-example/limitranges/limits', times: 1 ) end end kubeclient-4.11.0/test/test_missing_methods.rb000066400000000000000000000064641435070561000214740ustar00rootroot00000000000000require_relative 'test_helper' # Test method_missing, respond_to? and respond_to_missing behaviour class TestMissingMethods < MiniTest::Test def test_missing stub_core_api_list client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_equal(true, client.respond_to?(:get_pod)) assert_equal(true, client.respond_to?(:get_pods)) assert_equal(false, client.respond_to?(:get_pie)) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery assert_equal(false, client.respond_to?(:get_pie)) assert_equal(true, client.respond_to?(:get_pods)) assert_equal(true, client.respond_to?(:get_pod)) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery assert_instance_of(Method, client.method(:get_pods)) assert_raises(NameError) do client.method(:get_pies) end client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery assert_raises(NameError) do client.method(:get_pies) end assert_instance_of(Method, client.method(:get_pods)) stub_request(:get, %r{/api/v1$}).to_return( body: '', status: 404 ) # If discovery fails we expect the below raise an exception client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_raises(Kubeclient::HttpError) do client.discover end client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_raises(Kubeclient::HttpError) do client.method(:get_pods) end client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_raises(Kubeclient::HttpError) do client.respond_to?(:get_pods) end end def test_nonsuffix_plurals stub_request(:get, %r{/apis/extensions/v1beta1$}).to_return( body: open_test_file('extensions_v1beta1_api_resource_list.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/apis/extensions', 'v1beta1') assert_equal(true, client.respond_to?(:get_network_policy)) assert_equal(true, client.respond_to?(:get_network_policies)) assert_equal(true, client.respond_to?(:get_pod_security_policy)) assert_equal(true, client.respond_to?(:get_pod_security_policies)) end def test_irregular_names stub_core_api_list client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_equal(true, client.respond_to?(:get_endpoint)) assert_equal(true, client.respond_to?(:get_endpoints)) stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( body: open_test_file('security.openshift.io_api_resource_list.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') assert_equal(true, client.respond_to?(:get_security_context_constraint)) assert_equal(true, client.respond_to?(:get_security_context_constraints)) end def test_lowercase_kind stub_request(:get, %r{/apis/config.istio.io/v1alpha2$}).to_return( body: open_test_file('config.istio.io_api_resource_list.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/apis/config.istio.io', 'v1alpha2') assert_equal(true, client.respond_to?(:get_servicecontrolreport)) assert_equal(true, client.respond_to?(:get_servicecontrolreports)) end end kubeclient-4.11.0/test/test_namespace.rb000066400000000000000000000036541435070561000202320ustar00rootroot00000000000000require_relative 'test_helper' # Namespace entity tests class TestNamespace < MiniTest::Test def test_get_namespace_v1 stub_core_api_list stub_request(:get, %r{/namespaces}) .to_return(body: open_test_file('namespace.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') namespace = client.get_namespace('staging') assert_instance_of(Kubeclient::Resource, namespace) assert_equal('e388bc10-c021-11e4-a514-3c970e4a436a', namespace.metadata.uid) assert_equal('staging', namespace.metadata.name) assert_equal('1168', namespace.metadata.resourceVersion) assert_equal('v1', namespace.apiVersion) assert_requested( :get, 'http://localhost:8080/api/v1/namespaces/staging', times: 1 ) end def test_delete_namespace_v1 our_namespace = Kubeclient::Resource.new our_namespace.metadata = {} our_namespace.metadata.name = 'staging' stub_core_api_list stub_request(:delete, %r{/namespaces}) .to_return(body: open_test_file('namespace.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') our_namespace = client.delete_namespace(our_namespace.metadata.name) assert_kind_of(RecursiveOpenStruct, our_namespace) assert_requested( :delete, 'http://localhost:8080/api/v1/namespaces/staging', times: 1 ) end def test_create_namespace stub_core_api_list stub_request(:post, %r{/namespaces}) .to_return(body: open_test_file('created_namespace.json'), status: 201) namespace = Kubeclient::Resource.new namespace.metadata = {} namespace.metadata.name = 'development' client = Kubeclient::Client.new('http://localhost:8080/api/') created_namespace = client.create_namespace(namespace) assert_instance_of(Kubeclient::Resource, created_namespace) assert_equal(namespace.metadata.name, created_namespace.metadata.name) end end kubeclient-4.11.0/test/test_node.rb000066400000000000000000000040021435070561000172070ustar00rootroot00000000000000require_relative 'test_helper' # Node entity tests class TestNode < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/nodes}) .to_return(body: open_test_file('node.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') node = client.get_node('127.0.0.1') assert_instance_of(Kubeclient::Resource, node) assert_equal('041143c5-ce39-11e4-ac24-3c970e4a436a', node.metadata.uid) assert_equal('127.0.0.1', node.metadata.name) assert_equal('1724', node.metadata.resourceVersion) assert_equal('v1', node.apiVersion) assert_equal('2015-03-19T15:08:20+02:00', node.metadata.creationTimestamp) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/nodes/127.0.0.1', times: 1 ) end def test_get_from_json_v1_raw stub_core_api_list stub_request(:get, %r{/nodes}) .to_return(body: open_test_file('node.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') response = client.get_node('127.0.0.1', nil, as: :raw) assert_equal(open_test_file('node.json').read, response) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/nodes/127.0.0.1', times: 1 ) end def test_get_from_json_v1_raw_error stub_request(:get, %r{/nodes}) .to_return(body: open_test_file('node.json'), status: 200) stub_request(:get, %r{/api/v1$}) .to_return(body: open_test_file('core_api_resource_list.json'), status: 500) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') exception = assert_raises(Kubeclient::HttpError) do client.get_node('127.0.0.1', nil, as: :raw) end assert_instance_of(Kubeclient::HttpError, exception) assert_equal('500 Internal Server Error', exception.message) end end kubeclient-4.11.0/test/test_oidc_auth_provider.rb000066400000000000000000000066101435070561000221420ustar00rootroot00000000000000require_relative 'test_helper' require 'openid_connect' class OIDCAuthProviderTest < MiniTest::Test def setup @client_id = 'client_id' @client_secret = 'client_secret' @idp_issuer_url = 'idp_issuer_url' @refresh_token = 'refresh_token' @id_token = 'id_token' @new_id_token = 'new_id_token' end def test_expired_token OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i - 7200)) do OpenIDConnect::Client.stub(:new, openid_client_mock) do retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 'client-id' => @client_id, 'client-secret' => @client_secret, 'id-token' => @id_token, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token ) assert_equal(@new_id_token, retrieved_id_token) end end end end def test_valid_token OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i + 7200)) do retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 'client-id' => @client_id, 'client-secret' => @client_secret, 'id-token' => @id_token, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token ) assert_equal(@id_token, retrieved_id_token) end end end def test_missing_id_token OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do OpenIDConnect::Client.stub(:new, openid_client_mock) do retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 'client-id' => @client_id, 'client-secret' => @client_secret, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token ) assert_equal(@new_id_token, retrieved_id_token) end end end def test_token_with_unknown_kid OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do OpenIDConnect::ResponseObject::IdToken.stub( :decode, ->(_token, _jwks) { raise JSON::JWK::Set::KidNotFound } ) do OpenIDConnect::Client.stub(:new, openid_client_mock) do retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 'client-id' => @client_id, 'client-secret' => @client_secret, 'id-token' => @id_token, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token ) assert_equal(@new_id_token, retrieved_id_token) end end end end private def openid_client_mock access_token = Minitest::Mock.new access_token.expect(@id_token, @new_id_token) openid_client = Minitest::Mock.new openid_client.expect(:refresh_token=, nil, [@refresh_token]) openid_client.expect(:access_token!, access_token) end def id_token_mock(expiry) id_token_mock = Minitest::Mock.new id_token_mock.expect(:exp, expiry) end def discovery_mock discovery = Minitest::Mock.new discovery.expect(:jwks, 'jwks') discovery.expect(:authorization_endpoint, 'authz_endpoint') discovery.expect(:token_endpoint, 'token_endpoint') discovery.expect(:userinfo_endpoint, 'userinfo_endpoint') discovery end end kubeclient-4.11.0/test/test_persistent_volume.rb000066400000000000000000000015111435070561000220530ustar00rootroot00000000000000require_relative 'test_helper' # PersistentVolume tests class TestPersistentVolume < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/persistentvolumes}) .to_return(body: open_test_file('persistent_volume.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') volume = client.get_persistent_volume('pv0001') assert_instance_of(Kubeclient::Resource, volume) assert_equal('pv0001', volume.metadata.name) assert_equal('10Gi', volume.spec.capacity.storage) assert_equal('/tmp/data01', volume.spec.hostPath.path) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/persistentvolumes/pv0001', times: 1 ) end end kubeclient-4.11.0/test/test_persistent_volume_claim.rb000066400000000000000000000016131435070561000232230ustar00rootroot00000000000000require_relative 'test_helper' # PersistentVolumeClaim tests class TestPersistentVolumeClaim < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/persistentvolumeclaims}) .to_return(body: open_test_file('persistent_volume_claim.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') claim = client.get_persistent_volume_claim('myclaim-1', 'default') assert_instance_of(Kubeclient::Resource, claim) assert_equal('myclaim-1', claim.metadata.name) assert_equal('3Gi', claim.spec.resources.requests.storage) assert_equal('pv0001', claim.spec.volumeName) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1', times: 1 ) end end kubeclient-4.11.0/test/test_pod.rb000066400000000000000000000042711435070561000170540ustar00rootroot00000000000000require_relative 'test_helper' # Pod entity tests class TestPod < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pod.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') pod = client.get_pod('redis-master-pod', 'default') assert_instance_of(Kubeclient::Resource, pod) assert_equal('redis-master3', pod.metadata.name) assert_equal('dockerfile/redis', pod.spec.containers[0]['image']) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod', times: 1 ) end def test_get_chunks stub_core_api_list stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pods_1.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') pods = client.get_pods(limit: 2) assert_equal(2, pods.count) assert_equal('eyJ2IjoibWV0YS5rOHMua', pods.continue) continue = pods.continue stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pods_2.json'), status: 200) pods = client.get_pods(limit: 2, continue: continue) assert_equal(2, pods.count) assert_nil(pods.continue) assert_requested( :get, 'http://localhost:8080/api/v1', times: 1 ) assert_requested( :get, 'http://localhost:8080/api/v1/pods?limit=2', times: 1 ) assert_requested( :get, "http://localhost:8080/api/v1/pods?continue=#{continue}&limit=2", times: 1 ) end def test_get_chunks_410_gone stub_core_api_list stub_request(:get, %r{/pods}) .to_return(body: open_test_file('pods_410.json'), status: 410) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') err = assert_raises Kubeclient::HttpError do client.get_pods(limit: 2, continue: 'eyJ2IjoibWV0YS5') end assert_equal(err.message, "The provided from parameter is too old to display a consistent list result. \ You must start a new list without the from.") end end kubeclient-4.11.0/test/test_pod_log.rb000066400000000000000000000131701435070561000177130ustar00rootroot00000000000000require_relative 'test_helper' # Pod log tests class TestPodLog < MiniTest::Test def test_get_pod_log stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) .to_return(body: open_test_file('pod_log.txt'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') retrieved_log = client.get_pod_log('redis-master-pod', 'default') assert_equal(open_test_file('pod_log.txt').read, retrieved_log) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log', times: 1) end def test_get_pod_log_container stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) .to_return(body: open_test_file('pod_log.txt'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') retrieved_log = client.get_pod_log('redis-master-pod', 'default', container: 'ruby') assert_equal(open_test_file('pod_log.txt').read, retrieved_log) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?container=ruby', times: 1) end def test_get_pod_log_since_time stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) .to_return(body: open_test_file('pod_log.txt'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') retrieved_log = client.get_pod_log('redis-master-pod', 'default', timestamps: true, since_time: '2018-04-27T18:30:17.480321984Z') assert_equal(open_test_file('pod_log.txt').read, retrieved_log) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?sinceTime=2018-04-27T18:30:17.480321984Z×tamps=true', times: 1) end def test_get_pod_log_tail_lines selected_lines = open_test_file('pod_log.txt').to_a[-2..1].join stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) .to_return(body: selected_lines, status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') retrieved_log = client.get_pod_log('redis-master-pod', 'default', tail_lines: 2) assert_equal(selected_lines, retrieved_log) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?tailLines=2', times: 1) end def test_get_pod_limit_bytes selected_bytes = open_test_file('pod_log.txt').read(10) stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) .to_return(body: selected_bytes, status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') retrieved_log = client.get_pod_log('redis-master-pod', 'default', limit_bytes: 10) assert_equal(selected_bytes, retrieved_log) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?limitBytes=10', times: 1) end def test_watch_pod_log file = open_test_file('pod_log.txt') expected_lines = file.read.split("\n") stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) .to_return(body: file, status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') stream = client.watch_pod_log('redis-master-pod', 'default') stream.to_enum.with_index do |notice, index| assert_instance_of(String, notice) assert_equal(expected_lines[index], notice) end end def test_watch_pod_log_with_block file = open_test_file('pod_log.txt') first = file.readlines.first.chomp stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) .to_return(body: file, status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') client.watch_pod_log('redis-master-pod', 'default') do |line| assert_equal first, line break end end def test_watch_pod_log_follow_redirect expected_lines = open_test_file('pod_log.txt').read.split("\n") redirect = 'http://localhost:1234/api/namespaces/default/pods/redis-master-pod/log' stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) .to_return(status: 302, headers: { location: redirect }) stub_request(:get, redirect) .to_return(body: open_test_file('pod_log.txt'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') stream = client.watch_pod_log('redis-master-pod', 'default') stream.to_enum.with_index do |notice, index| assert_instance_of(String, notice) assert_equal(expected_lines[index], notice) end end def test_watch_pod_log_max_redirect redirect = 'http://localhost:1234/api/namespaces/default/pods/redis-master-pod/log' stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) .to_return(status: 302, headers: { location: redirect }) stub_request(:get, redirect) .to_return(body: open_test_file('pod_log.txt'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1', http_max_redirects: 0) assert_raises(Kubeclient::HttpError) do client.watch_pod_log('redis-master-pod', 'default').each do end end end end kubeclient-4.11.0/test/test_process_template.rb000066400000000000000000000063711435070561000216460ustar00rootroot00000000000000require_relative 'test_helper' # Process Template tests class TestProcessTemplate < MiniTest::Test def test_process_template client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') template = {} template[:metadata] = {} template[:metadata][:name] = 'my-template' template[:metadata][:namespace] = 'default' template[:kind] = 'Template' template[:apiVersion] = 'v1' service = {} service[:metadata] = {} service[:metadata][:name] = '${NAME_PREFIX}my-service' service[:kind] = 'Service' service[:apiVersion] = 'v1' template[:objects] = [service] param = { name: 'NAME_PREFIX', value: 'test/' } template[:parameters] = [param] req_body = '{"metadata":{"name":"my-template","namespace":"default"},' \ '"kind":"Template","apiVersion":"v1","objects":[{"metadata":' \ '{"name":"${NAME_PREFIX}my-service"},"kind":"Service","apiVersion":"v1"}],' \ '"parameters":[{"name":"NAME_PREFIX","value":"test/"}]}' expected_url = 'http://localhost:8080/api/v1/namespaces/default/processedtemplates' stub_request(:post, expected_url) .with(body: req_body, headers: { 'Content-Type' => 'application/json' }) .to_return(body: open_test_file('processed_template.json'), status: 200) processed_template = client.process_template(template) assert_equal('test/my-service', processed_template['objects'].first['metadata']['name']) assert_requested(:post, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['kind'] == 'Template' && data['apiVersion'] == 'v1' && data['metadata']['name'] == 'my-template' && data['metadata']['namespace'] == 'default' end end # Ensure _template and _templates methods hit `/templates` rather than # `/processedtemplates` URL. def test_templates_methods stub_request(:get, %r{/apis/template\.openshift\.io/v1$}).to_return( body: open_test_file('template.openshift.io_api_resource_list.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/apis/template.openshift.io', 'v1') expected_url = 'http://localhost:8080/apis/template.openshift.io/v1/namespaces/default/templates' stub_request(:get, expected_url) .to_return(body: open_test_file('template_list.json'), status: 200) client.get_templates(namespace: 'default') assert_requested(:get, expected_url, times: 1) expected_url = 'http://localhost:8080/apis/template.openshift.io/v1/namespaces/default/templates/my-template' stub_request(:get, expected_url) .to_return(body: open_test_file('template.json'), status: 200) client.get_template('my-template', 'default') assert_requested(:get, expected_url, times: 1) end def test_no_processedtemplates_methods stub_request(:get, %r{/apis/template\.openshift\.io/v1$}).to_return( body: open_test_file('template.openshift.io_api_resource_list.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/apis/template.openshift.io', 'v1') client.discover refute_respond_to(client, :get_processedtemplates) refute_respond_to(client, :get_processedtemplate) refute_respond_to(client, :get_processed_templates) refute_respond_to(client, :get_processed_template) end end kubeclient-4.11.0/test/test_real_cluster.rb000066400000000000000000000134521435070561000207570ustar00rootroot00000000000000require_relative 'test_helper' class KubeclientRealClusterTest < MiniTest::Test # Tests here actually connect to a cluster! # For simplicity, these tests use same config/*.kubeconfig files as test_config.rb, # so are intended to run from config/update_certs_k0s.rb script. def setup if ENV['KUBECLIENT_TEST_REAL_CLUSTER'] == 'true' WebMock.enable_net_connect! else skip('Requires real cluster, see test/config/update_certs_k0s.rb.') end end def teardown WebMock.disable_net_connect! # Don't allow any connections in other tests. end # Partially isolated tests that check Client behavior with given `verify_ssl` value: # localhost and 127.0.0.1 are among names on the certificate HOSTNAME_COVERED_BY_CERT = 'https://127.0.0.1:6443'.freeze # 127.0.0.2 also means localhost but is not included in the certificate. HOSTNAME_NOT_ON_CERT = 'https://127.0.0.2:6443'.freeze def test_real_cluster_verify_peer config = Kubeclient::Config.read(config_file('external.kubeconfig')) context = config.context client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_PEER), auth_options: context.auth_options ) check_cert_accepted(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_PEER), auth_options: context.auth_options ) check_cert_rejected(client2) end def test_real_cluster_verify_none config = Kubeclient::Config.read(config_file('external.kubeconfig')) context = config.context client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_NONE), auth_options: context.auth_options ) check_cert_accepted(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_NONE), auth_options: context.auth_options ) check_cert_accepted(client2) end # Integration tests that check combined Config -> Client behavior wrt. `verify_ssl`. # Quite redundant, but this was an embarrasing vulnerability so want to confirm... def test_real_cluster_concatenated_ca config = Kubeclient::Config.read(config_file('concatenated-ca.kubeconfig')) context = config.context client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_accepted(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_rejected(client2) end def test_real_cluster_verify_ssl_with_ca config = Kubeclient::Config.read(config_file('external.kubeconfig')) context = config.context client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_accepted(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_rejected(client2) end def test_real_cluster_verify_ssl_without_ca config = Kubeclient::Config.read(config_file('external-without-ca.kubeconfig')) context = config.context # Hostname matches cert but the local cluster uses self-signed certs from custom CA, # and this config omits CA data, so verification can't succeed. client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_rejected(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_rejected(client2) end def test_real_cluster_insecure_without_ca config = Kubeclient::Config.read(config_file('insecure.kubeconfig')) context = config.context # Hostname matches cert but the local cluster uses self-signed certs from custom CA, # and this config omits CA data, so verification would fail; # however, this config specifies `insecure-skip-tls-verify: true` so any cert goes. client1 = Kubeclient::Client.new( HOSTNAME_COVERED_BY_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_accepted(client1) client2 = Kubeclient::Client.new( HOSTNAME_NOT_ON_CERT, 'v1', ssl_options: context.ssl_options, auth_options: context.auth_options ) check_cert_accepted(client2) end private # Test cert checking on discovery, CRUD, and watch code paths. def check_cert_accepted(client) client.discover client.get_nodes exercise_watcher_with_timeout(client.watch_nodes) end def check_cert_rejected(client) # TODO: all OpenSSL exceptions should be wrapped with Kubeclient error. assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do client.discover end # Since discovery fails, methods like .get_nodes, .watch_nodes would all fail # on method_missing -> discover. Call lower-level methods to test actual connection. assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do client.get_entities('Node', 'nodes', {}) end assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do exercise_watcher_with_timeout(client.watch_entities('nodes')) end end def exercise_watcher_with_timeout(watcher) thread = Thread.new do sleep(1) watcher.finish end watcher.each do |_notice| break end thread.join end end kubeclient-4.11.0/test/test_replication_controller.rb000066400000000000000000000035301435070561000230430ustar00rootroot00000000000000require_relative 'test_helper' # Replication Controller entity tests class TestReplicationController < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/replicationcontrollers}) .to_return(body: open_test_file('replication_controller.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') rc = client.get_replication_controller('frontendController', 'default') assert_instance_of(Kubeclient::Resource, rc) assert_equal('guestbook-controller', rc.metadata.name) assert_equal('c71aa4c0-a240-11e4-a265-3c970e4a436a', rc.metadata.uid) assert_equal('default', rc.metadata.namespace) assert_equal(3, rc.spec.replicas) assert_equal('guestbook', rc.spec.selector.name) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController', times: 1) end def test_delete_replicaset_cascade stub_core_api_list client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') opts = Kubeclient::Resource.new( apiVersion: 'meta/v1', gracePeriodSeconds: 0, kind: 'DeleteOptions', propagationPolicy: 'Foreground' ) stub_request(:delete, 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController') .with(body: opts.to_hash.to_json) .to_return(status: 200, body: open_test_file('replication_controller.json'), headers: {}) rc = client.delete_replication_controller('frontendController', 'default', delete_options: opts) assert_kind_of(RecursiveOpenStruct, rc) assert_requested(:delete, 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController', times: 1) end end kubeclient-4.11.0/test/test_resource_list_without_kind.rb000066400000000000000000000040201435070561000237340ustar00rootroot00000000000000require_relative 'test_helper' # Core api resource list without kind tests class TestResourceListWithoutKind < MiniTest::Test def test_get_from_json_api_v1 stub_request(:get, %r{/api/v1$}) .to_return(body: open_test_file('core_api_resource_list_without_kind.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') client.discover [ { entity: 'pod', type: 'Pod', name: 'pods', methods: %w[pod pods] }, { entity: 'node', type: 'Node', name: 'nodes', methods: %w[node nodes] }, { entity: 'service', type: 'Service', name: 'services', methods: %w[service services] } ].each { |h| assert_entities(client.instance_variable_get(:@entities)[h[:entity]], h) } assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) end def test_get_from_json_oapi_v1 stub_request(:get, %r{/oapi/v1$}) .to_return(body: open_test_file('core_oapi_resource_list_without_kind.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/oapi/', 'v1') client.discover [ { entity: 'template', type: 'Template', name: 'templates', methods: %w[template templates] }, { entity: 'build', type: 'Build', name: 'builds', methods: %w[build builds] }, { entity: 'project', type: 'Project', name: 'projects', methods: %w[project projects] } ].each { |h| assert_entities(client.instance_variable_get(:@entities)[h[:entity]], h) } assert_requested(:get, 'http://localhost:8080/oapi/v1', times: 1) end def assert_entities(entity, h) assert_equal(entity.entity_type, h[:type]) assert_equal(entity.resource_name, h[:name]) assert_equal(entity.method_names, h[:methods]) end end kubeclient-4.11.0/test/test_resource_quota.rb000066400000000000000000000014131435070561000213250ustar00rootroot00000000000000require_relative 'test_helper' # ResourceQuota tests class TestResourceQuota < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/resourcequotas}) .to_return(body: open_test_file('resource_quota.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') quota = client.get_resource_quota('quota', 'quota-example') assert_instance_of(Kubeclient::Resource, quota) assert_equal('quota', quota.metadata.name) assert_equal('20', quota.spec.hard.cpu) assert_equal('10', quota.spec.hard.secrets) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/quota-example/resourcequotas/quota', times: 1) end end kubeclient-4.11.0/test/test_secret.rb000066400000000000000000000042211435070561000175520ustar00rootroot00000000000000require_relative 'test_helper' # Namespace entity tests class TestSecret < MiniTest::Test def test_get_secret_v1 stub_core_api_list stub_request(:get, %r{/secrets}) .to_return(body: open_test_file('created_secret.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') secret = client.get_secret('test-secret', 'dev') assert_instance_of(Kubeclient::Resource, secret) assert_equal('4e38a198-2bcb-11e5-a483-0e840567604d', secret.metadata.uid) assert_equal('test-secret', secret.metadata.name) assert_equal('v1', secret.apiVersion) assert_equal('Y2F0J3MgYXJlIGF3ZXNvbWUK', secret.data['super-secret']) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/dev/secrets/test-secret', times: 1) end def test_delete_secret_v1 stub_core_api_list stub_request(:delete, %r{/secrets}) .to_return(status: 200, body: open_test_file('created_secret.json')) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') secret = client.delete_secret('test-secret', 'dev') assert_kind_of(RecursiveOpenStruct, secret) assert_requested(:delete, 'http://localhost:8080/api/v1/namespaces/dev/secrets/test-secret', times: 1) end def test_create_secret_v1 stub_core_api_list stub_request(:post, %r{/secrets}) .to_return(body: open_test_file('created_secret.json'), status: 201) secret = Kubeclient::Resource.new secret.metadata = {} secret.metadata.name = 'test-secret' secret.metadata.namespace = 'dev' secret.data = {} secret.data['super-secret'] = 'Y2F0J3MgYXJlIGF3ZXNvbWUK' client = Kubeclient::Client.new('http://localhost:8080/api/') created_secret = client.create_secret(secret) assert_instance_of(Kubeclient::Resource, created_secret) assert_equal(secret.metadata.name, created_secret.metadata.name) assert_equal(secret.metadata.namespace, created_secret.metadata.namespace) assert_equal( secret.data['super-secret'], created_secret.data['super-secret'] ) end end kubeclient-4.11.0/test/test_security_context_constraint.rb000066400000000000000000000054211435070561000241470ustar00rootroot00000000000000require_relative 'test_helper' # kind: 'SecurityContextConstraints' entity tests. # This is one of the unusual `kind`s that are already plural (https://github.com/kubernetes/kubernetes/issues/8115). # We force singular in method names like 'create_endpoint', # but `kind` should remain plural as in kubernetes. class TestSecurityContextConstraints < MiniTest::Test def test_create_security_context_constraint stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( body: open_test_file('security.openshift.io_api_resource_list.json'), status: 200 ) testing_scc = Kubeclient::Resource.new( metadata: { name: 'teleportation' }, runAsUser: { type: 'MustRunAs' }, seLinuxContext: { type: 'MustRunAs' } ) req_body = '{"metadata":{"name":"teleportation"},"runAsUser":{"type":"MustRunAs"},' \ '"seLinuxContext":{"type":"MustRunAs"},' \ '"kind":"SecurityContextConstraints","apiVersion":"security.openshift.io/v1"}' stub_request(:post, 'http://localhost:8080/apis/security.openshift.io/v1/securitycontextconstraints') .with(body: req_body) .to_return(body: open_test_file('created_security_context_constraint.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') created_scc = client.create_security_context_constraint(testing_scc) assert_equal('SecurityContextConstraints', created_scc.kind) assert_equal('security.openshift.io/v1', created_scc.apiVersion) client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1', as: :parsed_symbolized) created_scc = client.create_security_context_constraint(testing_scc) assert_equal('SecurityContextConstraints', created_scc[:kind]) assert_equal('security.openshift.io/v1', created_scc[:apiVersion]) end def test_get_security_context_constraints stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( body: open_test_file('security.openshift.io_api_resource_list.json'), status: 200 ) stub_request(:get, %r{/securitycontextconstraints}) .to_return(body: open_test_file('security_context_constraint_list.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') collection = client.get_security_context_constraints(as: :parsed_symbolized) assert_equal('SecurityContextConstraintsList', collection[:kind]) assert_equal('security.openshift.io/v1', collection[:apiVersion]) # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic. collection = client.get_security_context_constraints assert_equal('SecurityContextConstraints', collection.kind) end end kubeclient-4.11.0/test/test_service.rb000066400000000000000000000274111435070561000177330ustar00rootroot00000000000000require_relative 'test_helper' # Service entity tests class TestService < MiniTest::Test def test_construct_our_own_service our_service = Kubeclient::Resource.new our_service.metadata = {} our_service.metadata.name = 'guestbook' our_service.metadata.namespace = 'staging' our_service.metadata.labels = {} our_service.metadata.labels.name = 'guestbook' our_service.spec = {} our_service.spec.ports = [{ 'port' => 3000, 'targetPort' => 'http-server', 'protocol' => 'TCP' }] assert_equal('guestbook', our_service.metadata.labels.name) hash = our_service.to_h assert_equal(our_service.metadata.labels.name, hash[:metadata][:labels][:name]) expected_url = 'http://localhost:8080/api/v1/namespaces/staging/services' stub_core_api_list stub_request(:post, expected_url) .to_return(body: open_test_file('created_service.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/') created = client.create_service(our_service) assert_instance_of(Kubeclient::Resource, created) assert_equal(created.metadata.name, our_service.metadata.name) assert_equal(created.spec.ports.size, our_service.spec.ports.size) # Check that original entity_config is not modified by kind/apiVersion patches: assert_nil(our_service.kind) assert_requested(:post, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['kind'] == 'Service' && data['apiVersion'] == 'v1' && data['metadata']['name'] == 'guestbook' && data['metadata']['namespace'] == 'staging' end end def test_construct_service_from_symbol_keys service = Kubeclient::Resource.new service.metadata = { labels: { tier: 'frontend' }, name: 'test-service', namespace: 'staging' } service.spec = { ports: [{ port: 3000, targetPort: 'http-server', protocol: 'TCP' }] } expected_url = 'http://localhost:8080/api/v1/namespaces/staging/services' stub_core_api_list stub_request(:post, expected_url) .to_return(body: open_test_file('created_service.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/') client.create_service(service) assert_requested(:post, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['kind'] == 'Service' && data['apiVersion'] == 'v1' && data['metadata']['name'] == 'test-service' && data['metadata']['labels']['tier'] == 'frontend' && data['metadata']['namespace'] == 'staging' end end def test_construct_service_from_string_keys service = Kubeclient::Resource.new service.metadata = { 'labels' => { 'tier' => 'frontend' }, 'name' => 'test-service', 'namespace' => 'staging' } service.spec = { 'ports' => [{ 'port' => 3000, 'targetPort' => 'http-server', 'protocol' => 'TCP' }] } stub_core_api_list expected_url = 'http://localhost:8080/api/v1/namespaces/staging/services' stub_request(:post, %r{namespaces/staging/services}) .to_return(body: open_test_file('created_service.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/') client.create_service(service) assert_requested(:post, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['kind'] == 'Service' && data['apiVersion'] == 'v1' && data['metadata']['name'] == 'test-service' && data['metadata']['labels']['tier'] == 'frontend' && data['metadata']['namespace'] == 'staging' end end def test_conversion_from_json_v1 stub_core_api_list stub_request(:get, %r{/services}) .to_return(body: open_test_file('service.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/') service = client.get_service('redis-slave', 'development') assert_instance_of(Kubeclient::Resource, service) assert_equal('2015-04-05T13:00:31Z', service.metadata.creationTimestamp) assert_equal('bdb80a8f-db93-11e4-b293-f8b156af4ae1', service.metadata.uid) assert_equal('redis-slave', service.metadata.name) assert_equal('2815', service.metadata.resourceVersion) assert_equal('v1', service.apiVersion) assert_equal('10.0.0.140', service.spec.clusterIP) assert_equal('development', service.metadata.namespace) assert_equal('TCP', service.spec.ports[0].protocol) assert_equal(6379, service.spec.ports[0].port) assert_equal('', service.spec.ports[0].name) assert_equal('redis-server', service.spec.ports[0].targetPort) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/development/services/redis-slave', times: 1) end def test_delete_service our_service = Kubeclient::Resource.new our_service.name = 'redis-service' # TODO, new ports assignment to be added our_service.labels = {} our_service.labels.component = 'apiserver' our_service.labels.provider = 'kubernetes' stub_core_api_list stub_request(:delete, %r{/namespaces/default/services}) .to_return(body: open_test_file('service.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') our_service = client.delete_service(our_service.name, 'default') assert_kind_of(RecursiveOpenStruct, our_service) assert_requested(:delete, 'http://localhost:8080/api/v1/namespaces/default/services/redis-service', times: 1) end def test_get_service_no_ns stub_core_api_list # when not specifying namespace for entities which # are not node or namespace, the request will fail stub_request(:get, %r{/services/redis-slave}) .to_return(status: 404) client = Kubeclient::Client.new('http://localhost:8080/api/') exception = assert_raises(Kubeclient::HttpError) do client.get_service('redis-slave') end assert_equal(404, exception.error_code) end def test_get_service stub_core_api_list stub_request(:get, %r{/namespaces/development/services/redis-slave}) .to_return(body: open_test_file('service.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/') service = client.get_service('redis-slave', 'development') assert_equal('redis-slave', service.metadata.name) assert_requested(:get, 'http://localhost:8080/api/v1/namespaces/development/services/redis-slave', times: 1) end def test_update_service service = Kubeclient::Resource.new name = 'my_service' service.metadata = {} service.metadata.name = name service.metadata.namespace = 'development' stub_core_api_list expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}" stub_request(:put, expected_url) .to_return(body: open_test_file('service_update.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.update_service(service) assert_kind_of(RecursiveOpenStruct, service) assert_requested(:put, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['metadata']['name'] == name && data['metadata']['namespace'] == 'development' end end def test_update_service_with_string_keys service = Kubeclient::Resource.new name = 'my_service' service.metadata = { 'name' => name, 'namespace' => 'development' } stub_core_api_list expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}" stub_request(:put, expected_url) .to_return(body: open_test_file('service_update.json'), status: 201) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.update_service(service) assert_kind_of(RecursiveOpenStruct, service) assert_requested(:put, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['metadata']['name'] == name && data['metadata']['namespace'] == 'development' end end def test_patch_service service = Kubeclient::Resource.new name = 'my_service' service.metadata = {} service.metadata.name = name service.metadata.namespace = 'development' stub_core_api_list expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}" stub_request(:patch, expected_url) .to_return(body: open_test_file('service_patch.json'), status: 200) patch = { metadata: { annotations: { key: 'value' } } } client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.patch_service(name, patch, 'development') assert_kind_of(RecursiveOpenStruct, service) assert_requested(:patch, expected_url, times: 1) do |req| data = JSON.parse(req.body) data['metadata']['annotations']['key'] == 'value' end end def test_apply_service service = Kubeclient::Resource.new name = 'my_service' service.metadata = {} service.metadata.name = name service.metadata.namespace = 'development' service.metadata.annotations = {} service.metadata.annotations['key'] = 'value' stub_core_api_list resource_name = "#{name}?fieldManager=myapp&force=true" expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{resource_name}" stub_request(:patch, expected_url) .to_return(body: open_test_file('service_patch.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.apply_service(service, field_manager: 'myapp') assert_kind_of(RecursiveOpenStruct, service) assert_requested(:patch, expected_url, times: 1) do |req| data = JSON.parse(req.body) req.headers['Content-Type'] == 'application/apply-patch+yaml' && data['metadata']['annotations']['key'] == 'value' end end def test_json_patch_service service = Kubeclient::Resource.new name = 'my-service' service.metadata = {} service.metadata.name = name service.metadata.namespace = 'development' stub_core_api_list expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}" stub_request(:patch, expected_url) .to_return(body: open_test_file('service_json_patch.json'), status: 200) patch = [ { 'op' => 'add', 'path' => '/spec/type', 'value' => 'LoadBalancer' } ] client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.json_patch_service(name, patch, 'development') assert_kind_of(RecursiveOpenStruct, service) assert_requested(:patch, expected_url, times: 1) do |req| data = JSON.parse(req.body) req.headers['Content-Type'] == 'application/json-patch+json' && data == patch end end def test_merge_patch_service service = Kubeclient::Resource.new name = 'my-service' service.metadata = {} service.metadata.name = name service.metadata.namespace = 'development' stub_core_api_list expected_url = "http://localhost:8080/api/v1/namespaces/development/services/#{name}" stub_request(:patch, expected_url) .to_return(body: open_test_file('service_merge_patch.json'), status: 200) patch = { spec: { type: 'NodePort' } } client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') service = client.merge_patch_service(name, patch, 'development') assert_kind_of(RecursiveOpenStruct, service) assert_requested(:patch, expected_url, times: 1) do |req| data = JSON.parse(req.body) req.headers['Content-Type'] == 'application/merge-patch+json' && data['spec']['type'] == 'NodePort' end end end kubeclient-4.11.0/test/test_service_account.rb000066400000000000000000000016141435070561000214440ustar00rootroot00000000000000require_relative 'test_helper' # ServiceAccount tests class TestServiceAccount < MiniTest::Test def test_get_from_json_v1 stub_core_api_list stub_request(:get, %r{/serviceaccounts}) .to_return(body: open_test_file('service_account.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') account = client.get_service_account('default') assert_instance_of(Kubeclient::Resource, account) assert_equal('default', account.metadata.name) assert_equal('default-token-6s23q', account.secrets[0].name) assert_equal('default-dockercfg-62tf3', account.secrets[1].name) assert_requested(:get, 'http://localhost:8080/api/v1/serviceaccounts/default', times: 1) assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) end end kubeclient-4.11.0/test/test_watch.rb000066400000000000000000000165341435070561000174050ustar00rootroot00000000000000require_relative 'test_helper' # Watch entity tests class TestWatch < MiniTest::Test def test_watch_pod_success stub_core_api_list expected = [ { 'type' => 'ADDED', 'resourceVersion' => '1389' }, { 'type' => 'MODIFIED', 'resourceVersion' => '1390' }, { 'type' => 'DELETED', 'resourceVersion' => '1398' } ] stub_request(:get, %r{/watch/pods}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') client.watch_pods.to_enum.with_index do |notice, index| assert_instance_of(Kubeclient::Resource, notice) assert_equal(expected[index]['type'], notice.type) assert_equal('Pod', notice.object.kind) assert_equal('php', notice.object.metadata.name) assert_equal(expected[index]['resourceVersion'], notice.object.metadata.resourceVersion) end end def test_watch_pod_block stub_core_api_list stub_request(:get, %r{/watch/pods}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') yielded = [] client.watch_pods { |notice| yielded << notice.type } assert_equal %w[ADDED MODIFIED DELETED], yielded end def test_watch_pod_raw stub_core_api_list stub_request(:get, %r{/watch/pods}).to_return( body: open_test_file('watch_stream.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') got = nil client.watch_pods(as: :raw).each { |notice| got = notice } assert_match(/\A{"type":"DELETED"/, got) end def test_watch_pod_failure stub_core_api_list stub_request(:get, %r{/watch/pods}).to_return(status: 404) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') assert_raises(Kubeclient::HttpError) do client.watch_pods.each do end end end def test_watch_pod_follow_redirect stub_core_api_list redirect = 'http://localhost:1234/api/v1/watch/pods' stub_request(:get, %r{/watch/pods}) .to_return(status: 302, headers: { location: redirect }) stub_request(:get, redirect).to_return( body: open_test_file('watch_stream.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') got = nil client.watch_pods.each { |notice| got = notice } assert_equal('DELETED', got.type) end def test_watch_pod_max_redirect stub_core_api_list redirect = 'http://localhost:1234/api/v1/watcher/pods' stub_request(:get, %r{/watch/pods}) .to_return(status: 302, headers: { location: redirect }) stub_request(:get, redirect).to_return( body: open_test_file('watch_stream.json'), status: 200 ) client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1', http_max_redirects: 0) assert_raises(Kubeclient::HttpError) do client.watch_pods.each do end end end def test_watch_pod_api_bearer_token_file_success stub_core_api_list file = Tempfile.new('token') client = Kubeclient::Client.new( 'http://localhost:8080/api/', 'v1', auth_options: { bearer_token_file: file.path } ) watcher = client.watch_pods(as: :raw) begin file.write("valid_token") file.rewind stub_token = stub_request(:get, %r{/watch/pods}) .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('watch_stream.json'), status: 200) got = nil watcher.each { |notice| got = notice } assert_match(/\A{"type":"DELETED"/, got) remove_request_stub(stub_token) file.write("rotated_token") file.close stub_request(:get, %r{/watch/pods}) .with(headers: { Authorization: 'Bearer rotated_token' }) .to_return(body: open_test_file('watch_stream.json'), status: 200) got = nil watcher.each { |notice| got = notice } assert_match(/\A{"type":"DELETED"/, got) ensure file.close file.unlink # deletes the temp file end end def test_watch_pod_api_bearer_token_success stub_core_api_list file = Tempfile.new('token') client = Kubeclient::Client.new( 'http://localhost:8080/api/', 'v1', auth_options: { bearer_token: "valid_token" } ) stub_token = stub_request(:get, %r{/watch/pods}) .with(headers: { Authorization: 'Bearer valid_token' }) .to_return(body: open_test_file('watch_stream.json'), status: 200) got = nil client.watch_pods(as: :raw).each { |notice| got = notice } assert_match(/\A{"type":"DELETED"/, got) end # Ensure that WatchStream respects a format that's not JSON def test_watch_stream_text url = 'http://www.example.com/foobar' expected_lines = open_test_file('pod_log.txt').read.split("\n") stub_request(:get, url) .to_return(body: open_test_file('pod_log.txt'), status: 200) stream = Kubeclient::Common::WatchStream.new(URI.parse(url), {}, formatter: ->(v) { v }) stream.to_enum.with_index do |line, index| assert_instance_of(String, line) assert_equal(expected_lines[index], line) end end def test_watch_with_resource_version api_host = 'http://localhost:8080/api' version = '1995' stub_core_api_list stub_request(:get, %r{.*\/watch/events}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new(api_host, 'v1') results = client.watch_events(version).to_enum assert_equal(3, results.count) assert_requested(:get, "#{api_host}/v1/watch/events?resourceVersion=#{version}", times: 1) end def test_watch_with_label_selector api_host = 'http://localhost:8080/api' selector = 'name=redis-master' stub_core_api_list stub_request(:get, %r{.*\/watch/events}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new(api_host, 'v1') results = client.watch_events(label_selector: selector).to_enum assert_equal(3, results.count) assert_requested(:get, "#{api_host}/v1/watch/events?labelSelector=#{selector}", times: 1) end def test_watch_with_field_selector api_host = 'http://localhost:8080/api' selector = 'involvedObject.kind=Pod' stub_core_api_list stub_request(:get, %r{.*\/watch/events}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new(api_host, 'v1') results = client.watch_events(field_selector: selector).to_enum assert_equal(3, results.count) assert_requested(:get, "#{api_host}/v1/watch/events?fieldSelector=#{selector}", times: 1) end def test_watch_with_finish_and_ebadf api_host = 'http://localhost:8080/api' stub_core_api_list stub_request(:get, %r{.*\/watch/events}) .to_return(body: open_test_file('watch_stream.json'), status: 200) client = Kubeclient::Client.new(api_host, 'v1') watcher = client.watch_events # explodes when StandardError is not caught watcher.each do watcher.finish raise StandardError end assert_requested(:get, "#{api_host}/v1/watch/events", times: 1) end end kubeclient-4.11.0/test/txt/000077500000000000000000000000001435070561000155215ustar00rootroot00000000000000kubeclient-4.11.0/test/txt/pod_log.txt000066400000000000000000000001751435070561000177100ustar00rootroot00000000000000Initializing server... ...loaded configuration ...updated settings ...discovered local servers ...frobinated disks Complete! kubeclient-4.11.0/test/valid_token_file000066400000000000000000000000141435070561000201160ustar00rootroot00000000000000valid_token