├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .whitesource ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── Rakefile ├── bin └── ci ├── kubeclient.gemspec ├── lib ├── kubeclient.rb └── kubeclient │ ├── aws_eks_credentials.rb │ ├── config.rb │ ├── entity_list.rb │ ├── exec_credentials.rb │ ├── gcp_auth_provider.rb │ ├── gcp_command_credentials.rb │ ├── google_application_default_credentials.rb │ ├── http_error.rb │ ├── informer.rb │ ├── missing_kind_compatibility.rb │ ├── oidc_auth_provider.rb │ ├── resource.rb │ ├── resource_already_exists_error.rb │ ├── resource_not_found_error.rb │ ├── version.rb │ └── watch_stream.rb ├── renovate.json └── test ├── config ├── allinone.kubeconfig ├── another-ca1.pem ├── another-ca2.pem ├── concatenated-ca.kubeconfig ├── concatenated-ca.pem ├── execauth.kubeconfig ├── external-ca.pem ├── external-cert.pem ├── external-key.rsa ├── external-without-ca.kubeconfig ├── external.kubeconfig ├── gcpauth.kubeconfig ├── gcpcmdauth.kubeconfig ├── impersonate-empty-groups.kubeconfig ├── impersonate.kubeconfig ├── insecure-custom-ca.kubeconfig ├── insecure.kubeconfig ├── nouser.kubeconfig ├── oidcauth.kubeconfig ├── secure-without-ca.kubeconfig ├── secure.kubeconfig ├── timestamps.kubeconfig ├── update_certs_k0s.rb └── userauth.kubeconfig ├── gemfiles ├── Gemfile.faraday-1 └── Gemfile.faraday-2 ├── helper.rb ├── json ├── bindings_list.json ├── component_status.json ├── component_status_list.json ├── config.istio.io_api_resource_list.json ├── config_map_list.json ├── core_api_resource_list.json ├── core_api_resource_list_without_kind.json ├── core_oapi_resource_list_without_kind.json ├── created_endpoint.json ├── created_namespace.json ├── created_secret.json ├── created_security_context_constraint.json ├── created_service.json ├── empty_pod_list.json ├── endpoint_list.json ├── entity_list.json ├── event_list.json ├── extensions_v1beta1_api_resource_list.json ├── limit_range.json ├── limit_range_list.json ├── namespace.json ├── namespace_already_exists.json ├── namespace_exception.json ├── namespace_list.json ├── node.json ├── node_list.json ├── node_notice.json ├── persistent_volume.json ├── persistent_volume_claim.json ├── persistent_volume_claim_list.json ├── persistent_volume_claims_nil_items.json ├── persistent_volume_list.json ├── pod.json ├── pod_list.json ├── pod_template_list.json ├── pods_1.json ├── pods_2.json ├── pods_410.json ├── processed_template.json ├── replication_controller.json ├── replication_controller_list.json ├── resource_quota.json ├── resource_quota_list.json ├── secret_list.json ├── security.openshift.io_api_resource_list.json ├── security_context_constraint_list.json ├── service.json ├── service_account.json ├── service_account_list.json ├── service_illegal_json_404.json ├── service_json_patch.json ├── service_list.json ├── service_merge_patch.json ├── service_patch.json ├── service_update.json ├── template.json ├── template.openshift.io_api_resource_list.json ├── template_list.json ├── versions_list.json └── watch_stream.json ├── test_common.rb ├── test_common_url_handling.rb ├── test_component_status.rb ├── test_config.rb ├── test_endpoint.rb ├── test_exec_credentials.rb ├── test_gcp_command_credentials.rb ├── test_google_application_default_credentials.rb ├── test_informer.rb ├── test_kubeclient.rb ├── test_limit_range.rb ├── test_missing_methods.rb ├── test_namespace.rb ├── test_node.rb ├── test_oidc_auth_provider.rb ├── test_persistent_volume.rb ├── test_persistent_volume_claim.rb ├── test_pod.rb ├── test_pod_log.rb ├── test_process_template.rb ├── test_real_cluster.rb ├── test_replication_controller.rb ├── test_resource_list_without_kind.rb ├── test_resource_quota.rb ├── test_secret.rb ├── test_security_context_constraint.rb ├── test_service.rb ├── test_service_account.rb ├── test_watch.rb ├── txt └── pod_log.txt └── valid_token_file /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_size = 2 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: '0 0 * * 0' 8 | jobs: 9 | ci: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | ruby: 14 | - '3.1' 15 | - '3.2' 16 | - '3.3' 17 | - '3.4' 18 | - ruby-head 19 | - truffleruby-head 20 | gemfile: 21 | - faraday-1 22 | - faraday-2 23 | os: 24 | - macos-latest 25 | - ubuntu-latest 26 | runs-on: ${{ matrix.os }} 27 | timeout-minutes: 10 28 | env: 29 | BUNDLE_GEMFILE: test/gemfiles/Gemfile.${{ matrix.gemfile }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: ${{ matrix.ruby }} 36 | bundler-cache: true 37 | - name: Run tests 38 | run: bin/ci 39 | lint: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Set up Ruby 44 | uses: ruby/setup-ruby@v1 45 | with: 46 | ruby-version: '3.1' # Run rubocop against lowest supported ruby 47 | bundler-cache: true 48 | - name: Run rubocop 49 | run: bundle exec rake rubocop 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | *.idea* 16 | /Gemfile.dev.rb 17 | /test/gemfiles/*.lock -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.7 # Oldest version kubeclient supports 3 | NewCops: enable 4 | Metrics/MethodLength: 5 | Enabled: false 6 | Metrics/ClassLength: 7 | Enabled: false 8 | Metrics/AbcSize: 9 | Enabled: false 10 | Layout/LineLength: 11 | Max: 100 12 | Metrics/ParameterLists: 13 | Max: 5 14 | CountKeywordArgs: false 15 | Metrics/CyclomaticComplexity: 16 | Max: 10 17 | Metrics/PerceivedComplexity: 18 | Max: 10 19 | Metrics/ModuleLength: 20 | Enabled: false 21 | Style/MethodCallWithArgsParentheses: 22 | Enabled: true 23 | IgnoredMethods: [require, raise, include, attr_reader, refute, assert, require_relative] 24 | Exclude: [Gemfile, Rakefile, kubeclient.gemspec, Gemfile.dev.rb] 25 | Metrics/BlockLength: 26 | Exclude: [kubeclient.gemspec] 27 | Security/MarshalLoad: 28 | Exclude: [test/**/*] 29 | Naming/FileName: 30 | Exclude: [Gemfile, Rakefile, Gemfile.dev.rb] 31 | Style/RegexpLiteral: 32 | Enabled: false 33 | Layout/EmptyLineAfterGuardClause: 34 | Enabled: false 35 | Style/IfUnlessModifier: 36 | Enabled: false 37 | Layout/HashAlignment: 38 | Enabled: false 39 | Naming/MethodParameterName: 40 | Enabled: false 41 | Gemspec/OrderedDependencies: 42 | Enabled: false 43 | Style/FrozenStringLiteralComment: 44 | Enabled: false 45 | Style/WordArray: 46 | Enabled: false 47 | Style/AccessorGrouping: 48 | EnforcedStyle: separated 49 | Style/NegatedIfElseCondition: 50 | Enabled: false 51 | Style/Semicolon: 52 | Exclude: ["test/**/*.rb"] 53 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "ManageIQ/whitesource-config@master" 3 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | dev_gemfile = File.expand_path('Gemfile.dev.rb', __dir__) 6 | eval_gemfile(dev_gemfile) if File.exist?(dev_gemfile) 7 | 8 | # Specify your gem's dependencies in kubeclient.gemspec 9 | gemspec 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Alissa Bonas 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing Kubeclient 2 | 3 | ## Versioning 4 | Kubeclient release versioning follows [SemVer](https://semver.org/). 5 | At some point in time it is decided to release version x.y.z. 6 | 7 | ## 0. (once) Install gem-release, needed for several commands here: 8 | 9 | ```bash 10 | gem install gem-release 11 | ``` 12 | 13 | ## 1. PR(s) for changelog & bump 14 | 15 | ```bash 16 | RELEASE_BRANCH="master" 17 | RELEASE_VERSION=x.y.z 18 | 19 | git checkout -b "release-$RELEASE_VERSION" $RELEASE_BRANCH 20 | ``` 21 | 22 | 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. 23 | 24 | Bump `lib/kubeclient/version.rb` manually, or by using: 25 | ```bash 26 | # Won't work with uncommitted changes, you have to commit the changelog first. 27 | gem bump --version $RELEASE_VERSION 28 | git show # View version bump change. 29 | ``` 30 | 31 | 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). 32 | 33 | ## 2. (once) Grabbing an authentication token for rubygems.org api 34 | ```bash 35 | RUBYGEMS_USERNAME=bob 36 | curl -u $RUBYGEMS_USERNAME https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials 37 | 38 | cat ~/.gem/credentials 39 | # Should look like this: 40 | :rubygems_api_key: **** 41 | ``` 42 | 43 | ## 3. Actual release 44 | 45 | Make sure we're locally after the bump PR *merge commit*: 46 | ```bash 47 | git checkout $RELEASE_BRANCH 48 | git status # Make sure there are no local changes 49 | git pull --ff-only https://github.com/ManageIQ/kubeclient $RELEASE_BRANCH 50 | git log -n1 51 | ``` 52 | 53 | Last sanity check: 54 | ```bash 55 | bundle install 56 | bundle exec rake test rubocop 57 | ``` 58 | 59 | Create and push the tag: 60 | ```bash 61 | gem tag --no-push 62 | git push --tags --dry-run https://github.com/ManageIQ/kubeclient # Check for unexpected tags 63 | git push --tags https://github.com/ManageIQ/kubeclient 64 | ``` 65 | 66 | Release onto rubygems.org: 67 | ```bash 68 | gem release 69 | ``` 70 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rubocop/rake_task' 5 | require 'yaml' 6 | 7 | task default: %i[test rubocop] 8 | 9 | begin 10 | fork { nil } 11 | rescue NotImplementedError 12 | # jruby and windows can't fork so use vanilla rake instead 13 | require 'rake/testtask' 14 | else 15 | desc 'Run each test in isolation' 16 | task :test do 17 | sh 'forking-test-runner test/test_* --helper test/helper.rb --verbose' 18 | end 19 | end 20 | RuboCop::RakeTask.new 21 | -------------------------------------------------------------------------------- /bin/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ "$OSTYPE" == "darwin"* ]]; then 6 | bundle exec rake test 7 | else 8 | # Sometimes minitest starts and then just hangs printing nothing. 9 | # GitHub by default kills after 6hours(!). Hopefully SIGTERM may let it print some details? 10 | timeout --signal=TERM 3m env test/config/update_certs_k0s.rb 11 | fi 12 | -------------------------------------------------------------------------------- /kubeclient.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require_relative 'lib/kubeclient/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'kubeclient' 9 | spec.version = Kubeclient::VERSION 10 | spec.authors = ['Alissa Bonas'] 11 | spec.email = ['abonas@redhat.com'] 12 | spec.summary = 'A client for Kubernetes REST api' 13 | spec.description = 'A client for Kubernetes REST api' 14 | spec.homepage = 'https://github.com/ManageIQ/kubeclient' 15 | spec.license = 'MIT' 16 | 17 | git_files = `git ls-files -z`.split("\x0") 18 | spec.files = git_files.grep_v(%r{^(test|spec|features)/}) 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.test_files = [] 21 | spec.required_ruby_version = '>= 2.7.0' 22 | 23 | spec.add_development_dependency 'bundler', '>= 1.6' 24 | spec.add_development_dependency 'rake', '~> 13.0' 25 | spec.add_development_dependency 'minitest', '~> 5.15.0' 26 | spec.add_development_dependency 'minitest-rg' 27 | spec.add_development_dependency 'mutex_m', '~> 0.3' 28 | spec.add_development_dependency 'webmock', '~> 3.0' 29 | spec.add_development_dependency 'rubocop', '~> 1.3.0' # locked to minor so new cops don't slip in 30 | spec.add_development_dependency 'googleauth', '~> 1.3' 31 | spec.add_development_dependency('mocha', '~> 1.5') 32 | spec.add_development_dependency 'openid_connect', '~> 1.1' 33 | spec.add_development_dependency 'net-smtp' 34 | spec.add_development_dependency 'forking_test_runner' 35 | 36 | spec.add_dependency 'faraday', '>= 1.1', '< 3.0' 37 | spec.add_dependency 'faraday-follow_redirects', '>= 0.3.0' 38 | spec.add_dependency 'recursive-open-struct', '>= 1.1.1', '< 3.0' 39 | spec.add_dependency 'http', '>= 3.0', '< 6.0' 40 | end 41 | -------------------------------------------------------------------------------- /lib/kubeclient/aws_eks_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | # Get a bearer token to authenticate against aws eks. 5 | class AmazonEksCredentials 6 | class AmazonEksDependencyError < LoadError # rubocop:disable Lint/InheritException 7 | end 8 | 9 | class << self 10 | def token(credentials, eks_cluster, region: 'us-east-1') 11 | begin 12 | require 'aws-sigv4' 13 | require 'base64' 14 | require 'cgi' 15 | rescue LoadError => e 16 | raise AmazonEksDependencyError, 17 | 'Error requiring aws gems. Kubeclient itself does not include the following ' \ 18 | 'gems: [aws-sigv4]. To support auth-provider eks, you must ' \ 19 | "include it in your calling application. Failed with: #{e.message}" 20 | end 21 | # https://github.com/aws/aws-sdk-ruby/pull/1848 22 | # Get a signer 23 | signer = if credentials.respond_to?(:credentials) 24 | Aws::Sigv4::Signer.new( 25 | service: 'sts', 26 | region: region, 27 | credentials_provider: credentials 28 | ) 29 | else 30 | Aws::Sigv4::Signer.new( 31 | service: 'sts', 32 | region: region, 33 | credentials: credentials 34 | ) 35 | end 36 | 37 | credentials = credentials.credentials if credentials.respond_to?(:credentials) 38 | 39 | # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html#presign_url-instance_method 40 | presigned_url_string = signer.presign_url( 41 | http_method: 'GET', 42 | url: "https://sts.#{region}.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15", 43 | body: '', 44 | credentials: credentials, 45 | expires_in: 60, 46 | headers: { 47 | 'X-K8s-Aws-Id' => eks_cluster 48 | } 49 | ) 50 | "k8s-aws-v1.#{Base64.urlsafe_encode64(presigned_url_string.to_s).sub(/=*$/, '')}" 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/kubeclient/entity_list.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'delegate' 4 | module Kubeclient 5 | module Common 6 | # Kubernetes Entity List 7 | class EntityList < DelegateClass(Array) 8 | attr_reader :continue 9 | attr_reader :kind 10 | attr_reader :resourceVersion # rubocop:disable Naming/MethodName 11 | 12 | def initialize(kind, resource_version, list, continue = nil) 13 | @kind = kind 14 | @resourceVersion = resource_version # rubocop:disable Naming/VariableName 15 | @continue = continue 16 | super(list) 17 | end 18 | 19 | def last? 20 | continue.nil? 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/kubeclient/exec_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | # An exec-based client auth provide 5 | # https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration 6 | # Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go 7 | class ExecCredentials 8 | class << self 9 | def run(opts) 10 | require 'open3' 11 | require 'json' 12 | 13 | raise ArgumentError, 'exec options are required' if opts.nil? 14 | 15 | cmd = opts['command'] 16 | args = opts['args'] 17 | env = map_env(opts['env']) 18 | 19 | # Validate exec options 20 | validate_opts(opts) 21 | 22 | out, err, st = Open3.capture3(env, cmd, *args) 23 | 24 | raise "exec command failed: #{err}" unless st.success? 25 | 26 | creds = JSON.parse(out) 27 | validate_credentials(opts, creds) 28 | creds['status'] 29 | end 30 | 31 | private 32 | 33 | def validate_opts(opts) 34 | raise KeyError, 'exec command is required' unless opts['command'] 35 | end 36 | 37 | def validate_client_credentials_status(status) 38 | has_client_cert_data = status.key?('clientCertificateData') 39 | has_client_key_data = status.key?('clientKeyData') 40 | 41 | if has_client_cert_data && !has_client_key_data 42 | raise 'exec plugin didn\'t return client key data' 43 | end 44 | 45 | if !has_client_cert_data && has_client_key_data 46 | raise 'exec plugin didn\'t return client certificate data' 47 | end 48 | 49 | has_client_cert_data && has_client_key_data 50 | end 51 | 52 | def validate_credentials_status(status) 53 | raise 'exec plugin didn\'t return a status field' if status.nil? 54 | 55 | has_client_credentials = validate_client_credentials_status(status) 56 | has_token = status.key?('token') 57 | 58 | if has_client_credentials && has_token 59 | raise 'exec plugin returned both token and client data' 60 | end 61 | 62 | return if has_client_credentials || has_token 63 | 64 | raise 'exec plugin didn\'t return a token or client data' unless has_token 65 | end 66 | 67 | def validate_credentials(opts, creds) 68 | # out should have ExecCredential structure 69 | raise 'invalid credentials' if creds.nil? 70 | 71 | # Verify apiVersion? 72 | api_version = opts['apiVersion'] 73 | if api_version && api_version != creds['apiVersion'] 74 | raise "exec plugin is configured to use API version #{api_version}, " \ 75 | "plugin returned version #{creds['apiVersion']}" 76 | end 77 | 78 | validate_credentials_status(creds['status']) 79 | end 80 | 81 | # Transform name/value pairs to hash 82 | def map_env(env) 83 | return {} unless env 84 | 85 | Hash[env.map { |e| [e['name'], e['value']] }] 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/kubeclient/gcp_auth_provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'google_application_default_credentials' 4 | require_relative 'gcp_command_credentials' 5 | 6 | module Kubeclient 7 | # Handle different ways to get a bearer token for Google Cloud Platform. 8 | class GCPAuthProvider 9 | class << self 10 | def token(config) 11 | if config.key?('cmd-path') 12 | Kubeclient::GCPCommandCredentials.token(config) 13 | else 14 | Kubeclient::GoogleApplicationDefaultCredentials.token 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/kubeclient/gcp_command_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | # Generates a bearer token for Google Cloud Platform. 5 | class GCPCommandCredentials 6 | class << self 7 | def token(config) 8 | require 'open3' 9 | require 'shellwords' 10 | require 'json' 11 | 12 | cmd = config['cmd-path'] 13 | args = config['cmd-args'] 14 | token_key = config['token-key'] 15 | 16 | out, err, st = Open3.capture3(cmd, *args.split) 17 | 18 | raise "exec command failed: #{err}" unless st.success? 19 | 20 | extract_token(out, token_key) 21 | end 22 | 23 | private 24 | 25 | def extract_token(output, key) 26 | path = 27 | key 28 | .gsub(/\A{(.*)}\z/, '\\1') # {.foo.bar} -> .foo.bar 29 | .sub(/\A\./, '') # .foo.bar -> foo.bar 30 | .split('.') 31 | JSON.parse(output).dig(*path) 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/kubeclient/google_application_default_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | # Get a bearer token from the Google's application default credentials. 5 | class GoogleApplicationDefaultCredentials 6 | class GoogleDependencyError < LoadError # rubocop:disable Lint/InheritException 7 | end 8 | 9 | class << self 10 | def token 11 | begin 12 | require 'googleauth' 13 | rescue LoadError => e 14 | raise( 15 | GoogleDependencyError, 16 | 'Error requiring googleauth gem. Kubeclient itself does not include the ' \ 17 | 'googleauth gem. To support auth-provider gcp, you must include it in your ' \ 18 | "calling application. Failed with: #{e.message}" 19 | ) 20 | end 21 | 22 | scopes = [ 23 | 'https://www.googleapis.com/auth/cloud-platform', 24 | 'https://www.googleapis.com/auth/userinfo.email' 25 | ] 26 | 27 | authorization = Google::Auth.get_application_default(scopes) 28 | authorization.apply({}) 29 | authorization.access_token 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/kubeclient/http_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # TODO: remove this on next major version bump 4 | # Deprected http exception 5 | class KubeException < StandardError 6 | attr_reader :error_code 7 | attr_reader :message 8 | attr_reader :response 9 | 10 | def initialize(error_code, message, response) # rubocop:disable Lint/MissingSuper 11 | @error_code = error_code 12 | @message = message 13 | @response = response 14 | end 15 | 16 | def to_s 17 | string = "HTTP status code #{@error_code}, #{@message}" 18 | if @response && @response[:request] 19 | request_method = @response[:request][:method]&.to_s&.upcase 20 | request_path = @response[:request][:url_path] 21 | string += " for #{request_method} #{request_path}" 22 | end 23 | string 24 | end 25 | end 26 | 27 | module Kubeclient 28 | # Exception that is raised when a http request fails 29 | class HttpError < KubeException 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/kubeclient/informer.rb: -------------------------------------------------------------------------------- 1 | module Kubeclient 2 | # caches results for multiple consumers to share and keeps them updated with a watch 3 | class Informer 4 | def initialize(client, resource_name, options: {}, reconcile_timeout: 15 * 60, logger: nil) 5 | @client = client 6 | @resource_name = resource_name 7 | @reconcile_timeout = reconcile_timeout 8 | @logger = logger 9 | @cache = nil 10 | @started = nil 11 | @stopped = false 12 | @watching = [] 13 | @options = options 14 | end 15 | 16 | def list 17 | @cache.values 18 | end 19 | 20 | def watch(&block) 21 | with_watching(&block) 22 | end 23 | 24 | # not implicit so users know they have to `stop` 25 | def start_worker 26 | @stopped = false 27 | @worker = Thread.new do 28 | until @stopped 29 | begin 30 | fill_cache 31 | stop_reason = watch_to_update_cache 32 | @logger&.info("watch restarted: #{stop_reason}") 33 | rescue StandardError => e 34 | # need to keep retrying since we work in the background 35 | @logger&.error("ignoring error during background work #{e}") 36 | ensure 37 | sleep(1) # do not overwhelm the api-server if we are somehow broken 38 | end 39 | end 40 | end 41 | sleep(0.01) until @cache || @stopped 42 | end 43 | 44 | def stop_worker 45 | @stopped = true # mark that any threads should be stopped if it is running 46 | pass_and_run(@worker) # skip the end loop sleep 47 | @worker.join 48 | end 49 | 50 | private 51 | 52 | def with_watching 53 | queue = Queue.new 54 | @watching << queue 55 | loop do 56 | x = queue.pop 57 | yield(x) 58 | end 59 | ensure 60 | @watching.delete(queue) 61 | end 62 | 63 | def cache_key(resource) 64 | resource.dig(:metadata, :uid) 65 | end 66 | 67 | def fill_cache 68 | get_options = @options.merge(raw: true, resource_version: '0') 69 | reply = @client.get_entities(nil, @resource_name, get_options) 70 | @cache = reply[:items].each_with_object({}) do |item, h| 71 | h[cache_key(item)] = item 72 | end 73 | @started = reply.dig(:metadata, :resourceVersion) 74 | end 75 | 76 | def watch_to_update_cache 77 | watcher_with_timeout do |watcher| 78 | stop_reason = 'disconnect' 79 | 80 | watcher.each do |notice| 81 | case notice[:type] 82 | when 'ADDED', 'MODIFIED' then @cache[cache_key(notice[:object])] = notice[:object] 83 | when 'DELETED' then @cache.delete(cache_key(notice[:object])) 84 | when 'ERROR' 85 | stop_reason = 'error' 86 | break 87 | else 88 | @logger&.error("Unsupported event type #{notice[:type]}") 89 | end 90 | @watching.each { |q| q << notice } 91 | end 92 | 93 | stop_reason 94 | end 95 | end 96 | 97 | def watcher_with_timeout 98 | watch_options = @options.merge(watch: true, resource_version: @started) 99 | @watcher = @client.watch_entities(@resource_name, watch_options) 100 | timeout_deadline = Time.now + @reconcile_timeout 101 | watcher_finished = false 102 | 103 | finisher_thread = Thread.new do 104 | sleep(0.5) until @stopped || watcher_finished || Time.now > timeout_deadline 105 | # loop calling finish until the actual method has 106 | # exited, since watcher.each may be called after the 107 | # finish in this thread is called 108 | loop do 109 | @watcher.finish 110 | break if watcher_finished 111 | sleep(0.5) 112 | end 113 | end 114 | 115 | stop_reason = yield(@watcher) 116 | Time.now > timeout_deadline ? 'reconcile' : stop_reason # return the reason 117 | ensure 118 | watcher_finished = true 119 | pass_and_run(finisher_thread) # skip the sleep to evaluate exit condition 120 | finisher_thread.join 121 | end 122 | 123 | def pass_and_run(thread) 124 | Thread.pass 125 | thread.run 126 | rescue ThreadError 127 | # thread was already dead 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/kubeclient/missing_kind_compatibility.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | module Common 5 | # Backward compatibility for old versions where kind is missing (e.g. OpenShift Enterprise 3.1) 6 | class MissingKindCompatibility 7 | MAPPING = { 8 | 'bindings' => 'Binding', 9 | 'componentstatuses' => 'ComponentStatus', 10 | 'endpoints' => 'Endpoints', 11 | 'events' => 'Event', 12 | 'limitranges' => 'LimitRange', 13 | 'namespaces' => 'Namespace', 14 | 'nodes' => 'Node', 15 | 'persistentvolumeclaims' => 'PersistentVolumeClaim', 16 | 'persistentvolumes' => 'PersistentVolume', 17 | 'pods' => 'Pod', 18 | 'podtemplates' => 'PodTemplate', 19 | 'replicationcontrollers' => 'ReplicationController', 20 | 'resourcequotas' => 'ResourceQuota', 21 | 'secrets' => 'Secret', 22 | 'securitycontextconstraints' => 'SecurityContextConstraints', 23 | 'serviceaccounts' => 'ServiceAccount', 24 | 'services' => 'Service', 25 | 'buildconfigs' => 'BuildConfig', 26 | 'builds' => 'Build', 27 | 'clusternetworks' => 'ClusterNetwork', 28 | 'clusterpolicies' => 'ClusterPolicy', 29 | 'clusterpolicybindings' => 'ClusterPolicyBinding', 30 | 'clusterrolebindings' => 'ClusterRoleBinding', 31 | 'clusterroles' => 'ClusterRole', 32 | 'deploymentconfigrollbacks' => 'DeploymentConfigRollback', 33 | 'deploymentconfigs' => 'DeploymentConfig', 34 | 'generatedeploymentconfigs' => 'DeploymentConfig', 35 | 'groups' => 'Group', 36 | 'hostsubnets' => 'HostSubnet', 37 | 'identities' => 'Identity', 38 | 'images' => 'Image', 39 | 'imagestreamimages' => 'ImageStreamImage', 40 | 'imagestreammappings' => 'ImageStreamMapping', 41 | 'imagestreams' => 'ImageStream', 42 | 'imagestreamtags' => 'ImageStreamTag', 43 | 'localresourceaccessreviews' => 'LocalResourceAccessReview', 44 | 'localsubjectaccessreviews' => 'LocalSubjectAccessReview', 45 | 'netnamespaces' => 'NetNamespace', 46 | 'oauthaccesstokens' => 'OAuthAccessToken', 47 | 'oauthauthorizetokens' => 'OAuthAuthorizeToken', 48 | 'oauthclientauthorizations' => 'OAuthClientAuthorization', 49 | 'oauthclients' => 'OAuthClient', 50 | 'policies' => 'Policy', 51 | 'policybindings' => 'PolicyBinding', 52 | 'processedtemplates' => 'Template', 53 | 'projectrequests' => 'ProjectRequest', 54 | 'projects' => 'Project', 55 | 'resourceaccessreviews' => 'ResourceAccessReview', 56 | 'rolebindings' => 'RoleBinding', 57 | 'roles' => 'Role', 58 | 'routes' => 'Route', 59 | 'subjectaccessreviews' => 'SubjectAccessReview', 60 | 'templates' => 'Template', 61 | 'useridentitymappings' => 'UserIdentityMapping', 62 | 'users' => 'User' 63 | }.freeze 64 | 65 | def self.resource_kind(name) 66 | MAPPING[name] 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/kubeclient/oidc_auth_provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | # Uses OIDC id-tokens and refreshes them if they are stale. 5 | class OIDCAuthProvider 6 | class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException 7 | end 8 | 9 | class << self 10 | def token(provider_config) 11 | begin 12 | require 'openid_connect' 13 | rescue LoadError => e 14 | raise( 15 | OpenIDConnectDependencyError, 16 | 'Error requiring openid_connect gem. Kubeclient itself does not include the ' \ 17 | 'openid_connect gem. To support auth-provider oidc, you must include it in your ' \ 18 | "calling application. Failed with: #{e.message}" 19 | ) 20 | end 21 | 22 | issuer_url = provider_config['idp-issuer-url'] 23 | discovery = OpenIDConnect::Discovery::Provider::Config.discover!(issuer_url) 24 | 25 | if provider_config.key?('id-token') && !expired?(provider_config['id-token'], discovery) 26 | return provider_config['id-token'] 27 | end 28 | 29 | client = OpenIDConnect::Client.new( 30 | identifier: provider_config['client-id'], 31 | secret: provider_config['client-secret'], 32 | authorization_endpoint: discovery.authorization_endpoint, 33 | token_endpoint: discovery.token_endpoint, 34 | userinfo_endpoint: discovery.userinfo_endpoint 35 | ) 36 | client.refresh_token = provider_config['refresh-token'] 37 | client.access_token!.id_token 38 | end 39 | 40 | def expired?(id_token, discovery) 41 | decoded_token = OpenIDConnect::ResponseObject::IdToken.decode( 42 | id_token, 43 | discovery.jwks 44 | ) 45 | # If token expired or expiring within 60 seconds 46 | Time.now.to_i + 60 > decoded_token.exp.to_i 47 | rescue JSON::JWK::Set::KidNotFound 48 | # Token cannot be verified: the kid it was signed with is not available for discovery 49 | # Consider it expired and fetch a new one. 50 | true 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/kubeclient/resource.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'recursive_open_struct' 4 | 5 | module Kubeclient 6 | # Represents all the objects returned by Kubeclient 7 | class Resource < RecursiveOpenStruct 8 | def initialize(hash = nil, args = {}) 9 | args[:recurse_over_arrays] = true 10 | super(hash, args) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/kubeclient/resource_already_exists_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | class ResourceAlreadyExistsError < HttpError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/kubeclient/resource_not_found_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Kubeclient 4 | class ResourceNotFoundError < HttpError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/kubeclient/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Kubernetes REST-API Client 4 | module Kubeclient 5 | VERSION = '4.10.1' 6 | end 7 | -------------------------------------------------------------------------------- /lib/kubeclient/watch_stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'http' 5 | module Kubeclient 6 | module Common 7 | # HTTP Stream used to watch changes on entities 8 | class WatchStream 9 | def initialize(uri, http_options, formatter:) 10 | @uri = uri 11 | @http_client = nil 12 | @http_options = http_options 13 | @http_options[:http_max_redirects] ||= Kubeclient::Client::DEFAULT_HTTP_MAX_REDIRECTS 14 | @formatter = formatter 15 | end 16 | 17 | def each 18 | @finished = false 19 | 20 | @http_client = build_client 21 | response = @http_client.request(:get, @uri, build_client_options) 22 | unless response.code < 300 23 | raise Kubeclient::HttpError.new(response.code, response.reason, response) 24 | end 25 | 26 | buffer = +'' 27 | response.body.each do |chunk| 28 | buffer << chunk 29 | while (line = buffer.slice!(/.+\n/)) 30 | yield(@formatter.call(line.chomp)) 31 | end 32 | end 33 | rescue StandardError 34 | raise unless @finished 35 | end 36 | 37 | def finish 38 | @finished = true 39 | @http_client&.close 40 | end 41 | 42 | private 43 | 44 | def max_hops 45 | @http_options[:http_max_redirects] + 1 46 | end 47 | 48 | def follow_option 49 | if max_hops > 1 50 | { max_hops: max_hops } 51 | else 52 | # i.e. Do not follow redirects as we have set http_max_redirects to 0 53 | # Setting `{ max_hops: 1 }` does not work FWIW 54 | false 55 | end 56 | end 57 | 58 | def build_client 59 | client = HTTP::Client.new(follow: follow_option) 60 | 61 | if @http_options[:basic_auth_user] && @http_options[:basic_auth_password] 62 | client = client.basic_auth( 63 | user: @http_options[:basic_auth_user], 64 | pass: @http_options[:basic_auth_password] 65 | ) 66 | end 67 | 68 | bearer_token = nil 69 | if @http_options[:bearer_token_file] 70 | bearer_token_file = @http_options[:bearer_token_file] 71 | if File.file?(bearer_token_file) && File.readable?(bearer_token_file) 72 | token = File.read(bearer_token_file).chomp 73 | bearer_token = "Bearer #{token}" 74 | end 75 | elsif @http_options[:bearer_token] 76 | bearer_token = "Bearer #{@http_options[:bearer_token]}" 77 | end 78 | 79 | if bearer_token 80 | client = client.auth(bearer_token) 81 | end 82 | 83 | client 84 | end 85 | 86 | def using_proxy 87 | proxy = @http_options[:http_proxy_uri] 88 | return nil unless proxy 89 | p_uri = URI.parse(proxy) 90 | { 91 | proxy_address: p_uri.hostname, 92 | proxy_port: p_uri.port, 93 | proxy_username: p_uri.user, 94 | proxy_password: p_uri.password 95 | } 96 | end 97 | 98 | def build_client_options 99 | client_options = { 100 | headers: @http_options[:headers], 101 | proxy: using_proxy 102 | } 103 | if @http_options[:ssl] 104 | client_options[:ssl] = @http_options[:ssl] 105 | socket_option = :ssl_socket_class 106 | else 107 | socket_option = :socket_class 108 | end 109 | client_options[socket_option] = @http_options[socket_option] if @http_options[socket_option] 110 | client_options 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "inheritConfig": true, 4 | "inheritConfigRepoName": "manageiq/renovate-config" 5 | } 6 | -------------------------------------------------------------------------------- /test/config/allinone.kubeconfig: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | clusters: 4 | - cluster: 5 | server: https://localhost:6443 6 | certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBRENDQWVpZ0F3SUJBZ0lVR2VkR1hLcWV2dUYxbVZFdXYxNVd4dC8xYlZBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERVdNQlFHQTFVRUF4TU5hM1ZpWlhKdVpYUmxjeTFqWVRBZUZ3MHlOREEwTURJeE56QTRNREJhRncwegpOREF6TXpFeE56QTRNREJhTUJneEZqQVVCZ05WQkFNVERXdDFZbVZ5Ym1WMFpYTXRZMkV3Z2dFaU1BMEdDU3FHClNJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURUdHVIU2tldStHMUZobmlZSGxUOGprM1NYM1IraGFFbFAKWW9xY3E4Rmx1T3RqV3NaQzcvdGltVkhzQzRhU09ya3ZKTjBWc2FUSVNWcGJhTW85RTJNeWVmbEFKMUR2eFBXWApITXR1V3NDNmhrZFZZcXQxVVpwcG1DNjFDQ0pKU3kzNTZockxuc09iK1RDVkpCYkc4TTFRaCtlUk40TVdVZmdYCmhEWC93ckswTjNLeTRuK0o3RXphNU5acml3OENwOEZTOUFDZU8zRXFNRmozVlVBV0ZrRXhUK2wxQ0FzMS95TzkKOVBnRkJOKzhrNXc2dVJsMkt0RHVLY1BRQ1RPQ0FSQ3cvUnVWWUFEcU03cGJIdXNCdFc4T2VOUzkxOTkzTWRqeQo0RngyUm91K2RaOHFSVzRuWjlDenVzdVNKd0Rxbjh5RUQyUHRjMFZkUUJGZS9CaGV2WDgzQWdNQkFBR2pRakJBCk1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJSc3E5Q1UKWXlMQmdQdXk1eUZrdjVraUluMFQ2akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBZDlwN3pKYThOU3lYTzBOUQordDd0UHRnb29hS2NDcnYvcnl5a2EvZUZiVGNlS0lPSEp1Qm5kMVkxUTVubzFYV2k2djZIaDVrSjRDbE9jdWU2CjFhOTFIUlA3Wkh4YW5IVWlDbGNqVWUrQ1FrdTJMS0RMQklQeUhURnBHa3ZYVFptbWxCcVI2TDlFa1hQZnhTRkMKRlpORFJLVXlQajNCYmYyeUE5NEphVTlsY0FWS2ZwVkU3SjVKZE5nL1lFRDNtalZsQmE5amZTWXdOS3lvTXlVaQpzeFZwd2JSRml0MzVucUp5TkgrRUdWblcwYkdvbW5hQXFZMkp2WGxYL2RLamlSWXg1aXdjbnRMa2I1Z2x0NDNRClFmYnB3cWt2RlphWFBiTGE0bFFkRFZvemFmN1BYMTgvL1l6dmtoZGNwNEYrQUtob0pka0hxZTdjRkJvNDd4VmcKUHlHTFNRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 7 | name: local 8 | contexts: 9 | - context: 10 | cluster: local 11 | namespace: default 12 | user: user 13 | name: Default 14 | current-context: Default 15 | kind: Config 16 | preferences: {} 17 | users: 18 | - name: user 19 | user: 20 | client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURXVENDQWtHZ0F3SUJBZ0lVUTNJUVM2K3JBd0FTb0E0M3hTbDJEUWxlbHNNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERVdNQlFHQTFVRUF4TU5hM1ZpWlhKdVpYUmxjeTFqWVRBZUZ3MHlOREEwTURJeE56QTRNREJhRncweQpOVEEwTURJeE56QTRNREJhTURReEZ6QVZCZ05WQkFvVERuTjVjM1JsYlRwdFlYTjBaWEp6TVJrd0Z3WURWUVFECkV4QnJkV0psY201bGRHVnpMV0ZrYldsdU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXlCWHFpVXJCeXZacmsva00xSC9ndnNRcVltRGtaSmlHRVZCQkdkc1oxNy91Sng0QXc3eGpoR2UrR0RBSApwSWNMN2o4MThvOXZSZ1JRV0p1L2EzbTFUeGdaamgvNEp2L3RDamloRlZNU1MySDRFYm53ZzU5UzBNZEtOMVQ4CmlRbW9tRW8xdnBhNUd3a2lBckpSVlNobWg4NFBjajdFZzZSUS8wT1hUMDlrb0FVWTAvWnlIYzRTUHczV2ZST3gKTWN4enlyZFltbjJrSkIrRkZPcUNwb3pvUkFERktnbnM2aDl6QmR6UkVzZGY0Q3czdkFrbmE5bWtGaXZrTjdKMgp6NWZndTN4K1BBVEF5bGpBRXlPeUEyV1phWnZ6MlI1aFpjSEgwZnhsYnNqTzgxdUhlTGRtZjJqbDUzRm1BdXdDClUzWE5yVzFmMkpFZW5JSldNZWIxVFZHMU1RSURBUUFCbzM4d2ZUQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0hRWUQKVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUF3R0ExVWRFd0VCL3dRQ01BQXdIUVlEVlIwTwpCQllFRk1vaGQ3UlZCMVhSRkNQMHZEa21KeFM0SmVEUE1COEdBMVVkSXdRWU1CYUFGR3lyMEpSaklzR0ErN0xuCklXUy9tU0lpZlJQcU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQSswQXhraHVPemxUS0xIOCtZWFZsNEd5MDYKK21lTmZqMHNZUTdzVVhDQStrOWg1bW1FUGxDSTRNbytURFRhdVlVejJjcDBzb29hUVF1YXJMVE9vb0g2ZThkeQpkVTgzbGRTbDhYN2sreHQ0bVdjbFE0SlJSZTlOZ05wY1pLTis4bkZaZTFST2JVT04zUVlpbnVEQkZIZXg3U2lnClh0TXFta2YwemZpS1MvMmVEN2dKaU1sb1duVWFZWTMyTXUrcytwc2VYTW9ETjhSK1E1dnhneWVVZnIyMVFYbDcKZlNVOEw1OTNCTmhhMGpybldpNUhxUFg2KzQybkpudU1YU3NQYkhEejNUbnlNSm4ySjl1QW93UVgrSW5XNGU1ago5NTNkbE8zRFZhVHN3Uk5Ya0xlb2E4VlYyUXYva3p2blVhbWpSa2VQckI2WStTdWpxdXZBb1RmbEpmaFYKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= 21 | client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeUJYcWlVckJ5dlpyay9rTTFIL2d2c1FxWW1Ea1pKaUdFVkJCR2RzWjE3L3VKeDRBCnc3eGpoR2UrR0RBSHBJY0w3ajgxOG85dlJnUlFXSnUvYTNtMVR4Z1pqaC80SnYvdENqaWhGVk1TUzJINEVibncKZzU5UzBNZEtOMVQ4aVFtb21FbzF2cGE1R3draUFySlJWU2htaDg0UGNqN0VnNlJRLzBPWFQwOWtvQVVZMC9aeQpIYzRTUHczV2ZST3hNY3h6eXJkWW1uMmtKQitGRk9xQ3Bvem9SQURGS2duczZoOXpCZHpSRXNkZjRDdzN2QWtuCmE5bWtGaXZrTjdKMno1Zmd1M3grUEFUQXlsakFFeU95QTJXWmFadnoyUjVoWmNISDBmeGxic2pPODF1SGVMZG0KZjJqbDUzRm1BdXdDVTNYTnJXMWYySkVlbklKV01lYjFUVkcxTVFJREFRQUJBb0lCQUNkdE9pUUhtUnhYWm1kbwpJZ0VWbWNHOWdlU1UxeXlEYVplcnlzZy9obDRmTWR4L2F3TW04aTA3REJoVURvMTdGdnpCYyszeUdha2dYSHk1ClpBVFhuTVNUS1NYNllybThvSWJ0RmNEUDk4RG04eXo3Q012RzcxaTRqSVZPdDl5QkRMbkZsY1dTaWJNc0g0bU0KZmI1Y0VhZ2RFS29nZURUQWdqMXFvanc4bHNWNUlsMjVZVHI4YWZ1MjlVN2dYdEV5NGV4UkVsaEI5VmQycjNlMgprVnJWOE9pa2hwcGVWcXkrbm1aR3NvQTJPREZVNXpyOWNoOVY5bTJ1aE9NK0M0eGt3ZTM3cmxPN09oSGhRa2hvCmtleXp2c3Z0QThKeTB5bU5WdWd1d3crQnpOYkxrLzZ4M0pMS2lMMk1PcFBzN0xMNnErREZYZWNjOFVxY3F1WloKQk1tWGdBRUNnWUVBN0d4Zmd2bWRPNXVSN0JmRzZPRmF1VUh1bnJkNGw1ODEreUJJclR2OG54UFRDTE01SjlydgpPei9TUUhlN3dVNFNIU2xYT3p4Yy9TNnZ3NmFrNmRjaWFkOS9zV0ZCTzdMMUU1VXVsdXFwUStsdm9XMGh6UElrCk5ESjhhcXFmTTloNWU4UmRpbEkwNG5wU0IySlF3MzJ2bmlxTHY4bU15TWR1N2NvQmFUMjdhVEVDZ1lFQTJLZEUKZlVTcksyYXZ5d25OSUNRQmk1dTNNR2VYeWoyMHRmU3J6YkpxOFk2VzBMN2YrVGFTWEdPOElMNWxhVitjR0VhUApQOGtKcEJpcStvS2tncWRWMGRXU2lsYTFjdjJOaWFZa2VpOHVyNEZ5RVN4MThsNGQ4YmJ3RTRwTTlzdmw2bHNsCitnNnhXTFNCNDhJeWZHaVpYaHQvU1o1eFVRczcrZTN4eG9jb0RBRUNnWUFsWVhVd2hqc0FBN2JzdDAyeDRIK3gKcnVVRm4waW40SHB1ZkVwQmFheWxra0xacTB5c0tvZU12S2lIbk9iSXg0bEJweG10dWZ1UjVHSFI4M1U3cTlJNwpIeVZYYkdNZUIwdU1qeGZRc01XV2dVQXZLOHdlVldLNEJndDU2Z1JGOUVJdytYazR0NHE1REZJUDdOR01OMEVmCkp1YjFxZTVpaUhYWGp3cnVFdDZnNFFLQmdRRE1zSXZXeG0vcDIyU3ZNWjFwaGxzbmFZRVQ3R3ZPbWFvSVZ4ZVMKaWNUY1g5bEJjM1RzWVpDNlZVaENyMS9reEg0WHVXSlI0MENHbHYvREduY1BNY1cxTUZENC9wOEQrMllqN0tKKwpCZUhYMHpiUDN1KzRndzlMZFlreERQTldJS0lpWHVkc2IwRkRDNWZFZFk5NVZ1Ry90YjcyOFFraWhyaXRrdzlnCmdBVm9BUUtCZ1FEZEJ2M0ZDczlMenhhTHBTUm9KSVlkSFZrc2g5Rk9FNXk1WEZCalNGTHdCL215UTZvdCtkZzQKMkk2emdMWElZZU45VEZiNDUrWkx5ek90RnlFaG92eXh6c0xqM0N0Y3pCM0ZKMDkrN0NwYVNYdXlYWEt6NjFRdgpsN3NYeHNlbms0MWJSUGZuS2VVdXY4ZVNrUmpGT09Zd0FoaFlXWlNCUnFZdVZJN0ZKWTZrQmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= 22 | -------------------------------------------------------------------------------- /test/config/another-ca1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDADCCAeigAwIBAgIUQZjM/5qoAF78qIDyc+rKi4qBdOIwDQYJKoZIhvcNAQEL 3 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQzMDBaFw0z 4 | MjAzMTkxNDQzMDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 5 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGkG7g+UjpDhZ7A4Pm7Hme+RWs5IHz4I2X 6 | IclvtO3LuJ26yzz2S8VaXFFeUqzEPb2G1RxFGvoAVN7qrTw0n5MQJCFLAA4dI7oY 7 | 8XLRJ7KgTBBIw1jYpgKb2zyHPIJE6VmslliKUiX+QDovdRU/dsbdup2EucrnGw4+ 8 | QNNAc3XMbXgm6lubA6znYZlSpcQ8BKer3tq75q4KUZicIjS6gKQyZjk9a6fcOuCS 9 | ybtlAKp9lYzcwxZkNrx+V1PJMQ1qaJWPnMAVi7Oj5Dm3Jmf1WHBcNEh52Q/0vYlt 10 | 4WSaeM5t/Py/m/7c4Ve97f5m2X6EhYyUbzov4qeZOnIJI3MnU1FxAgMBAAGjQjBA 11 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSl1qyt 12 | jd96WstRE8h9x5qkCvZUvjANBgkqhkiG9w0BAQsFAAOCAQEAJt55qYvBaniAwvgO 13 | tbO79g1FcQGrxpMX45TuoCE/K+MWDjrr6bp+FbLOqT8MwOsbGwwJIRTHGvkEkVso 14 | 5AWI5aSNs3hWnltOdz27ZSHeX77WB4daK1tLK6ggZrp3v9iIpbBwWBFdmAqsPvEs 15 | H17K2BgAzdh6xRKPQd0BGTUpJBfk50R2gDMj7FKyIzBN69IOGytBfAXBhHzEGy4+ 16 | MvtTEIMUjR//KgCrpNeyDuaWHttR5FdnuRxFO7O3BAfyNSaNmd/IEHQf7DIGgzOy 17 | +xWLyH/HRHj5C70qAqjbnrgBODI99BsA9U7oXTuyPLdIboAcFt2zD5DIYgZET52X 18 | 53w4jA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/config/another-ca2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDADCCAeigAwIBAgIUHW3OPnmuTquJ0YgbGpmm/blsY2QwDQYJKoZIhvcNAQEL 3 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQ0MDBaFw0z 4 | MjAzMTkxNDQ0MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 5 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLMEJs5agS0hNQBxPTtsI6dIhIi/pY8liI 6 | sNukbi5KwKf80FYNyRXqE8ufDVyTFzOc+MG96jnHjDaBWjrVN9On0PgUBo4nPyd4 7 | DtyvYx2jMzwToSEIo/Z1aroMx1oGywCgdS4/3FWAbhlSbyXKJmhfh6gX0TxWz+dV 8 | zqNuqQq9EWuRhOMg9vgzjfp3mjiPE10lW8pT0j5JT3PI/eGO+C2Z7z33LJXb6GM2 9 | nXvhGFMGY+7XG65pqJ3L8g1mk+LjPiwyIItw8wPtrnrZ2VXMklMd5Mn+jgCTNe1B 10 | om0nPpPIiTblCr6gcNcVjy5WGN37OKlqrT0JTuSPHcxSUp05LFjDAgMBAAGjQjBA 11 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQvV/sB 12 | wbR3UwjkLAMN+6P3fZ/3OjANBgkqhkiG9w0BAQsFAAOCAQEACAk4EQwCkw2EBsSR 13 | 2SKoa1SjYFkZzIr/0/TB2YcMUvHF+RpvlD5vQ8/RJjeAl1kc6/niZ9TWCemjBLqI 14 | hPoFe49zr49DyQjC2ZfsXVJvFCr6g7o4q4DtQ6ltyBuTJbkn1hI+aB8zgvpofG44 15 | mKj18Y7tPvgXtRua4SaeBq777+22AOvKxPied9p4PTrMN4RKTP6+yIbLflej7dBD 16 | zQDjfmmYsH0T2ZRtBpE1dYrUbU3tkizcMZRJBgreoxoff+r5coibMIm/7gh+YoSb 17 | BCItCaeuGSKQ8CJb8DElcPUd6nKUjmeiQL68ztsG/+CXLiL/TZb914VaaCXvPInw 18 | 49jJ7w== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/config/concatenated-ca.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority: concatenated-ca.pem 5 | server: https://localhost:6443 6 | name: local 7 | contexts: 8 | - context: 9 | cluster: local 10 | namespace: default 11 | user: user 12 | name: Default 13 | current-context: Default 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: user 18 | user: 19 | client-certificate: external-cert.pem 20 | client-key: external-key.rsa 21 | -------------------------------------------------------------------------------- /test/config/concatenated-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDADCCAeigAwIBAgIUQZjM/5qoAF78qIDyc+rKi4qBdOIwDQYJKoZIhvcNAQEL 3 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQzMDBaFw0z 4 | MjAzMTkxNDQzMDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 5 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGkG7g+UjpDhZ7A4Pm7Hme+RWs5IHz4I2X 6 | IclvtO3LuJ26yzz2S8VaXFFeUqzEPb2G1RxFGvoAVN7qrTw0n5MQJCFLAA4dI7oY 7 | 8XLRJ7KgTBBIw1jYpgKb2zyHPIJE6VmslliKUiX+QDovdRU/dsbdup2EucrnGw4+ 8 | QNNAc3XMbXgm6lubA6znYZlSpcQ8BKer3tq75q4KUZicIjS6gKQyZjk9a6fcOuCS 9 | ybtlAKp9lYzcwxZkNrx+V1PJMQ1qaJWPnMAVi7Oj5Dm3Jmf1WHBcNEh52Q/0vYlt 10 | 4WSaeM5t/Py/m/7c4Ve97f5m2X6EhYyUbzov4qeZOnIJI3MnU1FxAgMBAAGjQjBA 11 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSl1qyt 12 | jd96WstRE8h9x5qkCvZUvjANBgkqhkiG9w0BAQsFAAOCAQEAJt55qYvBaniAwvgO 13 | tbO79g1FcQGrxpMX45TuoCE/K+MWDjrr6bp+FbLOqT8MwOsbGwwJIRTHGvkEkVso 14 | 5AWI5aSNs3hWnltOdz27ZSHeX77WB4daK1tLK6ggZrp3v9iIpbBwWBFdmAqsPvEs 15 | H17K2BgAzdh6xRKPQd0BGTUpJBfk50R2gDMj7FKyIzBN69IOGytBfAXBhHzEGy4+ 16 | MvtTEIMUjR//KgCrpNeyDuaWHttR5FdnuRxFO7O3BAfyNSaNmd/IEHQf7DIGgzOy 17 | +xWLyH/HRHj5C70qAqjbnrgBODI99BsA9U7oXTuyPLdIboAcFt2zD5DIYgZET52X 18 | 53w4jA== 19 | -----END CERTIFICATE----- 20 | -----BEGIN CERTIFICATE----- 21 | MIIDADCCAeigAwIBAgIUGedGXKqevuF1mVEuv15Wxt/1bVAwDQYJKoZIhvcNAQEL 22 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yNDA0MDIxNzA4MDBaFw0z 23 | NDAzMzExNzA4MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 24 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTtuHSkeu+G1FhniYHlT8jk3SX3R+haElP 25 | Yoqcq8FluOtjWsZC7/timVHsC4aSOrkvJN0VsaTISVpbaMo9E2MyeflAJ1DvxPWX 26 | HMtuWsC6hkdVYqt1UZppmC61CCJJSy356hrLnsOb+TCVJBbG8M1Qh+eRN4MWUfgX 27 | hDX/wrK0N3Ky4n+J7Eza5NZriw8Cp8FS9ACeO3EqMFj3VUAWFkExT+l1CAs1/yO9 28 | 9PgFBN+8k5w6uRl2KtDuKcPQCTOCARCw/RuVYADqM7pbHusBtW8OeNS91993Mdjy 29 | 4Fx2Rou+dZ8qRW4nZ9CzusuSJwDqn8yED2Ptc0VdQBFe/BhevX83AgMBAAGjQjBA 30 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRsq9CU 31 | YyLBgPuy5yFkv5kiIn0T6jANBgkqhkiG9w0BAQsFAAOCAQEAd9p7zJa8NSyXO0NQ 32 | +t7tPtgooaKcCrv/ryyka/eFbTceKIOHJuBnd1Y1Q5no1XWi6v6Hh5kJ4ClOcue6 33 | 1a91HRP7ZHxanHUiClcjUe+CQku2LKDLBIPyHTFpGkvXTZmmlBqR6L9EkXPfxSFC 34 | FZNDRKUyPj3Bbf2yA94JaU9lcAVKfpVE7J5JdNg/YED3mjVlBa9jfSYwNKyoMyUi 35 | sxVpwbRFit35nqJyNH+EGVnW0bGomnaAqY2JvXlX/dKjiRYx5iwcntLkb5glt43Q 36 | QfbpwqkvFZaXPbLa4lQdDVozaf7PX18//Yzvkhdcp4F+AKhoJdkHqe7cFBo47xVg 37 | PyGLSQ== 38 | -----END CERTIFICATE----- 39 | -----BEGIN CERTIFICATE----- 40 | MIIDADCCAeigAwIBAgIUHW3OPnmuTquJ0YgbGpmm/blsY2QwDQYJKoZIhvcNAQEL 41 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yMjAzMjIxNDQ0MDBaFw0z 42 | MjAzMTkxNDQ0MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 43 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLMEJs5agS0hNQBxPTtsI6dIhIi/pY8liI 44 | sNukbi5KwKf80FYNyRXqE8ufDVyTFzOc+MG96jnHjDaBWjrVN9On0PgUBo4nPyd4 45 | DtyvYx2jMzwToSEIo/Z1aroMx1oGywCgdS4/3FWAbhlSbyXKJmhfh6gX0TxWz+dV 46 | zqNuqQq9EWuRhOMg9vgzjfp3mjiPE10lW8pT0j5JT3PI/eGO+C2Z7z33LJXb6GM2 47 | nXvhGFMGY+7XG65pqJ3L8g1mk+LjPiwyIItw8wPtrnrZ2VXMklMd5Mn+jgCTNe1B 48 | om0nPpPIiTblCr6gcNcVjy5WGN37OKlqrT0JTuSPHcxSUp05LFjDAgMBAAGjQjBA 49 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQvV/sB 50 | wbR3UwjkLAMN+6P3fZ/3OjANBgkqhkiG9w0BAQsFAAOCAQEACAk4EQwCkw2EBsSR 51 | 2SKoa1SjYFkZzIr/0/TB2YcMUvHF+RpvlD5vQ8/RJjeAl1kc6/niZ9TWCemjBLqI 52 | hPoFe49zr49DyQjC2ZfsXVJvFCr6g7o4q4DtQ6ltyBuTJbkn1hI+aB8zgvpofG44 53 | mKj18Y7tPvgXtRua4SaeBq777+22AOvKxPied9p4PTrMN4RKTP6+yIbLflej7dBD 54 | zQDjfmmYsH0T2ZRtBpE1dYrUbU3tkizcMZRJBgreoxoff+r5coibMIm/7gh+YoSb 55 | BCItCaeuGSKQ8CJb8DElcPUd6nKUjmeiQL68ztsG/+CXLiL/TZb914VaaCXvPInw 56 | 49jJ7w== 57 | -----END CERTIFICATE----- 58 | -------------------------------------------------------------------------------- /test/config/execauth.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:6443 5 | name: localhost:6443 6 | contexts: 7 | - context: 8 | cluster: localhost:6443 9 | namespace: default 10 | user: system:admin:exec-search-path 11 | name: localhost/system:admin:exec-search-path 12 | - context: 13 | cluster: localhost:6443 14 | namespace: default 15 | user: system:admin:exec-relative-path 16 | name: localhost/system:admin:exec-relative-path 17 | - context: 18 | cluster: localhost:6443 19 | namespace: default 20 | user: system:admin:exec-absolute-path 21 | name: localhost/system:admin:exec-absolute-path 22 | kind: Config 23 | preferences: {} 24 | users: 25 | - name: system:admin:exec-search-path 26 | user: 27 | exec: 28 | # Command to execute. Required. 29 | command: "example-exec-plugin" 30 | 31 | # API version to use when decoding the ExecCredentials resource. Required. 32 | # 33 | # The API version returned by the plugin MUST match the version listed here. 34 | # 35 | # To integrate with tools that support multiple versions (such as client.authentication.k8s.io/v1alpha1), 36 | # set an environment variable or pass an argument to the tool that indicates which version the exec plugin expects. 37 | apiVersion: "client.authentication.k8s.io/v1beta1" 38 | 39 | # Environment variables to set when executing the plugin. Optional. 40 | env: 41 | - name: "FOO" 42 | value: "bar" 43 | 44 | # Arguments to pass when executing the plugin. Optional. 45 | args: 46 | - "arg1" 47 | - "arg2" 48 | 49 | - name: system:admin:exec-relative-path 50 | user: 51 | exec: 52 | # Command to execute. Required. 53 | command: "dir/example-exec-plugin" 54 | apiVersion: "client.authentication.k8s.io/v1beta1" 55 | 56 | - name: system:admin:exec-absolute-path 57 | user: 58 | exec: 59 | # Command to execute. Required. 60 | command: "/abs/path/example-exec-plugin" 61 | apiVersion: "client.authentication.k8s.io/v1beta1" 62 | -------------------------------------------------------------------------------- /test/config/external-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDADCCAeigAwIBAgIUGedGXKqevuF1mVEuv15Wxt/1bVAwDQYJKoZIhvcNAQEL 3 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yNDA0MDIxNzA4MDBaFw0z 4 | NDAzMzExNzA4MDBaMBgxFjAUBgNVBAMTDWt1YmVybmV0ZXMtY2EwggEiMA0GCSqG 5 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTtuHSkeu+G1FhniYHlT8jk3SX3R+haElP 6 | Yoqcq8FluOtjWsZC7/timVHsC4aSOrkvJN0VsaTISVpbaMo9E2MyeflAJ1DvxPWX 7 | HMtuWsC6hkdVYqt1UZppmC61CCJJSy356hrLnsOb+TCVJBbG8M1Qh+eRN4MWUfgX 8 | hDX/wrK0N3Ky4n+J7Eza5NZriw8Cp8FS9ACeO3EqMFj3VUAWFkExT+l1CAs1/yO9 9 | 9PgFBN+8k5w6uRl2KtDuKcPQCTOCARCw/RuVYADqM7pbHusBtW8OeNS91993Mdjy 10 | 4Fx2Rou+dZ8qRW4nZ9CzusuSJwDqn8yED2Ptc0VdQBFe/BhevX83AgMBAAGjQjBA 11 | MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRsq9CU 12 | YyLBgPuy5yFkv5kiIn0T6jANBgkqhkiG9w0BAQsFAAOCAQEAd9p7zJa8NSyXO0NQ 13 | +t7tPtgooaKcCrv/ryyka/eFbTceKIOHJuBnd1Y1Q5no1XWi6v6Hh5kJ4ClOcue6 14 | 1a91HRP7ZHxanHUiClcjUe+CQku2LKDLBIPyHTFpGkvXTZmmlBqR6L9EkXPfxSFC 15 | FZNDRKUyPj3Bbf2yA94JaU9lcAVKfpVE7J5JdNg/YED3mjVlBa9jfSYwNKyoMyUi 16 | sxVpwbRFit35nqJyNH+EGVnW0bGomnaAqY2JvXlX/dKjiRYx5iwcntLkb5glt43Q 17 | QfbpwqkvFZaXPbLa4lQdDVozaf7PX18//Yzvkhdcp4F+AKhoJdkHqe7cFBo47xVg 18 | PyGLSQ== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/config/external-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDWTCCAkGgAwIBAgIUQ3IQS6+rAwASoA43xSl2DQlelsMwDQYJKoZIhvcNAQEL 3 | BQAwGDEWMBQGA1UEAxMNa3ViZXJuZXRlcy1jYTAeFw0yNDA0MDIxNzA4MDBaFw0y 4 | NTA0MDIxNzA4MDBaMDQxFzAVBgNVBAoTDnN5c3RlbTptYXN0ZXJzMRkwFwYDVQQD 5 | ExBrdWJlcm5ldGVzLWFkbWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 6 | AQEAyBXqiUrByvZrk/kM1H/gvsQqYmDkZJiGEVBBGdsZ17/uJx4Aw7xjhGe+GDAH 7 | pIcL7j818o9vRgRQWJu/a3m1TxgZjh/4Jv/tCjihFVMSS2H4Ebnwg59S0MdKN1T8 8 | iQmomEo1vpa5GwkiArJRVShmh84Pcj7Eg6RQ/0OXT09koAUY0/ZyHc4SPw3WfROx 9 | McxzyrdYmn2kJB+FFOqCpozoRADFKgns6h9zBdzREsdf4Cw3vAkna9mkFivkN7J2 10 | z5fgu3x+PATAyljAEyOyA2WZaZvz2R5hZcHH0fxlbsjO81uHeLdmf2jl53FmAuwC 11 | U3XNrW1f2JEenIJWMeb1TVG1MQIDAQABo38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYD 12 | VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0O 13 | BBYEFMohd7RVB1XRFCP0vDkmJxS4JeDPMB8GA1UdIwQYMBaAFGyr0JRjIsGA+7Ln 14 | IWS/mSIifRPqMA0GCSqGSIb3DQEBCwUAA4IBAQA+0AxkhuOzlTKLH8+YXVl4Gy06 15 | +meNfj0sYQ7sUXCA+k9h5mmEPlCI4Mo+TDTauYUz2cp0sooaQQuarLTOooH6e8dy 16 | dU83ldSl8X7k+xt4mWclQ4JRRe9NgNpcZKN+8nFZe1RObUON3QYinuDBFHex7Sig 17 | XtMqmkf0zfiKS/2eD7gJiMloWnUaYY32Mu+s+pseXMoDN8R+Q5vxgyeUfr21QXl7 18 | fSU8L593BNha0jrnWi5HqPX6+42nJnuMXSsPbHDz3TnyMJn2J9uAowQX+InW4e5j 19 | 953dlO3DVaTswRNXkLeoa8VV2Qv/kzvnUamjRkePrB6Y+SujquvAoTflJfhV 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /test/config/external-key.rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAyBXqiUrByvZrk/kM1H/gvsQqYmDkZJiGEVBBGdsZ17/uJx4A 3 | w7xjhGe+GDAHpIcL7j818o9vRgRQWJu/a3m1TxgZjh/4Jv/tCjihFVMSS2H4Ebnw 4 | g59S0MdKN1T8iQmomEo1vpa5GwkiArJRVShmh84Pcj7Eg6RQ/0OXT09koAUY0/Zy 5 | Hc4SPw3WfROxMcxzyrdYmn2kJB+FFOqCpozoRADFKgns6h9zBdzREsdf4Cw3vAkn 6 | a9mkFivkN7J2z5fgu3x+PATAyljAEyOyA2WZaZvz2R5hZcHH0fxlbsjO81uHeLdm 7 | f2jl53FmAuwCU3XNrW1f2JEenIJWMeb1TVG1MQIDAQABAoIBACdtOiQHmRxXZmdo 8 | IgEVmcG9geSU1yyDaZerysg/hl4fMdx/awMm8i07DBhUDo17FvzBc+3yGakgXHy5 9 | ZATXnMSTKSX6Yrm8oIbtFcDP98Dm8yz7CMvG71i4jIVOt9yBDLnFlcWSibMsH4mM 10 | fb5cEagdEKogeDTAgj1qojw8lsV5Il25YTr8afu29U7gXtEy4exRElhB9Vd2r3e2 11 | kVrV8OikhppeVqy+nmZGsoA2ODFU5zr9ch9V9m2uhOM+C4xkwe37rlO7OhHhQkho 12 | keyzvsvtA8Jy0ymNVuguww+BzNbLk/6x3JLKiL2MOpPs7LL6q+DFXecc8UqcquZZ 13 | BMmXgAECgYEA7GxfgvmdO5uR7BfG6OFauUHunrd4l581+yBIrTv8nxPTCLM5J9rv 14 | Oz/SQHe7wU4SHSlXOzxc/S6vw6ak6dciad9/sWFBO7L1E5UuluqpQ+lvoW0hzPIk 15 | NDJ8aqqfM9h5e8RdilI04npSB2JQw32vniqLv8mMyMdu7coBaT27aTECgYEA2KdE 16 | fUSrK2avywnNICQBi5u3MGeXyj20tfSrzbJq8Y6W0L7f+TaSXGO8IL5laV+cGEaP 17 | P8kJpBiq+oKkgqdV0dWSila1cv2NiaYkei8ur4FyESx18l4d8bbwE4pM9svl6lsl 18 | +g6xWLSB48IyfGiZXht/SZ5xUQs7+e3xxocoDAECgYAlYXUwhjsAA7bst02x4H+x 19 | ruUFn0in4HpufEpBaaylkkLZq0ysKoeMvKiHnObIx4lBpxmtufuR5GHR83U7q9I7 20 | HyVXbGMeB0uMjxfQsMWWgUAvK8weVWK4Bgt56gRF9EIw+Xk4t4q5DFIP7NGMN0Ef 21 | Jub1qe5iiHXXjwruEt6g4QKBgQDMsIvWxm/p22SvMZ1phlsnaYET7GvOmaoIVxeS 22 | icTcX9lBc3TsYZC6VUhCr1/kxH4XuWJR40CGlv/DGncPMcW1MFD4/p8D+2Yj7KJ+ 23 | BeHX0zbP3u+4gw9LdYkxDPNWIKIiXudsb0FDC5fEdY95VuG/tb728Qkihritkw9g 24 | gAVoAQKBgQDdBv3FCs9LzxaLpSRoJIYdHVksh9FOE5y5XFBjSFLwB/myQ6ot+dg4 25 | 2I6zgLXIYeN9TFb45+ZLyzOtFyEhovyxzsLj3CtczB3FJ09+7CpaSXuyXXKz61Qv 26 | l7sXxsenk41bRPfnKeUuv8eSkRjFOOYwAhhYWZSBRqYuVI7FJY6kBg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/config/external-without-ca.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | # Not defining custom `certificate-authority`. 5 | # Without it, the localhost cert should be rejected. 6 | server: https://localhost:6443 7 | name: local 8 | contexts: 9 | - context: 10 | cluster: local 11 | namespace: default 12 | user: user 13 | name: Default 14 | current-context: Default 15 | kind: Config 16 | preferences: {} 17 | users: 18 | - name: user 19 | user: 20 | client-certificate: external-cert.pem 21 | client-key: external-key.rsa 22 | -------------------------------------------------------------------------------- /test/config/external.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority: external-ca.pem 5 | server: https://localhost:6443 6 | name: local 7 | contexts: 8 | - context: 9 | cluster: local 10 | namespace: default 11 | user: user 12 | name: Default 13 | current-context: Default 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: user 18 | user: 19 | client-certificate: external-cert.pem 20 | client-key: external-key.rsa 21 | -------------------------------------------------------------------------------- /test/config/gcpauth.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | name: localhost:8443 6 | contexts: 7 | - context: 8 | cluster: localhost:8443 9 | namespace: default 10 | user: application-default-credentials 11 | name: localhost/application-default-credentials 12 | kind: Config 13 | preferences: {} 14 | users: 15 | - name: application-default-credentials 16 | user: 17 | auth-provider: 18 | config: 19 | access-token: 20 | expiry: 2019-02-19T11:07:29.827352-05:00 21 | name: gcp 22 | -------------------------------------------------------------------------------- /test/config/gcpcmdauth.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | name: localhost:8443 6 | contexts: 7 | - context: 8 | cluster: localhost:8443 9 | namespace: default 10 | user: application-default-credentials 11 | name: localhost/application-default-credentials 12 | kind: Config 13 | preferences: {} 14 | users: 15 | - name: application-default-credentials 16 | user: 17 | auth-provider: 18 | config: 19 | access-token: 20 | cmd-args: config config-helper --format=json 21 | cmd-path: /path/to/gcloud 22 | expiry: 2019-04-09T19:26:18Z 23 | expiry-key: '{.credential.token_expiry}' 24 | token-key: '{.credential.access_token}' 25 | name: gcp 26 | -------------------------------------------------------------------------------- /test/config/impersonate-empty-groups.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | insecure-skip-tls-verify: true 6 | name: localhost:8443 7 | contexts: 8 | - context: 9 | cluster: localhost:8443 10 | namespace: default 11 | user: impersonate 12 | name: localhost/impersonate 13 | current-context: localhost/impersonate 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: impersonate 18 | user: 19 | as: foo 20 | as-groups: [] 21 | as-user-extra: 22 | reason: [] 23 | -------------------------------------------------------------------------------- /test/config/impersonate.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | insecure-skip-tls-verify: true 6 | name: localhost:8443 7 | contexts: 8 | - context: 9 | cluster: localhost:8443 10 | namespace: default 11 | user: impersonate 12 | name: localhost/impersonate 13 | current-context: localhost/impersonate 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: impersonate 18 | user: 19 | as: foo 20 | as-groups: [bar, baz] 21 | as-user-extra: 22 | reason: [foo] 23 | -------------------------------------------------------------------------------- /test/config/insecure-custom-ca.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | # This is a silly configuration, skip-tls-verify makes CA data useless, but testing for completeness. 5 | certificate-authority: external-ca.pem 6 | server: https://localhost:6443 7 | insecure-skip-tls-verify: true 8 | name: local 9 | contexts: 10 | - context: 11 | cluster: local 12 | namespace: default 13 | user: user 14 | name: Default 15 | current-context: Default 16 | kind: Config 17 | preferences: {} 18 | users: 19 | - name: user 20 | user: 21 | client-certificate: external-cert.pem 22 | client-key: external-key.rsa 23 | -------------------------------------------------------------------------------- /test/config/insecure.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:6443 5 | insecure-skip-tls-verify: true 6 | name: local 7 | contexts: 8 | - context: 9 | cluster: local 10 | namespace: default 11 | user: user 12 | name: Default 13 | current-context: Default 14 | kind: Config 15 | preferences: {} 16 | users: 17 | - name: user 18 | user: 19 | # Providing ANY credentials in `insecure-skip-tls-verify` mode is unwise due to MITM risk. 20 | # At least client certs are not as catastrophic as bearer tokens. 21 | # 22 | # This combination of insecure + client certs was once broken in kubernetes but 23 | # is meaningful since 2015 (https://github.com/kubernetes/kubernetes/pull/15430). 24 | client-certificate: external-cert.pem 25 | client-key: external-key.rsa 26 | -------------------------------------------------------------------------------- /test/config/nouser.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:6443 5 | name: localhost:6443 6 | contexts: 7 | - context: 8 | cluster: localhost:6443 9 | namespace: default 10 | user: "" 11 | name: default/localhost:6443/nouser 12 | current-context: default/localhost:6443/nouser 13 | kind: Config 14 | preferences: {} 15 | users: [] 16 | -------------------------------------------------------------------------------- /test/config/oidcauth.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:8443 5 | name: localhost:8443 6 | contexts: 7 | - context: 8 | cluster: localhost:8443 9 | namespace: default 10 | user: oidc-auth-provider 11 | name: localhost/oidc-auth-provider 12 | kind: Config 13 | preferences: {} 14 | users: 15 | - name: oidc-auth-provider 16 | user: 17 | auth-provider: 18 | config: 19 | client-id: fake-client-id 20 | client-secret: fake-client-secret 21 | id-token: fake-id-token 22 | idp-issuer-url: https://accounts.google.com 23 | refresh-token: fake-refresh-token 24 | name: oidc 25 | -------------------------------------------------------------------------------- /test/config/secure-without-ca.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | # Not defining custom `certificate-authority`. 5 | # Without it, the localhost cert should be rejected. 6 | server: https://localhost:6443 7 | insecure-skip-tls-verify: false # Same as external-without-ca.kubeconfig but with explicit false here. 8 | name: local 9 | contexts: 10 | - context: 11 | cluster: local 12 | namespace: default 13 | user: user 14 | name: Default 15 | current-context: Default 16 | kind: Config 17 | preferences: {} 18 | users: 19 | - name: user 20 | user: 21 | client-certificate: external-cert.pem 22 | client-key: external-key.rsa 23 | -------------------------------------------------------------------------------- /test/config/secure.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | certificate-authority: external-ca.pem 5 | server: https://localhost:6443 6 | insecure-skip-tls-verify: false # Same as external.kubeconfig but with explicit false here. 7 | name: local 8 | contexts: 9 | - context: 10 | cluster: local 11 | namespace: default 12 | user: user 13 | name: Default 14 | current-context: Default 15 | kind: Config 16 | preferences: {} 17 | users: 18 | - name: user 19 | user: 20 | client-certificate: external-cert.pem 21 | client-key: external-key.rsa 22 | -------------------------------------------------------------------------------- /test/config/timestamps.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | users: 3 | - name: gke_username 4 | user: 5 | auth-provider: 6 | config: 7 | access-token: REDACTED 8 | cmd-args: config config-helper --format=json 9 | cmd-path: /Users/tannerbruce/opt/google-cloud-sdk/bin/gcloud 10 | expiry: 2018-07-07T18:25:36Z 11 | expiry-key: '{.credential.token_expiry}' 12 | token-key: '{.credential.access_token}' 13 | name: gcp 14 | 15 | # More syntaxes from go-yaml tests, hopefully covering all types possible in kubeconfig 16 | IPv4: 1.2.3.4 17 | Duration: 3s 18 | date_only: 2015-01-01 19 | rfc3339: 2015-02-24T18:19:39Z 20 | longer: 2015-02-24T18:19:39.123456789-03:00 21 | shorter: 2015-2-3T3:4:5Z 22 | iso_lower_t: 2015-02-24t18:19:39Z 23 | space_no_tz: 2015-02-24 18:19:39 24 | space_tz: 2001-12-14 21:59:43.10 -5 25 | timestamp_like_string: "2015-02-24T18:19:39Z" 26 | -------------------------------------------------------------------------------- /test/config/update_certs_k0s.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # https://docs.k0sproject.io/latest/k0s-in-docker/ 3 | # Runs in --privileged mode, only run this if you trust the k0s distribution. 4 | 5 | require 'English' 6 | 7 | # Like Kernel#system, returns true iff exit status == 0 8 | def sh?(*cmd) 9 | puts("+ #{cmd.join(' ')}") 10 | system(*cmd) 11 | end 12 | 13 | # Raises if exit status != 0 14 | def sh!(*cmd) 15 | sh?(*cmd) || raise("returned #{$CHILD_STATUS}") 16 | end 17 | 18 | # allow DOCKER='sudo docker', DOCKER=podman etc. 19 | DOCKER = ENV['DOCKER'] || 'docker' 20 | 21 | CONTAINER = 'k0s'.freeze 22 | 23 | sh! "#{DOCKER} container inspect #{CONTAINER} --format='exists' || 24 | #{DOCKER} run -d --name #{CONTAINER} --hostname k0s --privileged -v /var/lib/k0s -p 6443:6443 \ 25 | ghcr.io/k0sproject/k0s/k0s:v1.23.3-k0s.1" 26 | 27 | # sh! "#{DOCKER} exec #{CONTAINER} kubectl config view --raw" 28 | # is another way to dump kubeconfig but succeeds with dummy output even before admin.conf exists; 29 | # so accessing the file is better way as it lets us poll until ready: 30 | sleep(1) until sh?("#{DOCKER} exec #{CONTAINER} ls -l /var/lib/k0s/pki/admin.conf") 31 | 32 | sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.conf > test/config/allinone.kubeconfig" 33 | # The rest could easily be extracted from allinone.kubeconfig, but the test is more robust 34 | # if we don't reuse YAML and/or Kubeclient::Config parsing to construct test data. 35 | sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/ca.crt > test/config/external-ca.pem" 36 | sh! 'cat test/config/another-ca1.pem test/config/external-ca.pem '\ 37 | ' test/config/another-ca2.pem > test/config/concatenated-ca.pem' 38 | sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.crt > test/config/external-cert.pem" 39 | sh! "#{DOCKER} exec #{CONTAINER} cat /var/lib/k0s/pki/admin.key > test/config/external-key.rsa" 40 | 41 | # Wait for apiserver to be up. To speed startup, this only retries connection errors; 42 | # without `--fail-with-body` curl still returns 0 for well-formed 4xx or 5xx responses. 43 | sleep(1) until sh?( 44 | 'curl --cacert test/config/external-ca.pem ' \ 45 | '--key test/config/external-key.rsa ' \ 46 | '--cert test/config/external-cert.pem https://127.0.0.1:6443/healthz' 47 | ) 48 | 49 | sh! 'env KUBECLIENT_TEST_REAL_CLUSTER=true bundle exec rake test' 50 | 51 | sh! "#{DOCKER} rm -f #{CONTAINER}" 52 | 53 | puts 'If you run this only for tests, cleanup by running: git restore test/config/' 54 | -------------------------------------------------------------------------------- /test/config/userauth.kubeconfig: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: https://localhost:6443 5 | name: localhost:6443 6 | contexts: 7 | - context: 8 | cluster: localhost:6443 9 | namespace: default 10 | user: system:admin:token 11 | name: localhost/system:admin:token 12 | - context: 13 | cluster: localhost:6443 14 | namespace: default 15 | user: system:admin:userpass 16 | name: localhost/system:admin:userpass 17 | current-context: localhost/system:admin:token 18 | kind: Config 19 | preferences: {} 20 | users: 21 | - name: system:admin:token 22 | user: 23 | token: 0123456789ABCDEF0123456789ABCDEF 24 | - name: system:admin:userpass 25 | user: 26 | username: admin 27 | password: pAssw0rd123 28 | -------------------------------------------------------------------------------- /test/gemfiles/Gemfile.faraday-1: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in kubeclient.gemspec 4 | gemspec :path => '../../' 5 | 6 | gem 'faraday', '~> 1.1' -------------------------------------------------------------------------------- /test/gemfiles/Gemfile.faraday-2: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in kubeclient.gemspec 4 | gemspec :path => '../../' 5 | 6 | gem 'faraday', '~> 2.0' -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'minitest/autorun' 5 | require 'minitest/rg' 6 | require 'webmock/minitest' 7 | require 'mocha/minitest' 8 | require 'json' 9 | require_relative '../lib/kubeclient' 10 | 11 | Thread.abort_on_exception = true 12 | 13 | MiniTest::Test.class_eval do 14 | # Assumes test files will be in a subdirectory with the same name as the 15 | # file suffix. e.g. a file named foo.json would be a "json" subdirectory. 16 | def open_test_file(name) 17 | File.new(File.join(File.dirname(__FILE__), name.split('.').last, name)) 18 | end 19 | 20 | # kubeconfig files deviate from above convention. 21 | # They link to relaved certs etc. with various extensions, all in same dir. 22 | def config_file(name) 23 | File.join(File.dirname(__FILE__), 'config', name) 24 | end 25 | 26 | def stub_core_api_list 27 | stub_request(:get, %r{/api/v1$}) 28 | .to_return(body: open_test_file('core_api_resource_list.json'), status: 200) 29 | end 30 | end 31 | 32 | WebMock.disable_net_connect! 33 | -------------------------------------------------------------------------------- /test/json/bindings_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Status", 3 | "apiVersion": "v1", 4 | "metadata": {}, 5 | "status": "Failure", 6 | "message": "the server could not find the requested resource", 7 | "reason": "NotFound", 8 | "details": {}, 9 | "code": 404 10 | } -------------------------------------------------------------------------------- /test/json/component_status.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ComponentStatus", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "etcd-0", 6 | "selfLink": "/api/v1/namespaces/componentstatuses/etcd-0", 7 | "creationTimestamp": null 8 | }, 9 | "conditions": [ 10 | { 11 | "type": "Healthy", 12 | "status": "True", 13 | "message": "{\"health\": \"true\"}", 14 | "error": "nil" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /test/json/component_status_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ComponentStatusList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/componentstatuses" 6 | }, 7 | "items": [ 8 | { 9 | "metadata": { 10 | "name": "controller-manager", 11 | "selfLink": "/api/v1/namespaces/componentstatuses/controller-manager", 12 | "creationTimestamp": null 13 | }, 14 | "conditions": [ 15 | { 16 | "type": "Healthy", 17 | "status": "Unknown", 18 | "error": "Get http://127.0.0.1:10252/healthz: dial tcp 127.0.0.1:10252: connection refused" 19 | } 20 | ] 21 | }, 22 | { 23 | "metadata": { 24 | "name": "scheduler", 25 | "selfLink": "/api/v1/namespaces/componentstatuses/scheduler", 26 | "creationTimestamp": null 27 | }, 28 | "conditions": [ 29 | { 30 | "type": "Healthy", 31 | "status": "Unknown", 32 | "error": "Get http://127.0.0.1:10251/healthz: dial tcp 127.0.0.1:10251: connection refused" 33 | } 34 | ] 35 | }, 36 | { 37 | "metadata": { 38 | "name": "etcd-0", 39 | "selfLink": "/api/v1/namespaces/componentstatuses/etcd-0", 40 | "creationTimestamp": null 41 | }, 42 | "conditions": [ 43 | { 44 | "type": "Healthy", 45 | "status": "True", 46 | "message": "{\"health\": \"true\"}", 47 | "error": "nil" 48 | } 49 | ] 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /test/json/config_map_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ConfigMapList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/configmaps", 6 | "resourceVersion": "665" 7 | }, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /test/json/core_api_resource_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "APIResourceList", 3 | "groupVersion": "v1", 4 | "resources": [ 5 | { 6 | "name": "bindings", 7 | "namespaced": true, 8 | "kind": "Binding" 9 | }, 10 | { 11 | "name": "componentstatuses", 12 | "namespaced": false, 13 | "kind": "ComponentStatus" 14 | }, 15 | { 16 | "name": "configmaps", 17 | "namespaced": true, 18 | "kind": "ConfigMap" 19 | }, 20 | { 21 | "name": "endpoints", 22 | "namespaced": true, 23 | "kind": "Endpoints" 24 | }, 25 | { 26 | "name": "events", 27 | "namespaced": true, 28 | "kind": "Event" 29 | }, 30 | { 31 | "name": "limitranges", 32 | "namespaced": true, 33 | "kind": "LimitRange" 34 | }, 35 | { 36 | "name": "namespaces", 37 | "namespaced": false, 38 | "kind": "Namespace" 39 | }, 40 | { 41 | "name": "namespaces/finalize", 42 | "namespaced": false, 43 | "kind": "Namespace" 44 | }, 45 | { 46 | "name": "namespaces/status", 47 | "namespaced": false, 48 | "kind": "Namespace" 49 | }, 50 | { 51 | "name": "nodes", 52 | "namespaced": false, 53 | "kind": "Node" 54 | }, 55 | { 56 | "name": "nodes/proxy", 57 | "namespaced": false, 58 | "kind": "Node" 59 | }, 60 | { 61 | "name": "nodes/status", 62 | "namespaced": false, 63 | "kind": "Node" 64 | }, 65 | { 66 | "name": "persistentvolumeclaims", 67 | "namespaced": true, 68 | "kind": "PersistentVolumeClaim" 69 | }, 70 | { 71 | "name": "persistentvolumeclaims/status", 72 | "namespaced": true, 73 | "kind": "PersistentVolumeClaim" 74 | }, 75 | { 76 | "name": "persistentvolumes", 77 | "namespaced": false, 78 | "kind": "PersistentVolume" 79 | }, 80 | { 81 | "name": "persistentvolumes/status", 82 | "namespaced": false, 83 | "kind": "PersistentVolume" 84 | }, 85 | { 86 | "name": "pods", 87 | "namespaced": true, 88 | "kind": "Pod" 89 | }, 90 | { 91 | "name": "pods/attach", 92 | "namespaced": true, 93 | "kind": "Pod" 94 | }, 95 | { 96 | "name": "pods/binding", 97 | "namespaced": true, 98 | "kind": "Binding" 99 | }, 100 | { 101 | "name": "pods/exec", 102 | "namespaced": true, 103 | "kind": "Pod" 104 | }, 105 | { 106 | "name": "pods/log", 107 | "namespaced": true, 108 | "kind": "Pod" 109 | }, 110 | { 111 | "name": "pods/portforward", 112 | "namespaced": true, 113 | "kind": "Pod" 114 | }, 115 | { 116 | "name": "pods/proxy", 117 | "namespaced": true, 118 | "kind": "Pod" 119 | }, 120 | { 121 | "name": "pods/status", 122 | "namespaced": true, 123 | "kind": "Pod" 124 | }, 125 | { 126 | "name": "podtemplates", 127 | "namespaced": true, 128 | "kind": "PodTemplate" 129 | }, 130 | { 131 | "name": "replicationcontrollers", 132 | "namespaced": true, 133 | "kind": "ReplicationController" 134 | }, 135 | { 136 | "name": "replicationcontrollers/scale", 137 | "namespaced": true, 138 | "kind": "Scale" 139 | }, 140 | { 141 | "name": "replicationcontrollers/status", 142 | "namespaced": true, 143 | "kind": "ReplicationController" 144 | }, 145 | { 146 | "name": "resourcequotas", 147 | "namespaced": true, 148 | "kind": "ResourceQuota" 149 | }, 150 | { 151 | "name": "resourcequotas/status", 152 | "namespaced": true, 153 | "kind": "ResourceQuota" 154 | }, 155 | { 156 | "name": "secrets", 157 | "namespaced": true, 158 | "kind": "Secret" 159 | }, 160 | { 161 | "name": "serviceaccounts", 162 | "namespaced": true, 163 | "kind": "ServiceAccount" 164 | }, 165 | { 166 | "name": "services", 167 | "namespaced": true, 168 | "kind": "Service" 169 | }, 170 | { 171 | "name": "services/proxy", 172 | "namespaced": true, 173 | "kind": "Service" 174 | }, 175 | { 176 | "name": "services/status", 177 | "namespaced": true, 178 | "kind": "Service" 179 | } 180 | ] 181 | } 182 | -------------------------------------------------------------------------------- /test/json/core_api_resource_list_without_kind.json: -------------------------------------------------------------------------------- 1 | { 2 | "groupVersion": "v1", 3 | "resources": [ 4 | { 5 | "name": "bindings", 6 | "namespaced": true 7 | }, 8 | { 9 | "name": "componentstatuses", 10 | "namespaced": true 11 | }, 12 | { 13 | "name": "endpoints", 14 | "namespaced": true 15 | }, 16 | { 17 | "name": "events", 18 | "namespaced": true 19 | }, 20 | { 21 | "name": "limitranges", 22 | "namespaced": true 23 | }, 24 | { 25 | "name": "namespaces", 26 | "namespaced": false 27 | }, 28 | { 29 | "name": "namespaces/finalize", 30 | "namespaced": false 31 | }, 32 | { 33 | "name": "namespaces/status", 34 | "namespaced": false 35 | }, 36 | { 37 | "name": "nodes", 38 | "namespaced": false 39 | }, 40 | { 41 | "name": "nodes/status", 42 | "namespaced": false 43 | }, 44 | { 45 | "name": "persistentvolumeclaims", 46 | "namespaced": true 47 | }, 48 | { 49 | "name": "persistentvolumeclaims/status", 50 | "namespaced": true 51 | }, 52 | { 53 | "name": "persistentvolumes", 54 | "namespaced": false 55 | }, 56 | { 57 | "name": "persistentvolumes/status", 58 | "namespaced": false 59 | }, 60 | { 61 | "name": "pods", 62 | "namespaced": true 63 | }, 64 | { 65 | "name": "pods/attach", 66 | "namespaced": true 67 | }, 68 | { 69 | "name": "pods/binding", 70 | "namespaced": true 71 | }, 72 | { 73 | "name": "pods/exec", 74 | "namespaced": true 75 | }, 76 | { 77 | "name": "pods/log", 78 | "namespaced": true 79 | }, 80 | { 81 | "name": "pods/portforward", 82 | "namespaced": true 83 | }, 84 | { 85 | "name": "pods/proxy", 86 | "namespaced": true 87 | }, 88 | { 89 | "name": "pods/status", 90 | "namespaced": true 91 | }, 92 | { 93 | "name": "podtemplates", 94 | "namespaced": true 95 | }, 96 | { 97 | "name": "replicationcontrollers", 98 | "namespaced": true 99 | }, 100 | { 101 | "name": "replicationcontrollers/status", 102 | "namespaced": true 103 | }, 104 | { 105 | "name": "resourcequotas", 106 | "namespaced": true 107 | }, 108 | { 109 | "name": "resourcequotas/status", 110 | "namespaced": true 111 | }, 112 | { 113 | "name": "secrets", 114 | "namespaced": true 115 | }, 116 | { 117 | "name": "securitycontextconstraints", 118 | "namespaced": false 119 | }, 120 | { 121 | "name": "serviceaccounts", 122 | "namespaced": true 123 | }, 124 | { 125 | "name": "services", 126 | "namespaced": true 127 | } 128 | ] 129 | } 130 | -------------------------------------------------------------------------------- /test/json/core_oapi_resource_list_without_kind.json: -------------------------------------------------------------------------------- 1 | { 2 | "groupVersion": "v1", 3 | "resources": [ 4 | { 5 | "name": "buildconfigs", 6 | "namespaced": true 7 | }, 8 | { 9 | "name": "buildconfigs/instantiate", 10 | "namespaced": true 11 | }, 12 | { 13 | "name": "buildconfigs/instantiatebinary", 14 | "namespaced": true 15 | }, 16 | { 17 | "name": "buildconfigs/webhooks", 18 | "namespaced": true 19 | }, 20 | { 21 | "name": "builds", 22 | "namespaced": true 23 | }, 24 | { 25 | "name": "builds/clone", 26 | "namespaced": true 27 | }, 28 | { 29 | "name": "builds/details", 30 | "namespaced": true 31 | }, 32 | { 33 | "name": "builds/log", 34 | "namespaced": true 35 | }, 36 | { 37 | "name": "clusternetworks", 38 | "namespaced": false 39 | }, 40 | { 41 | "name": "clusterpolicies", 42 | "namespaced": false 43 | }, 44 | { 45 | "name": "clusterpolicybindings", 46 | "namespaced": false 47 | }, 48 | { 49 | "name": "clusterrolebindings", 50 | "namespaced": false 51 | }, 52 | { 53 | "name": "clusterroles", 54 | "namespaced": false 55 | }, 56 | { 57 | "name": "deploymentconfigrollbacks", 58 | "namespaced": true 59 | }, 60 | { 61 | "name": "deploymentconfigs", 62 | "namespaced": true 63 | }, 64 | { 65 | "name": "deploymentconfigs/log", 66 | "namespaced": true 67 | }, 68 | { 69 | "name": "deploymentconfigs/scale", 70 | "namespaced": true 71 | }, 72 | { 73 | "name": "generatedeploymentconfigs", 74 | "namespaced": true 75 | }, 76 | { 77 | "name": "groups", 78 | "namespaced": false 79 | }, 80 | { 81 | "name": "hostsubnets", 82 | "namespaced": false 83 | }, 84 | { 85 | "name": "identities", 86 | "namespaced": false 87 | }, 88 | { 89 | "name": "images", 90 | "namespaced": false 91 | }, 92 | { 93 | "name": "imagestreamimages", 94 | "namespaced": true 95 | }, 96 | { 97 | "name": "imagestreammappings", 98 | "namespaced": true 99 | }, 100 | { 101 | "name": "imagestreams", 102 | "namespaced": true 103 | }, 104 | { 105 | "name": "imagestreams/status", 106 | "namespaced": true 107 | }, 108 | { 109 | "name": "imagestreamtags", 110 | "namespaced": true 111 | }, 112 | { 113 | "name": "localresourceaccessreviews", 114 | "namespaced": true 115 | }, 116 | { 117 | "name": "localsubjectaccessreviews", 118 | "namespaced": true 119 | }, 120 | { 121 | "name": "netnamespaces", 122 | "namespaced": false 123 | }, 124 | { 125 | "name": "oauthaccesstokens", 126 | "namespaced": false 127 | }, 128 | { 129 | "name": "oauthauthorizetokens", 130 | "namespaced": false 131 | }, 132 | { 133 | "name": "oauthclientauthorizations", 134 | "namespaced": false 135 | }, 136 | { 137 | "name": "oauthclients", 138 | "namespaced": false 139 | }, 140 | { 141 | "name": "policies", 142 | "namespaced": true 143 | }, 144 | { 145 | "name": "policybindings", 146 | "namespaced": true 147 | }, 148 | { 149 | "name": "processedtemplates", 150 | "namespaced": true 151 | }, 152 | { 153 | "name": "projectrequests", 154 | "namespaced": false 155 | }, 156 | { 157 | "name": "projects", 158 | "namespaced": false 159 | }, 160 | { 161 | "name": "resourceaccessreviews", 162 | "namespaced": true 163 | }, 164 | { 165 | "name": "rolebindings", 166 | "namespaced": true 167 | }, 168 | { 169 | "name": "roles", 170 | "namespaced": true 171 | }, 172 | { 173 | "name": "routes", 174 | "namespaced": true 175 | }, 176 | { 177 | "name": "routes/status", 178 | "namespaced": true 179 | }, 180 | { 181 | "name": "subjectaccessreviews", 182 | "namespaced": true 183 | }, 184 | { 185 | "name": "templates", 186 | "namespaced": true 187 | }, 188 | { 189 | "name": "useridentitymappings", 190 | "namespaced": false 191 | }, 192 | { 193 | "name": "users", 194 | "namespaced": false 195 | } 196 | ] 197 | } 198 | -------------------------------------------------------------------------------- /test/json/created_endpoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Endpoints", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "myendpoint", 6 | "namespace": "default", 7 | "selfLink": "/api/v1/namespaces/default/endpoints/myendpoint", 8 | "uid": "59d05b48-dadb-11e5-937e-18037327aaeb", 9 | "resourceVersion": "393", 10 | "creationTimestamp": "2016-02-24T09:45:34Z" 11 | }, 12 | "subsets": [ 13 | { 14 | "addresses": [ 15 | { 16 | "ip": "172.17.0.25" 17 | } 18 | ], 19 | "ports": [ 20 | { 21 | "name": "https", 22 | "port": 6443, 23 | "protocol": "TCP" 24 | } 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /test/json/created_namespace.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Namespace", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "development", 6 | "selfLink": "/api/v1/namespaces/development", 7 | "uid": "13d820d6-df5b-11e4-bd42-f8b156af4ae1", 8 | "resourceVersion": "2533", 9 | "creationTimestamp": "2015-04-10T08:24:59Z" 10 | }, 11 | "spec": { 12 | "finalizers": [ 13 | "kubernetes" 14 | ] 15 | }, 16 | "status": { 17 | "phase": "Active" 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /test/json/created_secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Secret", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "test-secret", 6 | "namespace": "dev", 7 | "selfLink": "/api/v1/namespaces/dev/secrets/test-secret", 8 | "uid": "4e38a198-2bcb-11e5-a483-0e840567604d", 9 | "resourceVersion": "245569", 10 | "creationTimestamp": "2015-07-16T14:59:49Z" 11 | }, 12 | "data": { 13 | "super-secret": "Y2F0J3MgYXJlIGF3ZXNvbWUK" 14 | }, 15 | "type": "Opaque" 16 | } 17 | -------------------------------------------------------------------------------- /test/json/created_security_context_constraint.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowHostDirVolumePlugin": false, 3 | "allowHostIPC": false, 4 | "allowHostNetwork": false, 5 | "allowHostPID": false, 6 | "allowHostPorts": false, 7 | "allowPrivilegedContainer": false, 8 | "allowedCapabilities": null, 9 | "allowedFlexVolumes": null, 10 | "apiVersion": "security.openshift.io/v1", 11 | "defaultAddCapabilities": null, 12 | "fsGroup": { 13 | "type": "RunAsAny" 14 | }, 15 | "groups": [], 16 | "kind": "SecurityContextConstraints", 17 | "metadata": { 18 | "creationTimestamp": "2018-11-23T10:01:42Z", 19 | "name": "teleportation", 20 | "resourceVersion": "5274", 21 | "selfLink": "/apis/security.openshift.io/v1/securitycontextconstraints/teleportation", 22 | "uid": "c6e8e2ec-ef06-11e8-b4c0-68f728fac3ab" 23 | }, 24 | "priority": null, 25 | "readOnlyRootFilesystem": false, 26 | "requiredDropCapabilities": null, 27 | "runAsUser": { 28 | "type": "MustRunAs" 29 | }, 30 | "seLinuxContext": { 31 | "type": "MustRunAs" 32 | }, 33 | "supplementalGroups": { 34 | "type": "RunAsAny" 35 | }, 36 | "users": [], 37 | "volumes": [ 38 | "awsElasticBlockStore", 39 | "azureDisk", 40 | "azureFile", 41 | "cephFS", 42 | "cinder", 43 | "configMap", 44 | "downwardAPI", 45 | "emptyDir", 46 | "fc", 47 | "flexVolume", 48 | "flocker", 49 | "gcePersistentDisk", 50 | "gitRepo", 51 | "glusterfs", 52 | "iscsi", 53 | "nfs", 54 | "persistentVolumeClaim", 55 | "photonPersistentDisk", 56 | "portworxVolume", 57 | "projected", 58 | "quobyte", 59 | "rbd", 60 | "scaleIO", 61 | "secret", 62 | "storageOS", 63 | "vsphere" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /test/json/created_service.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Service", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "guestbook", 6 | "namespace": "staging", 7 | "selfLink": "/api/v1/namespaces/staging/services/guestbook", 8 | "uid": "29885239-df58-11e4-bd42-f8b156af4ae1", 9 | "resourceVersion": "1908", 10 | "creationTimestamp": "2015-04-10T08:04:07Z", 11 | "labels": { 12 | "name": "guestbook" 13 | } 14 | }, 15 | "spec": { 16 | "ports": [ 17 | { 18 | "name": "", 19 | "protocol": "TCP", 20 | "port": 3000, 21 | "targetPort": "http-server" 22 | } 23 | ], 24 | "selector": { 25 | "name": "guestbook" 26 | }, 27 | "clusterIP": "10.0.0.99", 28 | "sessionAffinity": "None" 29 | }, 30 | "status": {} 31 | } -------------------------------------------------------------------------------- /test/json/empty_pod_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PodList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/pods", 6 | "resourceVersion": "565" 7 | }, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /test/json/endpoint_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "EndpointsList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/namespaces/endpoints", 6 | "resourceVersion": "39" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "example", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/namespaces/default/endpoints/example", 14 | "uid": "db467530-b6aa-11e4-974a-525400c903c1", 15 | "resourceVersion": "38", 16 | "creationTimestamp": "2015-02-17T08:42:46-05:00" 17 | }, 18 | "endpoints": [ 19 | "172.17.0.63:80", 20 | "172.17.0.64:80" 21 | ] 22 | }, 23 | { 24 | "metadata": { 25 | "name": "kubernetes", 26 | "namespace": "default", 27 | "selfLink": "/api/v1/namespaces/default/endpoints/kubernetes", 28 | "resourceVersion": "8", 29 | "creationTimestamp": null 30 | }, 31 | "endpoints": [ 32 | "192.168.122.4:6443" 33 | ] 34 | }, 35 | { 36 | "metadata": { 37 | "name": "kubernetes-ro", 38 | "namespace": "default", 39 | "selfLink": "/api/v1/namespaces/default/endpoints/kubernetes-ro", 40 | "resourceVersion": "7", 41 | "creationTimestamp": null 42 | }, 43 | "endpoints": [ 44 | "192.168.122.4:7080" 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /test/json/entity_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ServiceList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/services", 6 | "resourceVersion": "59" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "kubernetes", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/services/kubernetes?namespace=default", 14 | "uid": "016e9dcd-ce39-11e4-ac24-3c970e4a436a", 15 | "resourceVersion": "6", 16 | "creationTimestamp": "2015-03-19T15:08:16+02:00", 17 | "labels": { 18 | "component": "apiserver", 19 | "provider": "kubernetes" 20 | } 21 | }, 22 | "spec": { 23 | "port": 443, 24 | "protocol": "TCP", 25 | "selector": null, 26 | "clusterIP": "10.0.0.2", 27 | "containerPort": 0, 28 | "sessionAffinity": "None" 29 | }, 30 | "status": {} 31 | }, 32 | { 33 | "metadata": { 34 | "name": "kubernetes-ro", 35 | "namespace": "default", 36 | "selfLink": "/api/v1/services/kubernetes-ro?namespace=default", 37 | "uid": "015b78bf-ce39-11e4-ac24-3c970e4a436a", 38 | "resourceVersion": "5", 39 | "creationTimestamp": "2015-03-19T15:08:15+02:00", 40 | "labels": { 41 | "component": "apiserver", 42 | "provider": "kubernetes" 43 | } 44 | }, 45 | "spec": { 46 | "port": 80, 47 | "protocol": "TCP", 48 | "selector": null, 49 | "clusterIP": "10.0.0.1", 50 | "containerPort": 0, 51 | "sessionAffinity": "None" 52 | }, 53 | "status": {} 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /test/json/event_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "EventList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/events", 6 | "resourceVersion": "152" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "php.13c130f78da4641e", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/events/php.13c130f78da4641e?namespace=default", 14 | "uid": "f3a454d2-b03a-11e4-89e4-525400c903c1", 15 | "resourceVersion": "178", 16 | "creationTimestamp": "2015-02-09T04:06:37-05:00" 17 | }, 18 | "involvedObject": { 19 | "kind": "BoundPod", 20 | "namespace": "default", 21 | "name": "php", 22 | "uid": "64273d20-b03a-11e4-89e4-525400c903c1", 23 | "apiVersion": "v1beta1", 24 | "fieldPath": "spec.containers{nginx}" 25 | }, 26 | "reason": "created", 27 | "message": "Created with docker id 9ba2a714411d2d0dd1e826b2fe5c3222b5cbfd9dd9133c841585cbb96b8c2c0f", 28 | "source": { 29 | "component": "kubelet", 30 | "host": "127.0.0.1" 31 | }, 32 | "timestamp": "2015-02-09T04:06:37-05:00" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/json/extensions_v1beta1_api_resource_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "APIResourceList", 3 | "groupVersion": "extensions/v1beta1", 4 | "resources": [ 5 | { 6 | "name": "daemonsets", 7 | "singularName": "", 8 | "namespaced": true, 9 | "kind": "DaemonSet", 10 | "verbs": [ 11 | "create", 12 | "delete", 13 | "deletecollection", 14 | "get", 15 | "list", 16 | "patch", 17 | "update", 18 | "watch" 19 | ], 20 | "shortNames": [ 21 | "ds" 22 | ] 23 | }, 24 | { 25 | "name": "daemonsets/status", 26 | "singularName": "", 27 | "namespaced": true, 28 | "kind": "DaemonSet", 29 | "verbs": [ 30 | "get", 31 | "patch", 32 | "update" 33 | ] 34 | }, 35 | { 36 | "name": "deployments", 37 | "singularName": "", 38 | "namespaced": true, 39 | "kind": "Deployment", 40 | "verbs": [ 41 | "create", 42 | "delete", 43 | "deletecollection", 44 | "get", 45 | "list", 46 | "patch", 47 | "update", 48 | "watch" 49 | ], 50 | "shortNames": [ 51 | "deploy" 52 | ] 53 | }, 54 | { 55 | "name": "deployments/rollback", 56 | "singularName": "", 57 | "namespaced": true, 58 | "kind": "DeploymentRollback", 59 | "verbs": [ 60 | "create" 61 | ] 62 | }, 63 | { 64 | "name": "deployments/scale", 65 | "singularName": "", 66 | "namespaced": true, 67 | "group": "extensions", 68 | "version": "v1beta1", 69 | "kind": "Scale", 70 | "verbs": [ 71 | "get", 72 | "patch", 73 | "update" 74 | ] 75 | }, 76 | { 77 | "name": "deployments/status", 78 | "singularName": "", 79 | "namespaced": true, 80 | "kind": "Deployment", 81 | "verbs": [ 82 | "get", 83 | "patch", 84 | "update" 85 | ] 86 | }, 87 | { 88 | "name": "ingresses", 89 | "singularName": "", 90 | "namespaced": true, 91 | "kind": "Ingress", 92 | "verbs": [ 93 | "create", 94 | "delete", 95 | "deletecollection", 96 | "get", 97 | "list", 98 | "patch", 99 | "update", 100 | "watch" 101 | ], 102 | "shortNames": [ 103 | "ing" 104 | ] 105 | }, 106 | { 107 | "name": "ingresses/status", 108 | "singularName": "", 109 | "namespaced": true, 110 | "kind": "Ingress", 111 | "verbs": [ 112 | "get", 113 | "patch", 114 | "update" 115 | ] 116 | }, 117 | { 118 | "name": "networkpolicies", 119 | "singularName": "", 120 | "namespaced": true, 121 | "kind": "NetworkPolicy", 122 | "verbs": [ 123 | "create", 124 | "delete", 125 | "deletecollection", 126 | "get", 127 | "list", 128 | "patch", 129 | "update", 130 | "watch" 131 | ], 132 | "shortNames": [ 133 | "netpol" 134 | ] 135 | }, 136 | { 137 | "name": "podsecuritypolicies", 138 | "singularName": "", 139 | "namespaced": false, 140 | "kind": "PodSecurityPolicy", 141 | "verbs": [ 142 | "create", 143 | "delete", 144 | "deletecollection", 145 | "get", 146 | "list", 147 | "patch", 148 | "update", 149 | "watch" 150 | ], 151 | "shortNames": [ 152 | "psp" 153 | ] 154 | }, 155 | { 156 | "name": "replicasets", 157 | "singularName": "", 158 | "namespaced": true, 159 | "kind": "ReplicaSet", 160 | "verbs": [ 161 | "create", 162 | "delete", 163 | "deletecollection", 164 | "get", 165 | "list", 166 | "patch", 167 | "update", 168 | "watch" 169 | ], 170 | "shortNames": [ 171 | "rs" 172 | ] 173 | }, 174 | { 175 | "name": "replicasets/scale", 176 | "singularName": "", 177 | "namespaced": true, 178 | "group": "extensions", 179 | "version": "v1beta1", 180 | "kind": "Scale", 181 | "verbs": [ 182 | "get", 183 | "patch", 184 | "update" 185 | ] 186 | }, 187 | { 188 | "name": "replicasets/status", 189 | "singularName": "", 190 | "namespaced": true, 191 | "kind": "ReplicaSet", 192 | "verbs": [ 193 | "get", 194 | "patch", 195 | "update" 196 | ] 197 | }, 198 | { 199 | "name": "replicationcontrollers", 200 | "singularName": "", 201 | "namespaced": true, 202 | "kind": "ReplicationControllerDummy", 203 | "verbs": [] 204 | }, 205 | { 206 | "name": "replicationcontrollers/scale", 207 | "singularName": "", 208 | "namespaced": true, 209 | "kind": "Scale", 210 | "verbs": [ 211 | "get", 212 | "patch", 213 | "update" 214 | ] 215 | } 216 | ] 217 | } -------------------------------------------------------------------------------- /test/json/limit_range.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "LimitRange", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "limits", 6 | "namespace": "quota-example", 7 | "selfLink": "/api/v1/namespaces/quota-example/limitranges/limits", 8 | "uid": "7a76a44c-3e9d-11e5-8214-0aaeec44370e", 9 | "resourceVersion": "103384", 10 | "creationTimestamp": "2015-08-09T13:49:39Z" 11 | }, 12 | "spec": { 13 | "limits": [ 14 | { 15 | "type": "Container", 16 | "default": { 17 | "cpu": "100m", 18 | "memory": "512Mi" 19 | } 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /test/json/limit_range_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "LimitRangeList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/namespaces/quota-example/limitranges/", 6 | "resourceVersion": "103421" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "limits", 12 | "namespace": "quota-example", 13 | "selfLink": "/api/v1/namespaces/quota-example/limitranges/limits", 14 | "uid": "7a76a44c-3e9d-11e5-8214-0aaeec44370e", 15 | "resourceVersion": "103384", 16 | "creationTimestamp": "2015-08-09T13:49:39Z" 17 | }, 18 | "spec": { 19 | "limits": [ 20 | { 21 | "type": "Container", 22 | "default": { 23 | "cpu": "100m", 24 | "memory": "512Mi" 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /test/json/namespace.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Namespace", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "staging", 6 | "selfLink": "/api/v1/namespaces/staging", 7 | "uid": "e388bc10-c021-11e4-a514-3c970e4a436a", 8 | "resourceVersion": "1168", 9 | "creationTimestamp": "2015-03-01T16:47:31+02:00" 10 | }, 11 | "spec": {}, 12 | "status": {} 13 | } -------------------------------------------------------------------------------- /test/json/namespace_already_exists.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": {}, 3 | "status": "Failure", 4 | "message": "namespaces \"default\" already exists", 5 | "reason": "AlreadyExists", 6 | "details": { 7 | "name": "default", 8 | "kind": "namespaces" 9 | }, 10 | "code": 409 11 | } 12 | -------------------------------------------------------------------------------- /test/json/namespace_exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Status", 3 | "apiVersion": "v1", 4 | "metadata": {}, 5 | "status": "Failure", 6 | "message": "converting to : type names don't match (Pod, Namespace)", 7 | "code": 500 8 | } -------------------------------------------------------------------------------- /test/json/namespace_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "NamespaceList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/namespaces", 6 | "resourceVersion": "1707" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "default", 12 | "selfLink": "/api/v1/namespaces/default", 13 | "uid": "56c3eb7c-c009-11e4-a514-3c970e4a436a", 14 | "resourceVersion": "4", 15 | "creationTimestamp": "2015-03-01T13:51:47+02:00" 16 | }, 17 | "spec": {}, 18 | "status": {} 19 | }, 20 | { 21 | "metadata": { 22 | "name": "staging", 23 | "selfLink": "/api/v1/namespaces/staging", 24 | "uid": "e388bc10-c021-11e4-a514-3c970e4a436a", 25 | "resourceVersion": "1168", 26 | "creationTimestamp": "2015-03-01T16:47:31+02:00" 27 | }, 28 | "spec": {}, 29 | "status": {} 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /test/json/node.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Node", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "127.0.0.1", 6 | "selfLink": "/api/v1/nodes/127.0.0.1", 7 | "uid": "041143c5-ce39-11e4-ac24-3c970e4a436a", 8 | "resourceVersion": "1724", 9 | "creationTimestamp": "2015-03-19T15:08:20+02:00" 10 | }, 11 | "spec": { 12 | "capacity": { 13 | "cpu": "1", 14 | "memory": "3Gi" 15 | } 16 | }, 17 | "status": { 18 | "hostIP": "127.0.0.1", 19 | "conditions": [ 20 | { 21 | "kind": "Ready", 22 | "status": "None", 23 | "lastProbeTime": "2015-03-20T14:16:52+02:00", 24 | "lastTransitionTime": "2015-03-19T15:08:20+02:00", 25 | "reason": "Node health check failed: kubelet /healthz endpoint returns not ok" 26 | } 27 | ] 28 | } 29 | } -------------------------------------------------------------------------------- /test/json/node_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "NodeList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/nodes", 6 | "resourceVersion": "137" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "127.0.0.1", 12 | "selfLink": "/api/v1/nodes/127.0.0.1", 13 | "uid": "041143c5-ce39-11e4-ac24-3c970e4a436a", 14 | "resourceVersion": "137", 15 | "creationTimestamp": "2015-03-19T15:08:20+02:00" 16 | }, 17 | "spec": { 18 | "capacity": { 19 | "cpu": "1", 20 | "memory": "3Gi" 21 | } 22 | }, 23 | "status": { 24 | "hostIP": "127.0.0.1", 25 | "conditions": [ 26 | { 27 | "kind": "Ready", 28 | "status": "None", 29 | "lastProbeTime": "2015-03-19T15:29:33+02:00", 30 | "lastTransitionTime": "2015-03-19T15:08:20+02:00", 31 | "reason": "Node health check failed: kubelet /healthz endpoint returns not ok" 32 | } 33 | ] 34 | } 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /test/json/node_notice.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ADDED", 3 | "object": { 4 | "apiVersion": "v1", 5 | "kind": "Node", 6 | "metadata": { 7 | "annotations": { 8 | "volumes.kubernetes.io/controller-managed-attach-detach": "true" 9 | }, 10 | "creationTimestamp": "2017-12-11T12:00:13Z", 11 | "labels": { 12 | "beta.kubernetes.io/arch": "amd64", 13 | "beta.kubernetes.io/os": "linux", 14 | "kubernetes.io/hostname": "openshift.local", 15 | "openshift-infra": "apiserver" 16 | }, 17 | "name": "openshift.local", 18 | "resourceVersion": "367410", 19 | "selfLink": "/api/v1/nodes/openshift.local", 20 | "uid": "d88c7af6-de6a-11e7-8725-52540080f1d2" 21 | }, 22 | "spec": { 23 | "externalID": "openshift.local" 24 | }, 25 | "status": { 26 | "addresses": [ 27 | { 28 | "address": "192.168.122.40", 29 | "type": "InternalIP" 30 | }, 31 | { 32 | "address": "openshift.local", 33 | "type": "Hostname" 34 | } 35 | ], 36 | "allocatable": { 37 | "cpu": "2", 38 | "memory": "8072896Ki", 39 | "pods": "20" 40 | }, 41 | "capacity": { 42 | "cpu": "2", 43 | "memory": "8175296Ki", 44 | "pods": "20" 45 | }, 46 | "conditions": [ 47 | { 48 | "lastHeartbeatTime": "2017-12-15T00:36:13Z", 49 | "lastTransitionTime": "2017-12-11T12:00:13Z", 50 | "message": "kubelet has sufficient disk space available", 51 | "reason": "KubeletHasSufficientDisk", 52 | "status": "False", 53 | "type": "OutOfDisk" 54 | }, 55 | { 56 | "lastHeartbeatTime": "2017-12-15T00:36:13Z", 57 | "lastTransitionTime": "2017-12-11T12:00:13Z", 58 | "message": "kubelet has sufficient memory available", 59 | "reason": "KubeletHasSufficientMemory", 60 | "status": "False", 61 | "type": "MemoryPressure" 62 | }, 63 | { 64 | "lastHeartbeatTime": "2017-12-15T00:36:13Z", 65 | "lastTransitionTime": "2017-12-11T12:00:13Z", 66 | "message": "kubelet has no disk pressure", 67 | "reason": "KubeletHasNoDiskPressure", 68 | "status": "False", 69 | "type": "DiskPressure" 70 | }, 71 | { 72 | "lastHeartbeatTime": "2017-12-15T00:36:13Z", 73 | "lastTransitionTime": "2017-12-14T15:43:39Z", 74 | "message": "kubelet is posting ready status", 75 | "reason": "KubeletReady", 76 | "status": "True", 77 | "type": "Ready" 78 | } 79 | ], 80 | "daemonEndpoints": { 81 | "kubeletEndpoint": { 82 | "Port": 10250 83 | } 84 | }, 85 | "images": [ 86 | { 87 | "names": [ 88 | "docker.io/openshift/origin@sha256:908c6c9ccf0e0feefe2658899656c6e73d2854777fa340738fb903f0a40c328d", 89 | "docker.io/openshift/origin:latest" 90 | ], 91 | "sizeBytes": 1222636603 92 | }, 93 | { 94 | "names": [ 95 | "docker.io/openshift/origin-deployer@sha256:3d324bce1870047edc418041cefdec88e0a5bbb5b3b9f6fd35b43f14919a656c", 96 | "docker.io/openshift/origin-deployer:v3.7.0" 97 | ], 98 | "sizeBytes": 1098951248 99 | }, 100 | { 101 | "names": [ 102 | "docker.io/cockpit/kubernetes@sha256:a8e58cd5e6f5a4d12d1e2dfd339686b74f3c22586952ca7aa184dc254ab49714", 103 | "docker.io/cockpit/kubernetes:latest" 104 | ], 105 | "sizeBytes": 375926556 106 | }, 107 | { 108 | "names": [ 109 | "docker.io/cockpit/kubernetes@sha256:0745b3823efc57e03a5ef378614dfcb6c2b1e3964220bbf908fb3046a91cef70" 110 | ], 111 | "sizeBytes": 350062743 112 | }, 113 | { 114 | "names": [ 115 | "docker.io/openshift/origin-service-catalog@sha256:ef851e06276af96838a93320d0e4be51cc8de6e5afb2fb0efd4e56cec114b937" 116 | ], 117 | "sizeBytes": 284732029 118 | }, 119 | { 120 | "names": [ 121 | "docker.io/openshift/origin-service-catalog@sha256:8addfd742d92d8da819b091d6bda40edc45e88d1446ffd1ad658b6d21b3c36fd" 122 | ], 123 | "sizeBytes": 284731998 124 | }, 125 | { 126 | "names": [ 127 | "docker.io/openshift/origin-service-catalog@sha256:b3a737cc346b3cae85ef2f5d020b607781a1cac38fe70678cb78fee2c2a3bf8a" 128 | ], 129 | "sizeBytes": 284731943 130 | }, 131 | { 132 | "names": [ 133 | "docker.io/openshift/origin-service-catalog@sha256:957934537721da33362693d4f1590dc79dc5da7438799bf14d645165768e53ef", 134 | "docker.io/openshift/origin-service-catalog:latest" 135 | ], 136 | "sizeBytes": 283929631 137 | }, 138 | { 139 | "names": [ 140 | "docker.io/openshift/origin-pod@sha256:2c257d83a01607b229ef5e3dca09f52c3a2a2788c09dc33f0444ec4e572a9e1d", 141 | "docker.io/openshift/origin-pod:v3.7.0" 142 | ], 143 | "sizeBytes": 218423400 144 | } 145 | ], 146 | "nodeInfo": { 147 | "architecture": "amd64", 148 | "bootID": "75be791d-88a2-4f56-a588-c071a80bf7cf", 149 | "containerRuntimeVersion": "docker://1.12.6", 150 | "kernelVersion": "3.10.0-693.11.1.el7.x86_64", 151 | "kubeProxyVersion": "v1.7.6+a08f5eeb62", 152 | "kubeletVersion": "v1.7.6+a08f5eeb62", 153 | "machineID": "adf09ffc2de2624aa5ed335727c7400d", 154 | "operatingSystem": "linux", 155 | "osImage": "CentOS Linux 7 (Core)", 156 | "systemUUID": "FC9FF0AD-E22D-4A62-A5ED-335727C7400D" 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /test/json/persistent_volume.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolume", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "pv0001", 6 | "selfLink": "/api/v1/persistentvolumes/pv0001", 7 | "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe", 8 | "resourceVersion": "1585", 9 | "creationTimestamp": "2015-08-25T14:51:35Z", 10 | "labels": { 11 | "type": "local" 12 | } 13 | }, 14 | "spec": { 15 | "capacity": { 16 | "storage": "10Gi" 17 | }, 18 | "hostPath": { 19 | "path": "/tmp/data01" 20 | }, 21 | "accessModes": [ 22 | "ReadWriteOnce" 23 | ], 24 | "claimRef": { 25 | "kind": "PersistentVolumeClaim", 26 | "namespace": "default", 27 | "name": "myclaim-1", 28 | "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", 29 | "apiVersion": "v1", 30 | "resourceVersion": "1582" 31 | }, 32 | "persistentVolumeReclaimPolicy": "Retain" 33 | }, 34 | "status": { 35 | "phase": "Bound" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/json/persistent_volume_claim.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeClaim", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "myclaim-1", 6 | "namespace": "default", 7 | "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1", 8 | "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", 9 | "resourceVersion": "1584", 10 | "creationTimestamp": "2015-08-25T14:51:55Z" 11 | }, 12 | "spec": { 13 | "accessModes": [ 14 | "ReadWriteOnce" 15 | ], 16 | "resources": { 17 | "requests": { 18 | "storage": "3Gi" 19 | } 20 | }, 21 | "volumeName": "pv0001" 22 | }, 23 | "status": { 24 | "phase": "Bound", 25 | "accessModes": [ 26 | "ReadWriteOnce" 27 | ], 28 | "capacity": { 29 | "storage": "10Gi" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/json/persistent_volume_claim_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeClaimList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/persistentvolumeclaims", 6 | "resourceVersion": "3188" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "myclaim-1", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1", 14 | "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", 15 | "resourceVersion": "1584", 16 | "creationTimestamp": "2015-08-25T14:51:55Z" 17 | }, 18 | "spec": { 19 | "accessModes": [ 20 | "ReadWriteOnce" 21 | ], 22 | "resources": { 23 | "requests": { 24 | "storage": "3Gi" 25 | } 26 | }, 27 | "volumeName": "pv0001" 28 | }, 29 | "status": { 30 | "phase": "Bound", 31 | "accessModes": [ 32 | "ReadWriteOnce" 33 | ], 34 | "capacity": { 35 | "storage": "10Gi" 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /test/json/persistent_volume_claims_nil_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeClaimList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/persistentvolumeclaims", 6 | "resourceVersion": "1089012" 7 | } 8 | } -------------------------------------------------------------------------------- /test/json/persistent_volume_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PersistentVolumeList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/persistentvolumes", 6 | "resourceVersion": "2999" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "pv0001", 12 | "selfLink": "/api/v1/persistentvolumes/pv0001", 13 | "uid": "c83eece1-4b38-11e5-8d27-28d2447dcefe", 14 | "resourceVersion": "1585", 15 | "creationTimestamp": "2015-08-25T14:51:35Z", 16 | "labels": { 17 | "type": "local" 18 | } 19 | }, 20 | "spec": { 21 | "capacity": { 22 | "storage": "10Gi" 23 | }, 24 | "hostPath": { 25 | "path": "/tmp/data01" 26 | }, 27 | "accessModes": [ 28 | "ReadWriteOnce" 29 | ], 30 | "claimRef": { 31 | "kind": "PersistentVolumeClaim", 32 | "namespace": "default", 33 | "name": "myclaim-1", 34 | "uid": "d47384a3-4b38-11e5-8d27-28d2447dcefe", 35 | "apiVersion": "v1", 36 | "resourceVersion": "1582" 37 | }, 38 | "persistentVolumeReclaimPolicy": "Retain" 39 | }, 40 | "status": { 41 | "phase": "Bound" 42 | } 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /test/json/pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Pod", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "redis-master3", 6 | "namespace": "default", 7 | "selfLink": "/api/v1/pods/redis-master3?namespace=default", 8 | "uid": "a344023f-a23c-11e4-a36b-3c970e4a436a", 9 | "resourceVersion": "9", 10 | "creationTimestamp": "2015-01-22T15:43:24+02:00", 11 | "labels": { 12 | "name": "redis-master" 13 | } 14 | }, 15 | "spec": { 16 | "volumes": null, 17 | "containers": [ 18 | { 19 | "name": "master", 20 | "image": "dockerfile/redis", 21 | "ports": [ 22 | { 23 | "hostPort": 6379, 24 | "containerPort": 6379, 25 | "protocol": "TCP" 26 | } 27 | ], 28 | "memory": "0", 29 | "cpu": "100m", 30 | "imagePullPolicy": "" 31 | }, 32 | { 33 | "name": "php-redis", 34 | "image": "kubernetes/example-guestbook-php-redis", 35 | "ports": [ 36 | { 37 | "hostPort": 8000, 38 | "containerPort": 80, 39 | "protocol": "TCP" 40 | } 41 | ], 42 | "memory": "50000000", 43 | "cpu": "100m", 44 | "imagePullPolicy": "" 45 | } 46 | ], 47 | "restartPolicy": { 48 | "always": { 49 | 50 | } 51 | }, 52 | "dnsPolicy": "ClusterFirst" 53 | }, 54 | "status": { 55 | "phase": "Running", 56 | "host": "127.0.0.1", 57 | "podIP": "172.17.0.2", 58 | "info": { 59 | "master": { 60 | "state": { 61 | "running": { 62 | "startedAt": "2015-01-22T13:43:29Z" 63 | } 64 | }, 65 | "restartCount": 0, 66 | "containerID": "docker://87458d9a12f9dc9a01b52c1eee5f09cf48939380271c0eaf31af298ce67b125e", 67 | "image": "dockerfile/redis" 68 | }, 69 | "net": { 70 | "state": { 71 | "running": { 72 | "startedAt": "2015-01-22T13:43:27Z" 73 | } 74 | }, 75 | "restartCount": 0, 76 | "containerID": "docker://3bb5ced1f831322d370f70b58137e1dd41216c2960b7a99394542b5230cbd259", 77 | "podIP": "172.17.0.2", 78 | "image": "kubernetes/pause:latest" 79 | }, 80 | "php-redis": { 81 | "state": { 82 | "running": { 83 | "startedAt": "2015-01-22T13:43:31Z" 84 | } 85 | }, 86 | "restartCount": 0, 87 | "containerID": "docker://5f08685c0a7a5c974d438a52c6560d72bb0aae7e805d2a34302b9b460f1297c7", 88 | "image": "kubernetes/example-guestbook-php-redis" 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/json/pod_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PodList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/pods", 6 | "resourceVersion": "1315" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "redis-master3", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/pods/redis-master3?namespace=default", 14 | "uid": "1da148b4-cef5-11e4-ac24-3c970e4a436a", 15 | "resourceVersion": "1301", 16 | "creationTimestamp": "2015-03-20T13:34:48+02:00", 17 | "labels": { 18 | "mylabel": "mylabelvalue", 19 | "role": "pod" 20 | } 21 | }, 22 | "spec": { 23 | "volumes": null, 24 | "containers": [ 25 | { 26 | "name": "master", 27 | "image": "dockerfile/redis", 28 | "ports": [ 29 | { 30 | "hostPort": 6379, 31 | "containerPort": 6379, 32 | "protocol": "TCP" 33 | } 34 | ], 35 | "resources": { 36 | "limits": { 37 | "cpu": "100m" 38 | } 39 | }, 40 | "terminationMessagePath": "/dev/termination-log", 41 | "imagePullPolicy": "IfNotPresent", 42 | "securityContext": { 43 | "capabilities": {} 44 | } 45 | }, 46 | { 47 | "name": "php-redis", 48 | "image": "kubernetes/example-guestbook-php-redis", 49 | "ports": [ 50 | { 51 | "hostPort": 8000, 52 | "containerPort": 80, 53 | "protocol": "TCP" 54 | } 55 | ], 56 | "resources": { 57 | "limits": { 58 | "cpu": "100m", 59 | "memory": "50000000" 60 | } 61 | }, 62 | "terminationMessagePath": "/dev/termination-log", 63 | "imagePullPolicy": "IfNotPresent", 64 | "securityContext": { 65 | "capabilities": {} 66 | } 67 | } 68 | ], 69 | "restartPolicy": { 70 | "always": {} 71 | }, 72 | "dnsPolicy": "ClusterFirst" 73 | }, 74 | "status": { 75 | "phase": "Pending" 76 | } 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /test/json/pod_template_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "PodTemplateList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/podtemplates", 6 | "resourceVersion": "672" 7 | }, 8 | "items": [] 9 | } -------------------------------------------------------------------------------- /test/json/pods_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind":"PodList", 3 | "apiVersion":"v1", 4 | "metadata":{ 5 | "selfLink":"/api/v1/pods", 6 | "resourceVersion":"53226147" 7 | }, 8 | "items":[ 9 | { 10 | "metadata":{ 11 | "name":"topological-inventory-persister-9-hznds", 12 | "generateName":"topological-inventory-persister-9-", 13 | "namespace":"topological-inventory-ci", 14 | "selfLink":"/api/v1/namespaces/topological-inventory-ci/pods/topological-inventory-persister-9-hznds", 15 | "uid":"0c114dde-d865-11e8-ba7e-d094660d31fb", 16 | "resourceVersion":"51987342", 17 | "creationTimestamp":"2018-10-25T14:48:34Z", 18 | "labels":{ 19 | "name":"topological-inventory-persister" 20 | } 21 | }, 22 | "spec":{ 23 | "volumes":[ 24 | { 25 | "name":"default-token-5pdjl", 26 | "secret":{ 27 | "secretName":"default-token-5pdjl", 28 | "defaultMode":420 29 | } 30 | } 31 | ], 32 | "containers":[ 33 | { 34 | "name":"topological-inventory-persister", 35 | "image":"docker-registry.default.svc:5000/topological-inventory-ci/topological-inventory-persister@sha256:0f654ea09e749019cf3bcc4b8ee43b8dd813fcbf487843b917cf190213741927", 36 | "resources":{ 37 | } 38 | } 39 | ], 40 | "restartPolicy":"Always", 41 | "terminationGracePeriodSeconds":30, 42 | "dnsPolicy":"ClusterFirst", 43 | "nodeSelector":{ 44 | "node-role.kubernetes.io/compute":"true" 45 | }, 46 | "serviceAccountName":"default", 47 | "serviceAccount":"default", 48 | "nodeName":"dell-r430-20.example.com", 49 | "schedulerName":"default-scheduler" 50 | }, 51 | "status":{ 52 | "phase":"Running", 53 | "hostIP":"10.8.96.55", 54 | "podIP":"10.129.1.108", 55 | "startTime":"2018-10-25T14:48:34Z", 56 | "qosClass":"BestEffort" 57 | } 58 | }, 59 | { 60 | "metadata":{ 61 | "name":"topological-inventory-persister-9-vzr6h", 62 | "generateName":"topological-inventory-persister-9-", 63 | "namespace":"topological-inventory-ci", 64 | "selfLink":"/api/v1/namespaces/topological-inventory-ci/pods/topological-inventory-persister-9-vzr6h", 65 | "uid":"3065d8ce-d86a-11e8-ba7e-d094660d31fb", 66 | "resourceVersion":"51996115", 67 | "creationTimestamp":"2018-10-25T15:25:22Z", 68 | "labels":{ 69 | "name":"topological-inventory-persister" 70 | } 71 | }, 72 | "spec":{ 73 | "volumes":null, 74 | "containers":[ 75 | { 76 | "name":"topological-inventory-persister", 77 | "image":"docker-registry.default.svc:5000/topological-inventory-ci/topological-inventory-persister@sha256:0f654ea09e749019cf3bcc4b8ee43b8dd813fcbf487843b917cf190213741927", 78 | "resources":{ 79 | } 80 | } 81 | ], 82 | "restartPolicy":"Always", 83 | "terminationGracePeriodSeconds":30, 84 | "dnsPolicy":"ClusterFirst", 85 | "nodeSelector":{ 86 | "node-role.kubernetes.io/compute":"true" 87 | }, 88 | "serviceAccountName":"default", 89 | "serviceAccount":"default", 90 | "nodeName":"dell-r430-20.example.com", 91 | "schedulerName":"default-scheduler" 92 | }, 93 | "status":{ 94 | "phase":"Running", 95 | "hostIP":"10.8.96.55", 96 | "podIP":"10.129.1.168", 97 | "startTime":"2018-10-25T15:25:22Z", 98 | "qosClass":"BestEffort" 99 | } 100 | } 101 | ] 102 | } 103 | -------------------------------------------------------------------------------- /test/json/pods_410.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind":"Status", 3 | "apiVersion":"v1", 4 | "metadata":{}, 5 | "status":"Failure", 6 | "message":"The provided from parameter is too old to display a consistent list result. You must start a new list without the from.", 7 | "reason":"Expired", 8 | "code":410 9 | } 10 | -------------------------------------------------------------------------------- /test/json/processed_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Template", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "my-templtae", 6 | "namespace": "default", 7 | "selfLink": "/oapi/v1/namespaces/default/processedtemplates/my-templtae", 8 | "uid": "2240c61c-8f70-11e5-a806-001a4a231290", 9 | "resourceVersion": "1399", 10 | "creationTimestamp": "2015-11-20T10:19:07Z" 11 | }, 12 | "objects": [ 13 | { 14 | "apiVersion": "v1", 15 | "kind": "Service", 16 | "metadata": { 17 | "name": "test/my-service" 18 | } 19 | } 20 | ], 21 | "parameters": [ 22 | { 23 | "name": "NAME_PREFIX", 24 | "value": "test/" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test/json/replication_controller.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ReplicationController", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "guestbook-controller", 6 | "namespace": "default", 7 | "selfLink": "/api/v1/replicationcontrollers/guestbook-controller?namespace=default", 8 | "uid": "c71aa4c0-a240-11e4-a265-3c970e4a436a", 9 | "resourceVersion": "8", 10 | "creationTimestamp": "2015-01-22T16:13:02+02:00", 11 | "labels": { 12 | "name": "guestbook" 13 | } 14 | }, 15 | "spec": { 16 | "replicas": 3, 17 | "selector": { 18 | "name": "guestbook" 19 | }, 20 | "template": { 21 | "metadata": { 22 | "creationTimestamp": null, 23 | "labels": { 24 | "name": "guestbook" 25 | } 26 | }, 27 | "spec": { 28 | "volumes": null, 29 | "containers": [ 30 | { 31 | "name": "guestbook", 32 | "image": "kubernetes/guestbook", 33 | "ports": [ 34 | { 35 | "name": "http-server", 36 | "containerPort": 3000, 37 | "protocol": "TCP" 38 | } 39 | ], 40 | "memory": "0", 41 | "cpu": "0m", 42 | "imagePullPolicy": "" 43 | } 44 | ], 45 | "restartPolicy": { 46 | "always": { 47 | 48 | } 49 | }, 50 | "dnsPolicy": "ClusterFirst" 51 | } 52 | } 53 | }, 54 | "status": { 55 | "replicas": 3 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/json/replication_controller_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ReplicationControllerList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/namespaces/default/replicationcontrollers", 6 | "resourceVersion": "1636" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "redis-master-controller", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/namespaces/default/replicationcontrollers/redis-master-controller?namespace=default", 14 | "uid": "108eb547-cefa-11e4-ac24-3c970e4a436a", 15 | "resourceVersion": "1631", 16 | "creationTimestamp": "2015-03-20T14:10:14+02:00", 17 | "labels": { 18 | "name": "redis-master" 19 | } 20 | }, 21 | "spec": { 22 | "replicas": 1, 23 | "selector": { 24 | "name": "redis-master" 25 | }, 26 | "template": { 27 | "metadata": { 28 | "creationTimestamp": null, 29 | "labels": { 30 | "app": "redis", 31 | "name": "redis-master" 32 | } 33 | }, 34 | "spec": { 35 | "volumes": null, 36 | "containers": [ 37 | { 38 | "name": "redis-master", 39 | "image": "dockerfile/redis", 40 | "ports": [ 41 | { 42 | "containerPort": 6379, 43 | "protocol": "TCP" 44 | } 45 | ], 46 | "resources": {}, 47 | "terminationMessagePath": "/dev/termination-log", 48 | "imagePullPolicy": "IfNotPresent", 49 | "securityContext": { 50 | "capabilities": {} 51 | } 52 | } 53 | ], 54 | "restartPolicy": { 55 | "always": {} 56 | }, 57 | "dnsPolicy": "ClusterFirst" 58 | } 59 | } 60 | }, 61 | "status": { 62 | "replicas": 1 63 | } 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /test/json/resource_quota.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ResourceQuota", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "quota", 6 | "namespace": "quota-example", 7 | "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/quota", 8 | "uid": "ab9f24a4-3c43-11e5-8214-0aaeec44370e", 9 | "resourceVersion": "12919", 10 | "creationTimestamp": "2015-08-06T14:01:44Z" 11 | }, 12 | "spec": { 13 | "hard": { 14 | "cpu": "20", 15 | "memory": "1Gi", 16 | "persistentvolumeclaims": "10", 17 | "pods": "10", 18 | "replicationcontrollers": "20", 19 | "resourcequotas": "1", 20 | "secrets": "10", 21 | "services": "5" 22 | } 23 | }, 24 | "status": { 25 | "hard": { 26 | "cpu": "20", 27 | "memory": "1Gi", 28 | "persistentvolumeclaims": "10", 29 | "pods": "10", 30 | "replicationcontrollers": "20", 31 | "resourcequotas": "1", 32 | "secrets": "10", 33 | "services": "5" 34 | }, 35 | "used": { 36 | "cpu": "0", 37 | "memory": "0", 38 | "persistentvolumeclaims": "0", 39 | "pods": "0", 40 | "replicationcontrollers": "1", 41 | "resourcequotas": "1", 42 | "secrets": "9", 43 | "services": "0" 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /test/json/resource_quota_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ResourceQuotaList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/", 6 | "resourceVersion": "102452" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "quota", 12 | "namespace": "quota-example", 13 | "selfLink": "/api/v1/namespaces/quota-example/resourcequotas/quota", 14 | "uid": "ab9f24a4-3c43-11e5-8214-0aaeec44370e", 15 | "resourceVersion": "12919", 16 | "creationTimestamp": "2015-08-06T14:01:44Z" 17 | }, 18 | "spec": { 19 | "hard": { 20 | "cpu": "20", 21 | "memory": "1Gi", 22 | "persistentvolumeclaims": "10", 23 | "pods": "10", 24 | "replicationcontrollers": "20", 25 | "resourcequotas": "1", 26 | "secrets": "10", 27 | "services": "5" 28 | } 29 | }, 30 | "status": { 31 | "hard": { 32 | "cpu": "20", 33 | "memory": "1Gi", 34 | "persistentvolumeclaims": "10", 35 | "pods": "10", 36 | "replicationcontrollers": "20", 37 | "resourcequotas": "1", 38 | "secrets": "10", 39 | "services": "5" 40 | }, 41 | "used": { 42 | "cpu": "0", 43 | "memory": "0", 44 | "persistentvolumeclaims": "0", 45 | "pods": "0", 46 | "replicationcontrollers": "1", 47 | "resourcequotas": "1", 48 | "secrets": "9", 49 | "services": "0" 50 | } 51 | } 52 | } 53 | ] 54 | } -------------------------------------------------------------------------------- /test/json/secret_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "SecretList", 3 | "apiVersion":"v1", 4 | "metadata":{ 5 | "selfLink":"/api/v1/secrets", 6 | "resourceVersion":"256788" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name":"default-token-my2pi", 12 | "namespace":"default", 13 | "selfLink":"/api/v1/namespaces/default/secrets/default-token-my2pi", 14 | "uid":"07b60654-2a65-11e5-a483-0e840567604d", 15 | "resourceVersion":"11", 16 | "creationTimestamp":"2015-07-14T20:15:11Z", 17 | "annotations": { 18 | "kubernetes.io/service-account.name":"default", 19 | "kubernetes.io/service-account.uid":"07b350a0-2a65-11e5-a483-0e840567604d" 20 | } 21 | }, 22 | "data":{ 23 | "ca.crt":"Y2F0J3MgYXJlIGF3ZXNvbWUK", 24 | "token":"Y2F0J3MgYXJlIGF3ZXNvbWUK" 25 | }, 26 | "type":"kubernetes.io/service-account-token" 27 | }, 28 | { 29 | "metadata": { 30 | "name": "test-secret", 31 | "namespace": "dev", 32 | "selfLink": "/api/v1/namespaces/dev/secrets/test-secret", 33 | "uid": "4e38a198-2bcb-11e5-a483-0e840567604d", 34 | "resourceVersion": "245569", 35 | "creationTimestamp": "2015-07-16T14:59:49Z" 36 | }, 37 | "data": { 38 | "super-secret": "Y2F0J3MgYXJlIGF3ZXNvbWUK" 39 | }, 40 | "type": "Opaque" 41 | } 42 | ] 43 | } 44 | 45 | -------------------------------------------------------------------------------- /test/json/security.openshift.io_api_resource_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "APIResourceList", 3 | "apiVersion": "v1", 4 | "groupVersion": "security.openshift.io/v1", 5 | "resources": [ 6 | { 7 | "name": "podsecuritypolicyreviews", 8 | "singularName": "", 9 | "namespaced": true, 10 | "kind": "PodSecurityPolicyReview", 11 | "verbs": [ 12 | "create" 13 | ] 14 | }, 15 | { 16 | "name": "podsecuritypolicyselfsubjectreviews", 17 | "singularName": "", 18 | "namespaced": true, 19 | "kind": "PodSecurityPolicySelfSubjectReview", 20 | "verbs": [ 21 | "create" 22 | ] 23 | }, 24 | { 25 | "name": "podsecuritypolicysubjectreviews", 26 | "singularName": "", 27 | "namespaced": true, 28 | "kind": "PodSecurityPolicySubjectReview", 29 | "verbs": [ 30 | "create" 31 | ] 32 | }, 33 | { 34 | "name": "rangeallocations", 35 | "singularName": "", 36 | "namespaced": false, 37 | "kind": "RangeAllocation", 38 | "verbs": [ 39 | "create", 40 | "delete", 41 | "deletecollection", 42 | "get", 43 | "list", 44 | "patch", 45 | "update", 46 | "watch" 47 | ] 48 | }, 49 | { 50 | "name": "securitycontextconstraints", 51 | "singularName": "", 52 | "namespaced": false, 53 | "kind": "SecurityContextConstraints", 54 | "verbs": [ 55 | "create", 56 | "delete", 57 | "deletecollection", 58 | "get", 59 | "list", 60 | "patch", 61 | "update", 62 | "watch" 63 | ], 64 | "shortNames": [ 65 | "scc" 66 | ] 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /test/json/service.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "Service", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "redis-slave", 6 | "namespace": "development", 7 | "selfLink": "/api/v1/namespaces/development/services/redis-slave", 8 | "uid": "bdb80a8f-db93-11e4-b293-f8b156af4ae1", 9 | "resourceVersion": "2815", 10 | "creationTimestamp": "2015-04-05T13:00:31Z", 11 | "labels": { 12 | "name": "redis", 13 | "role": "slave" 14 | } 15 | }, 16 | "spec": { 17 | "ports": [ 18 | { 19 | "name": "", 20 | "protocol": "TCP", 21 | "port": 6379, 22 | "targetPort": "redis-server" 23 | } 24 | ], 25 | "selector": { 26 | "name": "redis", 27 | "role": "slave" 28 | }, 29 | "clusterIP": "10.0.0.140", 30 | "sessionAffinity": "None" 31 | }, 32 | "status": {} 33 | } -------------------------------------------------------------------------------- /test/json/service_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ServiceAccount", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "name": "default", 6 | "namespace": "default", 7 | "selfLink": "/api/v1/namespaces/default/serviceaccounts/default", 8 | "uid": "d3d773f4-6bf0-11e5-843a-525400f8b93e", 9 | "resourceVersion": "94", 10 | "creationTimestamp": "2015-10-06T06:09:39Z" 11 | }, 12 | "secrets": [ 13 | { 14 | "name": "default-token-6s23q" 15 | }, 16 | { 17 | "name": "default-dockercfg-62tf3" 18 | } 19 | ], 20 | "imagePullSecrets": [ 21 | { 22 | "name": "default-dockercfg-62tf3" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/json/service_account_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "List", 3 | "apiVersion": "v1", 4 | "metadata": {}, 5 | "items": [ 6 | { 7 | "kind": "ServiceAccount", 8 | "apiVersion": "v1", 9 | "metadata": { 10 | "name": "builder", 11 | "namespace": "default", 12 | "selfLink": "/api/v1/namespaces/default/serviceaccounts/builder", 13 | "uid": "d40655f6-6bf0-11e5-843a-525400f8b93e", 14 | "resourceVersion": "133", 15 | "creationTimestamp": "2015-10-06T06:09:39Z" 16 | }, 17 | "secrets": [ 18 | { 19 | "name": "builder-token-5v6z2" 20 | }, 21 | { 22 | "name": "builder-dockercfg-qe2re" 23 | } 24 | ], 25 | "imagePullSecrets": [ 26 | { 27 | "name": "builder-dockercfg-qe2re" 28 | } 29 | ] 30 | }, 31 | { 32 | "kind": "ServiceAccount", 33 | "apiVersion": "v1", 34 | "metadata": { 35 | "name": "default", 36 | "namespace": "default", 37 | "selfLink": "/api/v1/namespaces/default/serviceaccounts/default", 38 | "uid": "d3d773f4-6bf0-11e5-843a-525400f8b93e", 39 | "resourceVersion": "94", 40 | "creationTimestamp": "2015-10-06T06:09:39Z" 41 | }, 42 | "secrets": [ 43 | { 44 | "name": "default-token-6s23q" 45 | }, 46 | { 47 | "name": "default-dockercfg-62tf3" 48 | } 49 | ], 50 | "imagePullSecrets": [ 51 | { 52 | "name": "default-dockercfg-62tf3" 53 | } 54 | ] 55 | }, 56 | { 57 | "kind": "ServiceAccount", 58 | "apiVersion": "v1", 59 | "metadata": { 60 | "name": "deployer", 61 | "namespace": "default", 62 | "selfLink": "/api/v1/namespaces/default/serviceaccounts/deployer", 63 | "uid": "d41d385e-6bf0-11e5-843a-525400f8b93e", 64 | "resourceVersion": "137", 65 | "creationTimestamp": "2015-10-06T06:09:39Z" 66 | }, 67 | "secrets": [ 68 | { 69 | "name": "deployer-token-h3i57" 70 | }, 71 | { 72 | "name": "deployer-dockercfg-qgjjj" 73 | } 74 | ], 75 | "imagePullSecrets": [ 76 | { 77 | "name": "deployer-dockercfg-qgjjj" 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /test/json/service_illegal_json_404.json: -------------------------------------------------------------------------------- 1 | 404: Page Not Found -------------------------------------------------------------------------------- /test/json/service_json_patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : {}, 3 | "kind" : "Service", 4 | "apiVersion" : "v1", 5 | "spec" : { 6 | "ports" : [ 7 | { 8 | "targetPort" : 80, 9 | "nodePort" : 0, 10 | "port" : 80, 11 | "protocol" : "TCP" 12 | } 13 | ], 14 | "clusterIP" : "1.2.3.4", 15 | "type": "LoadBalancer" 16 | }, 17 | "metadata" : { 18 | "name" : "my-service", 19 | "creationTimestamp" : null, 20 | "namespace" : "development", 21 | "resourceVersion" : "2", 22 | "annotations" : { 23 | "key" : "value" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/json/service_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "ServiceList", 3 | "apiVersion": "v1", 4 | "metadata": { 5 | "selfLink": "/api/v1/services", 6 | "resourceVersion": "36727" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "kubernetes", 12 | "namespace": "default", 13 | "selfLink": "/api/v1/namespaces/default/services/kubernetes", 14 | "uid": "b6606490-db86-11e4-b293-f8b156af4ae1", 15 | "resourceVersion": "6", 16 | "creationTimestamp": "2015-04-05T11:27:15Z", 17 | "labels": { 18 | "component": "apiserver", 19 | "provider": "kubernetes" 20 | } 21 | }, 22 | "spec": { 23 | "ports": [ 24 | { 25 | "name": "", 26 | "protocol": "TCP", 27 | "port": 443, 28 | "targetPort": 443 29 | } 30 | ], 31 | "selector": null, 32 | "clusterIP": "10.0.0.2", 33 | "sessionAffinity": "None" 34 | }, 35 | "status": {} 36 | }, 37 | { 38 | "metadata": { 39 | "name": "kubernetes-ro", 40 | "namespace": "default", 41 | "selfLink": "/api/v1/namespaces/default/services/kubernetes-ro", 42 | "uid": "b6606694-db86-11e4-b293-f8b156af4ae1", 43 | "resourceVersion": "5", 44 | "creationTimestamp": "2015-04-05T11:27:15Z", 45 | "labels": { 46 | "component": "apiserver", 47 | "provider": "kubernetes" 48 | } 49 | }, 50 | "spec": { 51 | "ports": [ 52 | { 53 | "name": "", 54 | "protocol": "TCP", 55 | "port": 80, 56 | "targetPort": 80 57 | } 58 | ], 59 | "selector": null, 60 | "clusterIP": "10.0.0.1", 61 | "sessionAffinity": "None" 62 | }, 63 | "status": {} 64 | }, 65 | { 66 | "metadata": { 67 | "name": "redis-slave", 68 | "namespace": "development", 69 | "selfLink": "/api/v1/namespaces/development/services/redis-slave", 70 | "uid": "bdb80a8f-db93-11e4-b293-f8b156af4ae1", 71 | "resourceVersion": "2815", 72 | "creationTimestamp": "2015-04-05T13:00:31Z", 73 | "labels": { 74 | "name": "redis", 75 | "role": "slave" 76 | } 77 | }, 78 | "spec": { 79 | "ports": [ 80 | { 81 | "name": "", 82 | "protocol": "TCP", 83 | "port": 6379, 84 | "targetPort": "redis-server" 85 | } 86 | ], 87 | "selector": { 88 | "name": "redis", 89 | "role": "slave" 90 | }, 91 | "clusterIP": "10.0.0.140", 92 | "sessionAffinity": "None" 93 | }, 94 | "status": {} 95 | } 96 | ] 97 | } -------------------------------------------------------------------------------- /test/json/service_merge_patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : {}, 3 | "kind" : "Service", 4 | "apiVersion" : "v1", 5 | "spec" : { 6 | "ports" : [ 7 | { 8 | "targetPort" : 80, 9 | "nodePort" : 0, 10 | "port" : 80, 11 | "protocol" : "TCP" 12 | } 13 | ], 14 | "clusterIP" : "1.2.3.4", 15 | "type": "NodePort" 16 | }, 17 | "metadata" : { 18 | "name" : "my-service", 19 | "creationTimestamp" : null, 20 | "namespace" : "development", 21 | "resourceVersion" : "2", 22 | "annotations" : { 23 | "key" : "value" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/json/service_patch.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : {}, 3 | "kind" : "Service", 4 | "apiVersion" : "v1", 5 | "spec" : { 6 | "ports" : [ 7 | { 8 | "targetPort" : 80, 9 | "nodePort" : 0, 10 | "port" : 80, 11 | "protocol" : "TCP" 12 | } 13 | ], 14 | "clusterIP" : "1.2.3.4" 15 | }, 16 | "metadata" : { 17 | "name" : "my_service", 18 | "creationTimestamp" : null, 19 | "namespace" : "development", 20 | "resourceVersion" : "2", 21 | "annotations" : { 22 | "key" : "value" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/json/service_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "status" : {}, 3 | "kind" : "Service", 4 | "apiVersion" : "v1", 5 | "spec" : { 6 | "ports" : [ 7 | { 8 | "targetPort" : 80, 9 | "nodePort" : 0, 10 | "port" : 80, 11 | "protocol" : "TCP" 12 | } 13 | ], 14 | "clusterIP" : "1.2.3.4" 15 | }, 16 | "metadata" : { 17 | "name" : "my_service", 18 | "creationTimestamp" : null, 19 | "namespace" : "default", 20 | "resourceVersion" : "2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/json/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "template.openshift.io/v1", 3 | "kind": "Template", 4 | "metadata": { 5 | "creationTimestamp": "2018-12-17T16:11:36Z", 6 | "name": "my-template", 7 | "namespace": "default", 8 | "resourceVersion": "21954", 9 | "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates/my-template", 10 | "uid": "6e03e3e6-0216-11e9-b1e0-68f728fac3ab" 11 | }, 12 | "objects": [ 13 | { 14 | "apiVersion": "v1", 15 | "kind": "Service", 16 | "metadata": { 17 | "name": "${NAME_PREFIX}my-service" 18 | } 19 | } 20 | ], 21 | "parameters": [ 22 | { 23 | "description": "Prefix for names", 24 | "name": "NAME_PREFIX" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test/json/template.openshift.io_api_resource_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "APIResourceList", 3 | "apiVersion": "v1", 4 | "groupVersion": "template.openshift.io/v1", 5 | "resources": [ 6 | { 7 | "name": "brokertemplateinstances", 8 | "singularName": "", 9 | "namespaced": false, 10 | "kind": "BrokerTemplateInstance", 11 | "verbs": [ 12 | "create", 13 | "delete", 14 | "deletecollection", 15 | "get", 16 | "list", 17 | "patch", 18 | "update", 19 | "watch" 20 | ] 21 | }, 22 | { 23 | "name": "processedtemplates", 24 | "singularName": "", 25 | "namespaced": true, 26 | "kind": "Template", 27 | "verbs": [ 28 | "create" 29 | ] 30 | }, 31 | { 32 | "name": "templateinstances", 33 | "singularName": "", 34 | "namespaced": true, 35 | "kind": "TemplateInstance", 36 | "verbs": [ 37 | "create", 38 | "delete", 39 | "deletecollection", 40 | "get", 41 | "list", 42 | "patch", 43 | "update", 44 | "watch" 45 | ] 46 | }, 47 | { 48 | "name": "templateinstances/status", 49 | "singularName": "", 50 | "namespaced": true, 51 | "kind": "TemplateInstance", 52 | "verbs": [ 53 | "get", 54 | "patch", 55 | "update" 56 | ] 57 | }, 58 | { 59 | "name": "templates", 60 | "singularName": "", 61 | "namespaced": true, 62 | "kind": "Template", 63 | "verbs": [ 64 | "create", 65 | "delete", 66 | "deletecollection", 67 | "get", 68 | "list", 69 | "patch", 70 | "update", 71 | "watch" 72 | ] 73 | } 74 | ] 75 | } -------------------------------------------------------------------------------- /test/json/template_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "TemplateList", 3 | "apiVersion": "template.openshift.io/v1", 4 | "metadata": { 5 | "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates", 6 | "resourceVersion": "22758" 7 | }, 8 | "items": [ 9 | { 10 | "metadata": { 11 | "name": "my-template", 12 | "namespace": "default", 13 | "selfLink": "/apis/template.openshift.io/v1/namespaces/default/templates/my-template", 14 | "uid": "6e03e3e6-0216-11e9-b1e0-68f728fac3ab", 15 | "resourceVersion": "21954", 16 | "creationTimestamp": "2018-12-17T16:11:36Z" 17 | }, 18 | "objects": [ 19 | { 20 | "apiVersion": "v1", 21 | "kind": "Service", 22 | "metadata": { 23 | "name": "${NAME_PREFIX}my-service" 24 | } 25 | } 26 | ], 27 | "parameters": [ 28 | { 29 | "name": "NAME_PREFIX", 30 | "description": "Prefix for names" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/json/versions_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "versions": [ 3 | "v1beta3", 4 | "v1" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/json/watch_stream.json: -------------------------------------------------------------------------------- 1 | {"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"}}} 2 | {"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"}}} 3 | {"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"}}} 4 | -------------------------------------------------------------------------------- /test/test_common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Unit tests for the common module 6 | class CommonTest < MiniTest::Test 7 | class ClientStub < Kubeclient::Client 8 | end 9 | 10 | def client 11 | @client ||= ClientStub.allocate 12 | end 13 | 14 | def test_underscore_entity 15 | %w[ 16 | Pod pod 17 | Service service 18 | ReplicationController replication_controller 19 | Node node 20 | Event event 21 | Endpoint endpoint 22 | Namespace namespace 23 | Secret secret 24 | ResourceQuota resource_quota 25 | LimitRange limit_range 26 | PersistentVolume persistent_volume 27 | PersistentVolumeClaim persistent_volume_claim 28 | ComponentStatus component_status 29 | ServiceAccount service_account 30 | Project project 31 | Route route 32 | ClusterRoleBinding cluster_role_binding 33 | Build build 34 | BuildConfig build_config 35 | Image image 36 | ImageStream image_stream 37 | dogstatsd dogstatsd 38 | lowerCamelUPPERCase lower_camel_upper_case 39 | HTTPAPISpecBinding httpapi_spec_binding 40 | APIService api_service 41 | OAuthAccessToken o_auth_access_token 42 | OAuthAuthorizeToken o_auth_authorize_token 43 | OAuthClient o_auth_client 44 | OAuthClientAuthorization o_auth_client_authorization 45 | ].each_slice(2) do |kind, expected_underscore| 46 | underscore = Kubeclient::Client.underscore_entity(kind) 47 | assert_equal(underscore, expected_underscore) 48 | end 49 | end 50 | 51 | def test_format_datetime_with_string 52 | value = '2018-04-27T18:30:17.480321984Z' 53 | formatted = client.send(:format_datetime, value) 54 | assert_equal(formatted, value) 55 | end 56 | 57 | def test_format_datetime_with_datetime 58 | value = DateTime.new(2018, 4, 30, 19, 20, 33) 59 | formatted = client.send(:format_datetime, value) 60 | assert_equal(formatted, '2018-04-30T19:20:33.000000000+00:00') 61 | end 62 | 63 | def test_format_datetime_with_time 64 | value = Time.new(2018, 4, 30, 19, 20, 33, 0) 65 | formatted = client.send(:format_datetime, value) 66 | assert_equal(formatted, '2018-04-30T19:20:33.000000000+00:00') 67 | end 68 | 69 | def test_parse_definition_with_unconventional_names 70 | %w[ 71 | PluralPolicy pluralpolicies plural_policy plural_policies 72 | LatinDatum latindata latin_datum latin_data 73 | Noseparator noseparators noseparator noseparators 74 | lowercase lowercases lowercase lowercases 75 | TestWithDash test-with-dashes test_with_dash test_with_dashes 76 | TestUnderscore test_underscores test_underscore test_underscores 77 | TestMismatch other-odd-name testmismatch otheroddname 78 | MixedDashMinus mixed-dash_minuses mixed_dash_minus mixed_dash_minuses 79 | SameUptoWordboundary sameup-toword-boundarys sameuptowordboundary sameuptowordboundarys 80 | ].each_slice(4) do |kind, plural, expected_single, expected_plural| 81 | method_names = Kubeclient::Client.parse_definition(kind, plural).method_names 82 | assert_equal(method_names[0], expected_single) 83 | assert_equal(method_names[1], expected_plural) 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/test_component_status.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # ComponentStatus tests 6 | class TestComponentStatus < MiniTest::Test 7 | def test_get_from_json_v3 8 | stub_core_api_list 9 | stub_request(:get, %r{/componentstatuses}) 10 | .to_return(body: open_test_file('component_status.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | component_status = client.get_component_status('etcd-0', 'default') 14 | 15 | assert_instance_of(Kubeclient::Resource, component_status) 16 | assert_equal('etcd-0', component_status.metadata.name) 17 | assert_equal('Healthy', component_status.conditions[0].type) 18 | assert_equal('True', component_status.conditions[0].status) 19 | 20 | assert_requested( 21 | :get, 22 | 'http://localhost:8080/api/v1', 23 | times: 1 24 | ) 25 | assert_requested( 26 | :get, 27 | 'http://localhost:8080/api/v1/namespaces/default/componentstatuses/etcd-0', 28 | times: 1 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_endpoint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # kind: 'Endpoints' entity tests. 6 | # This is one of the unusual `kind`s that are already plural (https://github.com/kubernetes/kubernetes/issues/8115). 7 | # We force singular in method names like 'create_endpoint', 8 | # but `kind` should remain plural as in kubernetes. 9 | class TestEndpoint < MiniTest::Test 10 | def test_create_endpoint 11 | stub_core_api_list 12 | testing_ep = Kubeclient::Resource.new 13 | testing_ep.metadata = {} 14 | testing_ep.metadata.name = 'myendpoint' 15 | testing_ep.metadata.namespace = 'default' 16 | testing_ep.subsets = [ 17 | { 18 | 'addresses' => [{ 'ip' => '172.17.0.25' }], 19 | 'ports' => [{ 'name' => 'https', 'port' => 6443, 'protocol' => 'TCP' }] 20 | } 21 | ] 22 | 23 | req_body = '{"metadata":{"name":"myendpoint","namespace":"default"},' \ 24 | '"subsets":[{"addresses":[{"ip":"172.17.0.25"}],"ports":[{"name":"https",' \ 25 | '"port":6443,"protocol":"TCP"}]}],"kind":"Endpoints","apiVersion":"v1"}' 26 | 27 | stub_request(:post, 'http://localhost:8080/api/v1/namespaces/default/endpoints') 28 | .with(body: req_body) 29 | .to_return(body: open_test_file('created_endpoint.json'), status: 201) 30 | 31 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 32 | created_ep = client.create_endpoint(testing_ep) 33 | assert_equal('Endpoints', created_ep.kind) 34 | assert_equal('v1', created_ep.apiVersion) 35 | 36 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1', as: :parsed_symbolized) 37 | created_ep = client.create_endpoint(testing_ep) 38 | assert_equal('Endpoints', created_ep[:kind]) 39 | assert_equal('v1', created_ep[:apiVersion]) 40 | end 41 | 42 | def test_get_endpoints 43 | stub_core_api_list 44 | stub_request(:get, %r{/endpoints}) 45 | .to_return(body: open_test_file('endpoint_list.json'), status: 200) 46 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 47 | 48 | collection = client.get_endpoints(as: :parsed_symbolized) 49 | assert_equal('EndpointsList', collection[:kind]) 50 | assert_equal('v1', collection[:apiVersion]) 51 | 52 | # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic. 53 | collection = client.get_endpoints 54 | assert_equal('Endpoints', collection.kind) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/test_exec_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'open3' 5 | 6 | # Unit tests for the ExecCredentials provider 7 | class ExecCredentialsTest < MiniTest::Test 8 | def test_exec_opts_missing 9 | expected_msg = 10 | 'exec options are required' 11 | exception = assert_raises(ArgumentError) do 12 | Kubeclient::ExecCredentials.run(nil) 13 | end 14 | assert_equal(expected_msg, exception.message) 15 | end 16 | 17 | def test_exec_command_missing 18 | expected_msg = 19 | 'exec command is required' 20 | exception = assert_raises(KeyError) do 21 | Kubeclient::ExecCredentials.run({}) 22 | end 23 | assert_equal(expected_msg, exception.message) 24 | end 25 | 26 | def test_exec_command_failure 27 | err = 'Error' 28 | expected_msg = 29 | "exec command failed: #{err}" 30 | 31 | st = Minitest::Mock.new 32 | st.expect(:success?, false) 33 | 34 | opts = { 'command' => 'dummy' } 35 | 36 | Open3.stub(:capture3, [nil, err, st]) do 37 | exception = assert_raises(RuntimeError) do 38 | Kubeclient::ExecCredentials.run(opts) 39 | end 40 | assert_equal(expected_msg, exception.message) 41 | end 42 | end 43 | 44 | def test_run_with_token_credentials 45 | opts = { 'command' => 'dummy' } 46 | 47 | credentials = { 48 | 'token' => '0123456789ABCDEF0123456789ABCDEF' 49 | } 50 | 51 | creds = JSON.dump( 52 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 53 | 'status' => credentials 54 | ) 55 | 56 | st = Minitest::Mock.new 57 | st.expect(:success?, true) 58 | 59 | Open3.stub(:capture3, [creds, nil, st]) do 60 | assert_equal(credentials, Kubeclient::ExecCredentials.run(opts)) 61 | end 62 | end 63 | 64 | def test_run_with_client_credentials 65 | opts = { 'command' => 'dummy' } 66 | 67 | credentials = { 68 | 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF', 69 | 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF' 70 | } 71 | 72 | creds = JSON.dump( 73 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 74 | 'status' => credentials 75 | ) 76 | 77 | st = Minitest::Mock.new 78 | st.expect(:success?, true) 79 | 80 | Open3.stub(:capture3, [creds, nil, st]) do 81 | assert_equal(credentials, Kubeclient::ExecCredentials.run(opts)) 82 | end 83 | end 84 | 85 | def test_run_with_missing_client_certificate_data 86 | opts = { 'command' => 'dummy' } 87 | 88 | credentials = { 89 | 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF' 90 | } 91 | 92 | creds = JSON.dump( 93 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 94 | 'status' => credentials 95 | ) 96 | 97 | st = Minitest::Mock.new 98 | st.expect(:success?, true) 99 | 100 | expected_msg = 'exec plugin didn\'t return client certificate data' 101 | 102 | Open3.stub(:capture3, [creds, nil, st]) do 103 | exception = assert_raises(RuntimeError) do 104 | Kubeclient::ExecCredentials.run(opts) 105 | end 106 | assert_equal(expected_msg, exception.message) 107 | end 108 | end 109 | 110 | def test_run_with_missing_client_key_data 111 | opts = { 'command' => 'dummy' } 112 | 113 | credentials = { 114 | 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF' 115 | } 116 | 117 | creds = JSON.dump( 118 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 119 | 'status' => credentials 120 | ) 121 | 122 | st = Minitest::Mock.new 123 | st.expect(:success?, true) 124 | 125 | expected_msg = 'exec plugin didn\'t return client key data' 126 | 127 | Open3.stub(:capture3, [creds, nil, st]) do 128 | exception = assert_raises(RuntimeError) do 129 | Kubeclient::ExecCredentials.run(opts) 130 | end 131 | assert_equal(expected_msg, exception.message) 132 | end 133 | end 134 | 135 | def test_run_with_client_data_and_token 136 | opts = { 'command' => 'dummy' } 137 | 138 | credentials = { 139 | 'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF', 140 | 'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF', 141 | 'token' => '0123456789ABCDEF0123456789ABCDEF' 142 | } 143 | 144 | creds = JSON.dump( 145 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 146 | 'status' => credentials 147 | ) 148 | 149 | st = Minitest::Mock.new 150 | st.expect(:success?, true) 151 | 152 | expected_msg = 'exec plugin returned both token and client data' 153 | 154 | Open3.stub(:capture3, [creds, nil, st]) do 155 | exception = assert_raises(RuntimeError) do 156 | Kubeclient::ExecCredentials.run(opts) 157 | end 158 | assert_equal(expected_msg, exception.message) 159 | end 160 | end 161 | 162 | def test_status_missing 163 | opts = { 'command' => 'dummy' } 164 | 165 | creds = JSON.dump('apiVersion' => 'client.authentication.k8s.io/v1alpha1') 166 | 167 | st = Minitest::Mock.new 168 | st.expect(:success?, true) 169 | 170 | expected_msg = 'exec plugin didn\'t return a status field' 171 | 172 | Open3.stub(:capture3, [creds, nil, st]) do 173 | exception = assert_raises(RuntimeError) do 174 | Kubeclient::ExecCredentials.run(opts) 175 | end 176 | assert_equal(expected_msg, exception.message) 177 | end 178 | end 179 | 180 | def test_credentials_missing 181 | opts = { 'command' => 'dummy' } 182 | 183 | creds = JSON.dump( 184 | 'apiVersion' => 'client.authentication.k8s.io/v1alpha1', 185 | 'status' => {} 186 | ) 187 | 188 | st = Minitest::Mock.new 189 | st.expect(:success?, true) 190 | 191 | expected_msg = 'exec plugin didn\'t return a token or client data' 192 | 193 | Open3.stub(:capture3, [creds, nil, st]) do 194 | exception = assert_raises(RuntimeError) do 195 | Kubeclient::ExecCredentials.run(opts) 196 | end 197 | assert_equal(expected_msg, exception.message) 198 | end 199 | end 200 | 201 | def test_api_version_mismatch 202 | api_version = 'client.authentication.k8s.io/v1alpha1' 203 | expected_version = 'client.authentication.k8s.io/v1beta1' 204 | 205 | opts = { 206 | 'command' => 'dummy', 207 | 'apiVersion' => expected_version 208 | } 209 | 210 | creds = JSON.dump( 211 | 'apiVersion' => api_version 212 | ) 213 | 214 | st = Minitest::Mock.new 215 | st.expect(:success?, true) 216 | 217 | expected_msg = "exec plugin is configured to use API version #{expected_version}," \ 218 | " plugin returned version #{api_version}" 219 | 220 | Open3.stub(:capture3, [creds, nil, st]) do 221 | exception = assert_raises(RuntimeError) do 222 | Kubeclient::ExecCredentials.run(opts) 223 | end 224 | assert_equal(expected_msg, exception.message) 225 | end 226 | end 227 | end 228 | -------------------------------------------------------------------------------- /test/test_gcp_command_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'open3' 5 | 6 | # Unit tests for the GCPCommandCredentials token provider 7 | class GCPCommandCredentialsTest < MiniTest::Test 8 | def test_token 9 | opts = { 10 | 'cmd-args' => 'config config-helper --format=json', 11 | 'cmd-path' => '/path/to/gcloud', 12 | 'expiry-key' => '{.credential.token_expiry}', 13 | 'token-key' => '{.credential.access_token}' 14 | } 15 | 16 | creds = JSON.dump( 17 | 'credential' => { 18 | 'access_token' => '9A3A941836F2458175BE18AA1971EBBF47949B07', 19 | 'token_expiry' => '2019-04-12T15:02:51Z' 20 | } 21 | ) 22 | 23 | st = Minitest::Mock.new 24 | st.expect(:success?, true) 25 | 26 | Open3.stub(:capture3, [creds, nil, st]) do 27 | assert_equal( 28 | '9A3A941836F2458175BE18AA1971EBBF47949B07', 29 | Kubeclient::GCPCommandCredentials.token(opts) 30 | ) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/test_google_application_default_credentials.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'googleauth' 5 | 6 | # Unit tests for the ApplicationDefaultCredentials token provider 7 | class GoogleApplicationDefaultCredentialsTest < MiniTest::Test 8 | def test_token 9 | auth = Minitest::Mock.new 10 | auth.expect(:apply, nil, [{}]) 11 | auth.expect(:access_token, 'valid_token') 12 | 13 | Google::Auth.stub(:get_application_default, auth) do 14 | assert_equal('valid_token', Kubeclient::GoogleApplicationDefaultCredentials.token) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_limit_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # LimitRange tests 6 | class TestLimitRange < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/limitranges}) 10 | .to_return(body: open_test_file('limit_range.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | limit_range = client.get_limit_range('limits', 'quota-example') 14 | 15 | assert_instance_of(Kubeclient::Resource, limit_range) 16 | assert_equal('limits', limit_range.metadata.name) 17 | assert_equal('Container', limit_range.spec.limits[0].type) 18 | assert_equal('100m', limit_range.spec.limits[0].default.cpu) 19 | assert_equal('512Mi', limit_range.spec.limits[0].default.memory) 20 | 21 | assert_requested( 22 | :get, 23 | 'http://localhost:8080/api/v1/namespaces/quota-example/limitranges/limits', 24 | times: 1 25 | ) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/test_missing_methods.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Test method_missing, respond_to? and respond_to_missing behaviour 6 | class TestMissingMethods < MiniTest::Test 7 | def test_missing 8 | stub_core_api_list 9 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 10 | assert_equal(true, client.respond_to?(:get_pod)) 11 | assert_equal(true, client.respond_to?(:get_pods)) 12 | assert_equal(false, client.respond_to?(:get_pie)) 13 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery 14 | assert_equal(false, client.respond_to?(:get_pie)) 15 | assert_equal(true, client.respond_to?(:get_pods)) 16 | assert_equal(true, client.respond_to?(:get_pod)) 17 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery 18 | assert_instance_of(Method, client.method(:get_pods)) 19 | assert_raises(NameError) do 20 | client.method(:get_pies) 21 | end 22 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') # Reset discovery 23 | assert_raises(NameError) do 24 | client.method(:get_pies) 25 | end 26 | assert_instance_of(Method, client.method(:get_pods)) 27 | 28 | stub_request(:get, %r{/api/v1$}).to_return( 29 | body: '', 30 | status: 404 31 | ) # If discovery fails we expect the below raise an exception 32 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 33 | assert_raises(Kubeclient::HttpError) do 34 | client.discover 35 | end 36 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 37 | assert_raises(Kubeclient::HttpError) do 38 | client.method(:get_pods) 39 | end 40 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 41 | assert_raises(Kubeclient::HttpError) do 42 | client.respond_to?(:get_pods) 43 | end 44 | end 45 | 46 | def test_nonsuffix_plurals 47 | stub_request(:get, %r{/apis/extensions/v1beta1$}).to_return( 48 | body: open_test_file('extensions_v1beta1_api_resource_list.json'), 49 | status: 200 50 | ) 51 | client = Kubeclient::Client.new('http://localhost:8080/apis/extensions', 'v1beta1') 52 | assert_equal(true, client.respond_to?(:get_network_policy)) 53 | assert_equal(true, client.respond_to?(:get_network_policies)) 54 | assert_equal(true, client.respond_to?(:get_pod_security_policy)) 55 | assert_equal(true, client.respond_to?(:get_pod_security_policies)) 56 | end 57 | 58 | def test_irregular_names 59 | stub_core_api_list 60 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 61 | assert_equal(true, client.respond_to?(:get_endpoint)) 62 | assert_equal(true, client.respond_to?(:get_endpoints)) 63 | 64 | stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( 65 | body: open_test_file('security.openshift.io_api_resource_list.json'), 66 | status: 200 67 | ) 68 | client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') 69 | assert_equal(true, client.respond_to?(:get_security_context_constraint)) 70 | assert_equal(true, client.respond_to?(:get_security_context_constraints)) 71 | end 72 | 73 | def test_lowercase_kind 74 | stub_request(:get, %r{/apis/config.istio.io/v1alpha2$}).to_return( 75 | body: open_test_file('config.istio.io_api_resource_list.json'), 76 | status: 200 77 | ) 78 | client = Kubeclient::Client.new('http://localhost:8080/apis/config.istio.io', 'v1alpha2') 79 | assert_equal(true, client.respond_to?(:get_servicecontrolreport)) 80 | assert_equal(true, client.respond_to?(:get_servicecontrolreports)) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/test_namespace.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Namespace entity tests 6 | class TestNamespace < MiniTest::Test 7 | def test_get_namespace_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/namespaces}) 10 | .to_return(body: open_test_file('namespace.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | namespace = client.get_namespace('staging') 14 | 15 | assert_instance_of(Kubeclient::Resource, namespace) 16 | assert_equal('e388bc10-c021-11e4-a514-3c970e4a436a', namespace.metadata.uid) 17 | assert_equal('staging', namespace.metadata.name) 18 | assert_equal('1168', namespace.metadata.resourceVersion) 19 | assert_equal('v1', namespace.apiVersion) 20 | 21 | assert_requested( 22 | :get, 23 | 'http://localhost:8080/api/v1/namespaces/staging', 24 | times: 1 25 | ) 26 | end 27 | 28 | def test_delete_namespace_v1 29 | our_namespace = Kubeclient::Resource.new 30 | our_namespace.metadata = {} 31 | our_namespace.metadata.name = 'staging' 32 | 33 | stub_core_api_list 34 | stub_request(:delete, %r{/namespaces}) 35 | .to_return(body: open_test_file('namespace.json'), status: 200) 36 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 37 | our_namespace = client.delete_namespace(our_namespace.metadata.name) 38 | assert_kind_of(RecursiveOpenStruct, our_namespace) 39 | 40 | assert_requested( 41 | :delete, 42 | 'http://localhost:8080/api/v1/namespaces/staging', 43 | times: 1 44 | ) 45 | end 46 | 47 | def test_create_namespace 48 | stub_core_api_list 49 | stub_request(:post, %r{/namespaces}) 50 | .to_return(body: open_test_file('created_namespace.json'), status: 201) 51 | 52 | namespace = Kubeclient::Resource.new 53 | namespace.metadata = {} 54 | namespace.metadata.name = 'development' 55 | 56 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 57 | created_namespace = client.create_namespace(namespace) 58 | assert_instance_of(Kubeclient::Resource, created_namespace) 59 | assert_equal(namespace.metadata.name, created_namespace.metadata.name) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/test_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Node entity tests 6 | class TestNode < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/nodes}) 10 | .to_return(body: open_test_file('node.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | node = client.get_node('127.0.0.1') 14 | 15 | assert_instance_of(Kubeclient::Resource, node) 16 | 17 | assert_equal('041143c5-ce39-11e4-ac24-3c970e4a436a', node.metadata.uid) 18 | assert_equal('127.0.0.1', node.metadata.name) 19 | assert_equal('1724', node.metadata.resourceVersion) 20 | assert_equal('v1', node.apiVersion) 21 | assert_equal('2015-03-19T15:08:20+02:00', node.metadata.creationTimestamp) 22 | 23 | assert_requested( 24 | :get, 25 | 'http://localhost:8080/api/v1', 26 | times: 1 27 | ) 28 | assert_requested( 29 | :get, 30 | 'http://localhost:8080/api/v1/nodes/127.0.0.1', 31 | times: 1 32 | ) 33 | end 34 | 35 | def test_get_from_json_v1_raw 36 | stub_core_api_list 37 | stub_request(:get, %r{/nodes}) 38 | .to_return(body: open_test_file('node.json'), status: 200) 39 | 40 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 41 | response = client.get_node('127.0.0.1', nil, as: :raw) 42 | 43 | assert_equal(open_test_file('node.json').read, response) 44 | 45 | assert_requested( 46 | :get, 47 | 'http://localhost:8080/api/v1', 48 | times: 1 49 | ) 50 | assert_requested( 51 | :get, 52 | 'http://localhost:8080/api/v1/nodes/127.0.0.1', 53 | times: 1 54 | ) 55 | end 56 | 57 | def test_get_from_json_v1_raw_error 58 | stub_request(:get, %r{/nodes}) 59 | .to_return(body: open_test_file('node.json'), status: 200) 60 | stub_request(:get, %r{/api/v1$}) 61 | .to_return(body: open_test_file('core_api_resource_list.json'), status: 500) 62 | 63 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 64 | 65 | exception = assert_raises(Kubeclient::HttpError) do 66 | client.get_node('127.0.0.1', nil, as: :raw) 67 | end 68 | 69 | assert_instance_of(Kubeclient::HttpError, exception) 70 | assert_equal(500, exception.error_code) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/test_oidc_auth_provider.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'openid_connect' 5 | 6 | class OIDCAuthProviderTest < MiniTest::Test 7 | def setup 8 | super() 9 | @client_id = 'client_id' 10 | @client_secret = 'client_secret' 11 | @idp_issuer_url = 'idp_issuer_url' 12 | @refresh_token = 'refresh_token' 13 | @id_token = 'id_token' 14 | @new_id_token = 'new_id_token' 15 | end 16 | 17 | def test_expired_token 18 | OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do 19 | OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i - 7200)) do 20 | OpenIDConnect::Client.stub(:new, openid_client_mock) do 21 | retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 22 | 'client-id' => @client_id, 23 | 'client-secret' => @client_secret, 24 | 'id-token' => @id_token, 25 | 'idp-issuer-url' => @idp_issuer_url, 26 | 'refresh-token' => @refresh_token 27 | ) 28 | assert_equal(@new_id_token, retrieved_id_token) 29 | end 30 | end 31 | end 32 | end 33 | 34 | def test_valid_token 35 | OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do 36 | OpenIDConnect::ResponseObject::IdToken.stub(:decode, id_token_mock(Time.now.to_i + 7200)) do 37 | retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 38 | 'client-id' => @client_id, 39 | 'client-secret' => @client_secret, 40 | 'id-token' => @id_token, 41 | 'idp-issuer-url' => @idp_issuer_url, 42 | 'refresh-token' => @refresh_token 43 | ) 44 | assert_equal(@id_token, retrieved_id_token) 45 | end 46 | end 47 | end 48 | 49 | def test_missing_id_token 50 | OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do 51 | OpenIDConnect::Client.stub(:new, openid_client_mock) do 52 | retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 53 | 'client-id' => @client_id, 54 | 'client-secret' => @client_secret, 55 | 'idp-issuer-url' => @idp_issuer_url, 56 | 'refresh-token' => @refresh_token 57 | ) 58 | assert_equal(@new_id_token, retrieved_id_token) 59 | end 60 | end 61 | end 62 | 63 | def test_token_with_unknown_kid 64 | OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do 65 | OpenIDConnect::ResponseObject::IdToken.stub( 66 | :decode, ->(_token, _jwks) { raise JSON::JWK::Set::KidNotFound } 67 | ) do 68 | OpenIDConnect::Client.stub(:new, openid_client_mock) do 69 | retrieved_id_token = Kubeclient::OIDCAuthProvider.token( 70 | 'client-id' => @client_id, 71 | 'client-secret' => @client_secret, 72 | 'id-token' => @id_token, 73 | 'idp-issuer-url' => @idp_issuer_url, 74 | 'refresh-token' => @refresh_token 75 | ) 76 | assert_equal(@new_id_token, retrieved_id_token) 77 | end 78 | end 79 | end 80 | end 81 | 82 | private 83 | 84 | def openid_client_mock 85 | access_token = Minitest::Mock.new 86 | access_token.expect(@id_token, @new_id_token) 87 | 88 | openid_client = Minitest::Mock.new 89 | openid_client.expect(:refresh_token=, nil, [@refresh_token]) 90 | openid_client.expect(:access_token!, access_token) 91 | end 92 | 93 | def id_token_mock(expiry) 94 | id_token_mock = Minitest::Mock.new 95 | id_token_mock.expect(:exp, expiry) 96 | end 97 | 98 | def discovery_mock 99 | discovery = Minitest::Mock.new 100 | discovery.expect(:jwks, 'jwks') 101 | discovery.expect(:authorization_endpoint, 'authz_endpoint') 102 | discovery.expect(:token_endpoint, 'token_endpoint') 103 | discovery.expect(:userinfo_endpoint, 'userinfo_endpoint') 104 | discovery 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /test/test_persistent_volume.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # PersistentVolume tests 6 | class TestPersistentVolume < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/persistentvolumes}) 10 | .to_return(body: open_test_file('persistent_volume.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | volume = client.get_persistent_volume('pv0001') 14 | 15 | assert_instance_of(Kubeclient::Resource, volume) 16 | assert_equal('pv0001', volume.metadata.name) 17 | assert_equal('10Gi', volume.spec.capacity.storage) 18 | assert_equal('/tmp/data01', volume.spec.hostPath.path) 19 | 20 | assert_requested( 21 | :get, 22 | 'http://localhost:8080/api/v1', 23 | times: 1 24 | ) 25 | assert_requested( 26 | :get, 27 | 'http://localhost:8080/api/v1/persistentvolumes/pv0001', 28 | times: 1 29 | ) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_persistent_volume_claim.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # PersistentVolumeClaim tests 6 | class TestPersistentVolumeClaim < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/persistentvolumeclaims}) 10 | .to_return(body: open_test_file('persistent_volume_claim.json'), status: 200) 11 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 12 | claim = client.get_persistent_volume_claim('myclaim-1', 'default') 13 | 14 | assert_instance_of(Kubeclient::Resource, claim) 15 | assert_equal('myclaim-1', claim.metadata.name) 16 | assert_equal('3Gi', claim.spec.resources.requests.storage) 17 | assert_equal('pv0001', claim.spec.volumeName) 18 | 19 | assert_requested( 20 | :get, 21 | 'http://localhost:8080/api/v1', 22 | times: 1 23 | ) 24 | assert_requested( 25 | :get, 26 | 'http://localhost:8080/api/v1/namespaces/default/persistentvolumeclaims/myclaim-1', 27 | times: 1 28 | ) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/test_pod.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Pod entity tests 6 | class TestPod < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/pods}) 10 | .to_return(body: open_test_file('pod.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | pod = client.get_pod('redis-master-pod', 'default') 14 | 15 | assert_instance_of(Kubeclient::Resource, pod) 16 | assert_equal('redis-master3', pod.metadata.name) 17 | assert_equal('dockerfile/redis', pod.spec.containers[0]['image']) 18 | 19 | assert_requested( 20 | :get, 21 | 'http://localhost:8080/api/v1', 22 | times: 1 23 | ) 24 | assert_requested( 25 | :get, 26 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod', 27 | times: 1 28 | ) 29 | end 30 | 31 | def test_get_chunks 32 | stub_core_api_list 33 | stub_request(:get, %r{/pods}) 34 | .to_return(body: open_test_file('pods_1.json'), status: 200) 35 | 36 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 37 | pods = client.get_pods(limit: 2) 38 | 39 | assert_equal(2, pods.count) 40 | assert_equal('eyJ2IjoibWV0YS5rOHMua', pods.continue) 41 | 42 | continue = pods.continue 43 | 44 | stub_request(:get, %r{/pods}) 45 | .to_return(body: open_test_file('pods_2.json'), status: 200) 46 | 47 | pods = client.get_pods(limit: 2, continue: continue) 48 | assert_equal(2, pods.count) 49 | assert_nil(pods.continue) 50 | 51 | assert_requested( 52 | :get, 53 | 'http://localhost:8080/api/v1', 54 | times: 1 55 | ) 56 | assert_requested( 57 | :get, 58 | 'http://localhost:8080/api/v1/pods?limit=2', 59 | times: 1 60 | ) 61 | assert_requested( 62 | :get, 63 | "http://localhost:8080/api/v1/pods?continue=#{continue}&limit=2", 64 | times: 1 65 | ) 66 | end 67 | 68 | def test_get_chunks_410_gone 69 | stub_core_api_list 70 | stub_request(:get, %r{/pods}) 71 | .to_return(body: open_test_file('pods_410.json'), status: 410) 72 | 73 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 74 | 75 | err = assert_raises(Kubeclient::HttpError) do 76 | client.get_pods(limit: 2, continue: 'eyJ2IjoibWV0YS5') 77 | end 78 | 79 | assert_equal( 80 | err.message, 81 | 'The provided from parameter is too old to display a consistent list result. ' \ 82 | 'You must start a new list without the from.' 83 | ) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/test_pod_log.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Pod log tests 6 | class TestPodLog < MiniTest::Test 7 | def test_get_pod_log 8 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) 9 | .to_return(body: open_test_file('pod_log.txt'), status: 200) 10 | 11 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 12 | retrieved_log = client.get_pod_log('redis-master-pod', 'default') 13 | 14 | assert_equal(open_test_file('pod_log.txt').read, retrieved_log) 15 | 16 | assert_requested( 17 | :get, 18 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log', 19 | times: 1 20 | ) 21 | end 22 | 23 | def test_get_pod_log_container 24 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) 25 | .to_return(body: open_test_file('pod_log.txt'), status: 200) 26 | 27 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 28 | retrieved_log = client.get_pod_log('redis-master-pod', 'default', container: 'ruby') 29 | 30 | assert_equal(open_test_file('pod_log.txt').read, retrieved_log) 31 | 32 | assert_requested( 33 | :get, 34 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?container=ruby', 35 | times: 1 36 | ) 37 | end 38 | 39 | def test_get_pod_log_since_time 40 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) 41 | .to_return(body: open_test_file('pod_log.txt'), status: 200) 42 | 43 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 44 | retrieved_log = client.get_pod_log( 45 | 'redis-master-pod', 'default', 46 | timestamps: true, since_time: '2018-04-27T18:30:17.480321984Z' 47 | ) 48 | 49 | assert_equal(open_test_file('pod_log.txt').read, retrieved_log) 50 | 51 | assert_requested( 52 | :get, 53 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?sinceTime=2018-04-27T18:30:17.480321984Z×tamps=true', 54 | times: 1 55 | ) 56 | end 57 | 58 | def test_get_pod_log_tail_lines 59 | selected_lines = open_test_file('pod_log.txt').to_a[-2..1].join 60 | 61 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) 62 | .to_return(body: selected_lines, status: 200) 63 | 64 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 65 | retrieved_log = client.get_pod_log('redis-master-pod', 'default', tail_lines: 2) 66 | 67 | assert_equal(selected_lines, retrieved_log) 68 | 69 | assert_requested( 70 | :get, 71 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?tailLines=2', 72 | times: 1 73 | ) 74 | end 75 | 76 | def test_get_pod_limit_bytes 77 | selected_bytes = open_test_file('pod_log.txt').read(10) 78 | 79 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log}) 80 | .to_return(body: selected_bytes, status: 200) 81 | 82 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 83 | retrieved_log = client.get_pod_log('redis-master-pod', 'default', limit_bytes: 10) 84 | 85 | assert_equal(selected_bytes, retrieved_log) 86 | 87 | assert_requested( 88 | :get, 89 | 'http://localhost:8080/api/v1/namespaces/default/pods/redis-master-pod/log?limitBytes=10', 90 | times: 1 91 | ) 92 | end 93 | 94 | def test_watch_pod_log 95 | file = open_test_file('pod_log.txt') 96 | expected_lines = file.read.split("\n") 97 | 98 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) 99 | .to_return(body: file, status: 200) 100 | 101 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 102 | 103 | stream = client.watch_pod_log('redis-master-pod', 'default') 104 | stream.to_enum.with_index do |notice, index| 105 | assert_instance_of(String, notice) 106 | assert_equal(expected_lines[index], notice) 107 | end 108 | end 109 | 110 | def test_watch_pod_log_with_block 111 | file = open_test_file('pod_log.txt') 112 | first = file.readlines.first.chomp 113 | 114 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) 115 | .to_return(body: file, status: 200) 116 | 117 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 118 | 119 | client.watch_pod_log('redis-master-pod', 'default') do |line| 120 | assert_equal(first, line) 121 | break 122 | end 123 | end 124 | 125 | def test_watch_pod_log_follow_redirect 126 | expected_lines = open_test_file('pod_log.txt').read.split("\n") 127 | redirect = 'http://localhost:1234/api/namespaces/default/pods/redis-master-pod/log' 128 | 129 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) 130 | .to_return(status: 302, headers: { location: redirect }) 131 | 132 | stub_request(:get, redirect) 133 | .to_return(body: open_test_file('pod_log.txt'), status: 200) 134 | 135 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 136 | stream = client.watch_pod_log('redis-master-pod', 'default') 137 | stream.to_enum.with_index do |notice, index| 138 | assert_instance_of(String, notice) 139 | assert_equal(expected_lines[index], notice) 140 | end 141 | end 142 | 143 | def test_watch_pod_log_max_redirect 144 | redirect = 'http://localhost:1234/api/namespaces/default/pods/redis-master-pod/log' 145 | 146 | stub_request(:get, %r{/namespaces/default/pods/[a-z0-9-]+/log\?.*follow}) 147 | .to_return(status: 302, headers: { location: redirect }) 148 | 149 | stub_request(:get, redirect) 150 | .to_return(body: open_test_file('pod_log.txt'), status: 200) 151 | 152 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1', http_max_redirects: 0) 153 | assert_raises(Kubeclient::HttpError) do 154 | client.watch_pod_log('redis-master-pod', 'default').each {} # rubocop:disable Lint/EmptyBlock 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /test/test_process_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Process Template tests 6 | class TestProcessTemplate < MiniTest::Test 7 | def test_process_template 8 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 9 | template = {} 10 | template[:metadata] = {} 11 | template[:metadata][:name] = 'my-template' 12 | template[:metadata][:namespace] = 'default' 13 | template[:kind] = 'Template' 14 | template[:apiVersion] = 'v1' 15 | service = {} 16 | service[:metadata] = {} 17 | service[:metadata][:name] = '${NAME_PREFIX}my-service' 18 | service[:kind] = 'Service' 19 | service[:apiVersion] = 'v1' 20 | template[:objects] = [service] 21 | param = { name: 'NAME_PREFIX', value: 'test/' } 22 | template[:parameters] = [param] 23 | 24 | req_body = '{"metadata":{"name":"my-template","namespace":"default"},' \ 25 | '"kind":"Template","apiVersion":"v1","objects":[{"metadata":' \ 26 | '{"name":"${NAME_PREFIX}my-service"},"kind":"Service","apiVersion":"v1"}],' \ 27 | '"parameters":[{"name":"NAME_PREFIX","value":"test/"}]}' 28 | 29 | expected_url = 'http://localhost:8080/api/v1/namespaces/default/processedtemplates' 30 | stub_request(:post, expected_url) 31 | .with(body: req_body, headers: { 'Content-Type' => 'application/json' }) 32 | .to_return(body: open_test_file('processed_template.json'), status: 200) 33 | 34 | processed_template = client.process_template(template) 35 | 36 | assert_equal('test/my-service', processed_template['objects'].first['metadata']['name']) 37 | 38 | assert_requested(:post, expected_url, times: 1) do |req| 39 | data = JSON.parse(req.body) 40 | data['kind'] == 'Template' && 41 | data['apiVersion'] == 'v1' && 42 | data['metadata']['name'] == 'my-template' && 43 | data['metadata']['namespace'] == 'default' 44 | end 45 | end 46 | 47 | # Ensure _template and _templates methods hit `/templates` rather than 48 | # `/processedtemplates` URL. 49 | def test_templates_methods 50 | stub_request(:get, %r{/apis/template\.openshift\.io/v1$}).to_return( 51 | body: open_test_file('template.openshift.io_api_resource_list.json'), 52 | status: 200 53 | ) 54 | client = Kubeclient::Client.new('http://localhost:8080/apis/template.openshift.io', 'v1') 55 | 56 | expected_url = 'http://localhost:8080/apis/template.openshift.io/v1/namespaces/default/templates' 57 | stub_request(:get, expected_url) 58 | .to_return(body: open_test_file('template_list.json'), status: 200) 59 | client.get_templates(namespace: 'default') 60 | assert_requested(:get, expected_url, times: 1) 61 | 62 | expected_url = 'http://localhost:8080/apis/template.openshift.io/v1/namespaces/default/templates/my-template' 63 | stub_request(:get, expected_url) 64 | .to_return(body: open_test_file('template.json'), status: 200) 65 | client.get_template('my-template', 'default') 66 | assert_requested(:get, expected_url, times: 1) 67 | end 68 | 69 | def test_no_processedtemplates_methods 70 | stub_request(:get, %r{/apis/template\.openshift\.io/v1$}).to_return( 71 | body: open_test_file('template.openshift.io_api_resource_list.json'), 72 | status: 200 73 | ) 74 | client = Kubeclient::Client.new('http://localhost:8080/apis/template.openshift.io', 'v1') 75 | client.discover 76 | 77 | refute_respond_to(client, :get_processedtemplates) 78 | refute_respond_to(client, :get_processedtemplate) 79 | refute_respond_to(client, :get_processed_templates) 80 | refute_respond_to(client, :get_processed_template) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /test/test_real_cluster.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helper' 2 | 3 | class KubeclientRealClusterTest < MiniTest::Test 4 | # Tests here actually connect to a cluster! 5 | # For simplicity, these tests use same config/*.kubeconfig files as test_config.rb, 6 | # so are intended to run from config/update_certs_k0s.rb script. 7 | def setup 8 | if ENV['KUBECLIENT_TEST_REAL_CLUSTER'] == 'true' 9 | WebMock.enable_net_connect! 10 | else 11 | skip('Requires real cluster, see test/config/update_certs_k0s.rb.') 12 | end 13 | end 14 | 15 | def teardown 16 | WebMock.disable_net_connect! # Don't allow any connections in other tests. 17 | end 18 | 19 | # Partially isolated tests that check Client behavior with given `verify_ssl` value: 20 | 21 | # localhost and 127.0.0.1 are among names on the certificate 22 | HOSTNAME_COVERED_BY_CERT = 'https://127.0.0.1:6443'.freeze 23 | # 127.0.0.2 also means localhost but is not included in the certificate. 24 | HOSTNAME_NOT_ON_CERT = 'https://127.0.0.2:6443'.freeze 25 | 26 | def test_real_cluster_verify_peer 27 | config = Kubeclient::Config.read(config_file('external.kubeconfig')) 28 | context = config.context 29 | client1 = Kubeclient::Client.new( 30 | HOSTNAME_COVERED_BY_CERT, 'v1', 31 | ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_PEER), 32 | auth_options: context.auth_options 33 | ) 34 | check_cert_accepted(client1) 35 | client2 = Kubeclient::Client.new( 36 | HOSTNAME_NOT_ON_CERT, 'v1', 37 | ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_PEER), 38 | auth_options: context.auth_options 39 | ) 40 | check_cert_rejected(client2) 41 | end 42 | 43 | def test_real_cluster_verify_none 44 | config = Kubeclient::Config.read(config_file('external.kubeconfig')) 45 | context = config.context 46 | client1 = Kubeclient::Client.new( 47 | HOSTNAME_COVERED_BY_CERT, 'v1', 48 | ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_NONE), 49 | auth_options: context.auth_options 50 | ) 51 | check_cert_accepted(client1) 52 | client2 = Kubeclient::Client.new( 53 | HOSTNAME_NOT_ON_CERT, 'v1', 54 | ssl_options: context.ssl_options.merge(verify_ssl: OpenSSL::SSL::VERIFY_NONE), 55 | auth_options: context.auth_options 56 | ) 57 | check_cert_accepted(client2) 58 | end 59 | 60 | # Integration tests that check combined Config -> Client behavior wrt. `verify_ssl`. 61 | # Quite redundant, but this was an embarrasing vulnerability so want to confirm... 62 | 63 | def test_real_cluster_concatenated_ca 64 | config = Kubeclient::Config.read(config_file('concatenated-ca.kubeconfig')) 65 | context = config.context 66 | client1 = Kubeclient::Client.new( 67 | HOSTNAME_COVERED_BY_CERT, 'v1', 68 | ssl_options: context.ssl_options, auth_options: context.auth_options 69 | ) 70 | check_cert_accepted(client1) 71 | client2 = Kubeclient::Client.new( 72 | HOSTNAME_NOT_ON_CERT, 'v1', 73 | ssl_options: context.ssl_options, auth_options: context.auth_options 74 | ) 75 | check_cert_rejected(client2) 76 | end 77 | 78 | def test_real_cluster_verify_ssl_with_ca 79 | config = Kubeclient::Config.read(config_file('external.kubeconfig')) 80 | context = config.context 81 | client1 = Kubeclient::Client.new( 82 | HOSTNAME_COVERED_BY_CERT, 'v1', 83 | ssl_options: context.ssl_options, auth_options: context.auth_options 84 | ) 85 | check_cert_accepted(client1) 86 | client2 = Kubeclient::Client.new( 87 | HOSTNAME_NOT_ON_CERT, 'v1', 88 | ssl_options: context.ssl_options, auth_options: context.auth_options 89 | ) 90 | check_cert_rejected(client2) 91 | end 92 | 93 | def test_real_cluster_verify_ssl_without_ca 94 | config = Kubeclient::Config.read(config_file('external-without-ca.kubeconfig')) 95 | context = config.context 96 | # Hostname matches cert but the local cluster uses self-signed certs from custom CA, 97 | # and this config omits CA data, so verification can't succeed. 98 | client1 = Kubeclient::Client.new( 99 | HOSTNAME_COVERED_BY_CERT, 'v1', 100 | ssl_options: context.ssl_options, auth_options: context.auth_options 101 | ) 102 | check_cert_rejected(client1) 103 | client2 = Kubeclient::Client.new( 104 | HOSTNAME_NOT_ON_CERT, 'v1', 105 | ssl_options: context.ssl_options, auth_options: context.auth_options 106 | ) 107 | check_cert_rejected(client2) 108 | end 109 | 110 | def test_real_cluster_insecure_without_ca 111 | config = Kubeclient::Config.read(config_file('insecure.kubeconfig')) 112 | context = config.context 113 | # Hostname matches cert but the local cluster uses self-signed certs from custom CA, 114 | # and this config omits CA data, so verification would fail; 115 | # however, this config specifies `insecure-skip-tls-verify: true` so any cert goes. 116 | client1 = Kubeclient::Client.new( 117 | HOSTNAME_COVERED_BY_CERT, 'v1', 118 | ssl_options: context.ssl_options, auth_options: context.auth_options 119 | ) 120 | check_cert_accepted(client1) 121 | client2 = Kubeclient::Client.new( 122 | HOSTNAME_NOT_ON_CERT, 'v1', 123 | ssl_options: context.ssl_options, auth_options: context.auth_options 124 | ) 125 | check_cert_accepted(client2) 126 | end 127 | 128 | private 129 | 130 | # Test cert checking on discovery, CRUD, and watch code paths. 131 | def check_cert_accepted(client) 132 | client.discover 133 | client.get_nodes 134 | exercise_watcher_with_timeout(client.watch_nodes) 135 | end 136 | 137 | def check_cert_rejected(client) 138 | # TODO: all OpenSSL exceptions should be wrapped with Kubeclient error. 139 | assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do 140 | client.discover 141 | end 142 | # Since discovery fails, methods like .get_nodes, .watch_nodes would all fail 143 | # on method_missing -> discover. Call lower-level methods to test actual connection. 144 | assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do 145 | client.get_entities('Node', 'nodes', {}) 146 | end 147 | assert_raises(Kubeclient::HttpError, OpenSSL::SSL::SSLError) do 148 | exercise_watcher_with_timeout(client.watch_entities('nodes')) 149 | end 150 | end 151 | 152 | def exercise_watcher_with_timeout(watcher) 153 | thread = Thread.new do 154 | sleep(1) 155 | watcher.finish 156 | end 157 | watcher.each do |_notice| # rubocop:disable Lint/UnreachableLoop 158 | break 159 | end 160 | thread.join 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /test/test_replication_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Replication Controller entity tests 6 | class TestReplicationController < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/replicationcontrollers}) 10 | .to_return(body: open_test_file('replication_controller.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | rc = client.get_replication_controller('frontendController', 'default') 14 | 15 | assert_instance_of(Kubeclient::Resource, rc) 16 | assert_equal('guestbook-controller', rc.metadata.name) 17 | assert_equal('c71aa4c0-a240-11e4-a265-3c970e4a436a', rc.metadata.uid) 18 | assert_equal('default', rc.metadata.namespace) 19 | assert_equal(3, rc.spec.replicas) 20 | assert_equal('guestbook', rc.spec.selector.name) 21 | 22 | assert_requested( 23 | :get, 24 | 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController', 25 | times: 1 26 | ) 27 | end 28 | 29 | def test_delete_replicaset_cascade 30 | stub_core_api_list 31 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 32 | opts = Kubeclient::Resource.new( 33 | apiVersion: 'meta/v1', 34 | gracePeriodSeconds: 0, 35 | kind: 'DeleteOptions', 36 | propagationPolicy: 'Foreground' 37 | ) 38 | 39 | stub_request( 40 | :delete, 41 | 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController' 42 | ) 43 | .with(body: opts.to_hash.to_json) 44 | .to_return(status: 200, body: open_test_file('replication_controller.json'), headers: {}) 45 | rc = client.delete_replication_controller('frontendController', 'default', delete_options: opts) 46 | assert_kind_of(RecursiveOpenStruct, rc) 47 | 48 | assert_requested( 49 | :delete, 50 | 'http://localhost:8080/api/v1/namespaces/default/replicationcontrollers/frontendController', 51 | times: 1 52 | ) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/test_resource_list_without_kind.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Core api resource list without kind tests 6 | class TestResourceListWithoutKind < MiniTest::Test 7 | def test_get_from_json_api_v1 8 | stub_request(:get, %r{/api/v1$}) 9 | .to_return(body: open_test_file('core_api_resource_list_without_kind.json'), status: 200) 10 | 11 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 12 | client.discover 13 | 14 | [ 15 | { 16 | entity: 'pod', 17 | type: 'Pod', 18 | name: 'pods', 19 | methods: %w[pod pods] 20 | }, 21 | { 22 | entity: 'node', 23 | type: 'Node', 24 | name: 'nodes', 25 | methods: %w[node nodes] 26 | }, 27 | { 28 | entity: 'service', 29 | type: 'Service', 30 | name: 'services', 31 | methods: %w[service services] 32 | } 33 | ].each { |h| assert_entities(client.instance_variable_get(:@entities)[h[:entity]], h) } 34 | 35 | assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) 36 | end 37 | 38 | def test_get_from_json_oapi_v1 39 | stub_request(:get, %r{/oapi/v1$}) 40 | .to_return(body: open_test_file('core_oapi_resource_list_without_kind.json'), status: 200) 41 | 42 | client = Kubeclient::Client.new('http://localhost:8080/oapi/', 'v1') 43 | client.discover 44 | 45 | [ 46 | { 47 | entity: 'template', 48 | type: 'Template', 49 | name: 'templates', 50 | methods: %w[template templates] 51 | }, 52 | { 53 | entity: 'build', 54 | type: 'Build', 55 | name: 'builds', 56 | methods: %w[build builds] 57 | }, 58 | { 59 | entity: 'project', 60 | type: 'Project', 61 | name: 'projects', 62 | methods: %w[project projects] 63 | } 64 | ].each { |h| assert_entities(client.instance_variable_get(:@entities)[h[:entity]], h) } 65 | 66 | assert_requested(:get, 'http://localhost:8080/oapi/v1', times: 1) 67 | end 68 | 69 | def assert_entities(entity, h) 70 | assert_equal(entity.entity_type, h[:type]) 71 | assert_equal(entity.resource_name, h[:name]) 72 | assert_equal(entity.method_names, h[:methods]) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/test_resource_quota.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # ResourceQuota tests 6 | class TestResourceQuota < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/resourcequotas}) 10 | .to_return(body: open_test_file('resource_quota.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | quota = client.get_resource_quota('quota', 'quota-example') 14 | 15 | assert_instance_of(Kubeclient::Resource, quota) 16 | assert_equal('quota', quota.metadata.name) 17 | assert_equal('20', quota.spec.hard.cpu) 18 | assert_equal('10', quota.spec.hard.secrets) 19 | 20 | assert_requested( 21 | :get, 'http://localhost:8080/api/v1/namespaces/quota-example/resourcequotas/quota', times: 1 22 | ) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/test_secret.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Namespace entity tests 6 | class TestSecret < MiniTest::Test 7 | def test_get_secret_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/secrets}) 10 | .to_return(body: open_test_file('created_secret.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | secret = client.get_secret('test-secret', 'dev') 14 | 15 | assert_instance_of(Kubeclient::Resource, secret) 16 | assert_equal('4e38a198-2bcb-11e5-a483-0e840567604d', secret.metadata.uid) 17 | assert_equal('test-secret', secret.metadata.name) 18 | assert_equal('v1', secret.apiVersion) 19 | assert_equal('Y2F0J3MgYXJlIGF3ZXNvbWUK', secret.data['super-secret']) 20 | 21 | assert_requested( 22 | :get, 'http://localhost:8080/api/v1/namespaces/dev/secrets/test-secret', times: 1 23 | ) 24 | end 25 | 26 | def test_delete_secret_v1 27 | stub_core_api_list 28 | stub_request(:delete, %r{/secrets}) 29 | .to_return(status: 200, body: open_test_file('created_secret.json')) 30 | 31 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 32 | secret = client.delete_secret('test-secret', 'dev') 33 | assert_kind_of(RecursiveOpenStruct, secret) 34 | 35 | assert_requested( 36 | :delete, 'http://localhost:8080/api/v1/namespaces/dev/secrets/test-secret', times: 1 37 | ) 38 | end 39 | 40 | def test_create_secret_v1 41 | stub_core_api_list 42 | stub_request(:post, %r{/secrets}) 43 | .to_return(body: open_test_file('created_secret.json'), status: 201) 44 | 45 | secret = Kubeclient::Resource.new 46 | secret.metadata = {} 47 | secret.metadata.name = 'test-secret' 48 | secret.metadata.namespace = 'dev' 49 | secret.data = {} 50 | secret.data['super-secret'] = 'Y2F0J3MgYXJlIGF3ZXNvbWUK' 51 | 52 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 53 | created_secret = client.create_secret(secret) 54 | assert_instance_of(Kubeclient::Resource, created_secret) 55 | assert_equal(secret.metadata.name, created_secret.metadata.name) 56 | assert_equal(secret.metadata.namespace, created_secret.metadata.namespace) 57 | assert_equal( 58 | secret.data['super-secret'], 59 | created_secret.data['super-secret'] 60 | ) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/test_security_context_constraint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # kind: 'SecurityContextConstraints' entity tests. 6 | # This is one of the unusual `kind`s that are already plural (https://github.com/kubernetes/kubernetes/issues/8115). 7 | # We force singular in method names like 'create_endpoint', 8 | # but `kind` should remain plural as in kubernetes. 9 | class TestSecurityContextConstraints < MiniTest::Test 10 | def test_create_security_context_constraint 11 | stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( 12 | body: open_test_file('security.openshift.io_api_resource_list.json'), 13 | status: 200 14 | ) 15 | 16 | testing_scc = Kubeclient::Resource.new( 17 | metadata: { 18 | name: 'teleportation' 19 | }, 20 | runAsUser: { 21 | type: 'MustRunAs' 22 | }, 23 | seLinuxContext: { 24 | type: 'MustRunAs' 25 | } 26 | ) 27 | req_body = '{"metadata":{"name":"teleportation"},"runAsUser":{"type":"MustRunAs"},' \ 28 | '"seLinuxContext":{"type":"MustRunAs"},' \ 29 | '"kind":"SecurityContextConstraints","apiVersion":"security.openshift.io/v1"}' 30 | 31 | stub_request(:post, 'http://localhost:8080/apis/security.openshift.io/v1/securitycontextconstraints') 32 | .with(body: req_body) 33 | .to_return(body: open_test_file('created_security_context_constraint.json'), status: 201) 34 | 35 | client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') 36 | created_scc = client.create_security_context_constraint(testing_scc) 37 | assert_equal('SecurityContextConstraints', created_scc.kind) 38 | assert_equal('security.openshift.io/v1', created_scc.apiVersion) 39 | 40 | client = Kubeclient::Client.new( 41 | 'http://localhost:8080/apis/security.openshift.io', 'v1', 42 | as: :parsed_symbolized 43 | ) 44 | created_scc = client.create_security_context_constraint(testing_scc) 45 | assert_equal('SecurityContextConstraints', created_scc[:kind]) 46 | assert_equal('security.openshift.io/v1', created_scc[:apiVersion]) 47 | end 48 | 49 | def test_get_security_context_constraints 50 | stub_request(:get, %r{/apis/security.openshift.io/v1$}).to_return( 51 | body: open_test_file('security.openshift.io_api_resource_list.json'), 52 | status: 200 53 | ) 54 | stub_request(:get, %r{/securitycontextconstraints}) 55 | .to_return(body: open_test_file('security_context_constraint_list.json'), status: 200) 56 | client = Kubeclient::Client.new('http://localhost:8080/apis/security.openshift.io', 'v1') 57 | 58 | collection = client.get_security_context_constraints(as: :parsed_symbolized) 59 | assert_equal('SecurityContextConstraintsList', collection[:kind]) 60 | assert_equal('security.openshift.io/v1', collection[:apiVersion]) 61 | 62 | # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic. 63 | collection = client.get_security_context_constraints 64 | assert_equal('SecurityContextConstraints', collection.kind) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/test_service_account.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # ServiceAccount tests 6 | class TestServiceAccount < MiniTest::Test 7 | def test_get_from_json_v1 8 | stub_core_api_list 9 | stub_request(:get, %r{/serviceaccounts}) 10 | .to_return(body: open_test_file('service_account.json'), status: 200) 11 | 12 | client = Kubeclient::Client.new('http://localhost:8080/api/', 'v1') 13 | account = client.get_service_account('default') 14 | 15 | assert_instance_of(Kubeclient::Resource, account) 16 | assert_equal('default', account.metadata.name) 17 | assert_equal('default-token-6s23q', account.secrets[0].name) 18 | assert_equal('default-dockercfg-62tf3', account.secrets[1].name) 19 | 20 | assert_requested(:get, 'http://localhost:8080/api/v1/serviceaccounts/default', times: 1) 21 | assert_requested(:get, 'http://localhost:8080/api/v1', times: 1) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/txt/pod_log.txt: -------------------------------------------------------------------------------- 1 | Initializing server... 2 | ...loaded configuration 3 | ...updated settings 4 | ...discovered local servers 5 | ...frobinated disks 6 | Complete! 7 | -------------------------------------------------------------------------------- /test/valid_token_file: -------------------------------------------------------------------------------- 1 | valid_token 2 | --------------------------------------------------------------------------------