From 15c47236cc42a8cb1de951e80a687f81781c50c3 Mon Sep 17 00:00:00 2001 From: Danny Turner Date: Tue, 12 Nov 2019 08:40:05 -0800 Subject: [PATCH] Global prune 2 (#612) * Prune global resources * Add code comments for unexpected behaviors --- lib/krane/cli/global_deploy_command.rb | 5 +- lib/krane/cluster_resource_discovery.rb | 51 +++++++++++- lib/krane/global_deploy_task.rb | 5 +- lib/krane/kubeclient_builder.rb | 8 ++ test/exe/global_deploy_test.rb | 7 +- .../fixtures/for_unit_tests/api_resources.txt | 21 +++++ test/fixtures/for_unit_tests/api_versions.txt | 32 ++++++++ test/fixtures/globals/priority_class.yml | 8 ++ test/helpers/fixture_deploy_helper.rb | 12 +-- test/helpers/kubeclient_helper.rb | 4 + test/integration-serial/serial_deploy_test.rb | 26 ++++++ test/integration/global_deploy_test.rb | 80 ++++++++++++++++--- test/unit/cluster_resource_discovery_test.rb | 53 ++++++------ 13 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 test/fixtures/for_unit_tests/api_resources.txt create mode 100644 test/fixtures/for_unit_tests/api_versions.txt create mode 100644 test/fixtures/globals/priority_class.yml diff --git a/lib/krane/cli/global_deploy_command.rb b/lib/krane/cli/global_deploy_command.rb index 10a2d2cc5..c65a2ccd8 100644 --- a/lib/krane/cli/global_deploy_command.rb +++ b/lib/krane/cli/global_deploy_command.rb @@ -14,6 +14,9 @@ class GlobalDeployCommand desc: "Verify workloads correctly deployed" }, "selector" => { type: :string, banner: "'label=value'", required: true, desc: "Select workloads owned by selector(s)" }, + "prune" => { type: :boolean, desc: "Enable deletion of resources that match"\ + " the provided selector and do not appear in the provided templates", + default: true }, } def self.from_options(context, options) @@ -35,7 +38,7 @@ def self.from_options(context, options) deploy.run!( verify_result: options["verify-result"], - prune: false, + prune: options[:prune], ) end end diff --git a/lib/krane/cluster_resource_discovery.rb b/lib/krane/cluster_resource_discovery.rb index 677376e17..41c4d0cc6 100644 --- a/lib/krane/cluster_resource_discovery.rb +++ b/lib/krane/cluster_resource_discovery.rb @@ -17,13 +17,49 @@ def crds end def global_resource_kinds - @globals ||= fetch_globals.map { |g| g["kind"] } + @globals ||= fetch_resources(namespaced: false).map { |g| g["kind"] } + end + + def prunable_resources(namespaced:) + black_list = %w(Namespace Node) + api_versions = fetch_api_versions + + fetch_resources(namespaced: namespaced).map do |resource| + next unless resource['verbs'].one? { |v| v == "delete" } + next if black_list.include?(resource['kind']) + version = api_versions[resource['apigroup'].to_s].last + [resource['apigroup'], version, resource['kind']].compact.join("/") + end.compact end private - def fetch_globals - raw, _, st = kubectl.run("api-resources", "--namespaced=false", output: "wide", attempts: 5, + # kubectl api-versions returns a list of group/version strings e.g. autoscaling/v2beta2 + # A kind may not exist in all versions of the group. + def fetch_api_versions + raw, _, st = kubectl.run("api-versions", attempts: 5, use_namespace: false) + # The "core" group is represented by an empty string + versions = { "" => %w(v1) } + if st.success? + rows = raw.split("\n") + rows.each do |group_version| + group, version = group_version.split("/") + versions[group] ||= [] + versions[group] << version + end + end + versions + end + + # kubectl api-resources -o wide returns 5 columns + # NAME SHORTNAMES APIGROUP NAMESPACED KIND VERBS + # SHORTNAMES and APIGROUP may be blank + # VERBS is an array + # serviceaccounts sa true ServiceAccount [create delete deletecollection get list patch update watch] + def fetch_resources(namespaced: false) + command = %w(api-resources) + command << "--namespaced=#{namespaced}" + raw, _, st = kubectl.run(*command, output: "wide", attempts: 5, use_namespace: false) if st.success? rows = raw.split("\n") @@ -34,9 +70,16 @@ def fetch_globals fields = full_width_field_names.each_with_object({}) do |name, hash| start = cursor cursor = start + name.length + # Last field should consume the remainder of the line + cursor = 0 if full_width_field_names.last == name.strip hash[name.strip] = [start, cursor - 1] end - resources.map { |r| fields.map { |k, (s, e)| [k.strip, r[s..e].strip] }.to_h } + resources.map do |resource| + resource = fields.map { |k, (s, e)| [k.strip, resource[s..e].strip] }.to_h + # Manually parse verbs: "[get list]" into %w(get list) + resource["verbs"] = resource["verbs"][1..-2].split + resource + end else [] end diff --git a/lib/krane/global_deploy_task.rb b/lib/krane/global_deploy_task.rb index eaa17deee..c817cb25d 100644 --- a/lib/krane/global_deploy_task.rb +++ b/lib/krane/global_deploy_task.rb @@ -103,7 +103,6 @@ def run!(verify_result: true, prune: true) private def deploy!(resources, verify_result, prune) - prune_whitelist = [] resource_deployer = ResourceDeployer.new(task_config: @task_config, prune_whitelist: prune_whitelist, max_watch_seconds: @global_timeout, selector: @selector, statsd_tags: statsd_tags) @@ -197,6 +196,10 @@ def kubeclient_builder @kubeclient_builder ||= KubeclientBuilder.new end + def prune_whitelist + cluster_resource_discoverer.prunable_resources(namespaced: false) + end + def check_initial_status(resources) cache = ResourceCache.new(@task_config) Concurrency.split_across_threads(resources) { |r| r.sync(cache) } diff --git a/lib/krane/kubeclient_builder.rb b/lib/krane/kubeclient_builder.rb index 7d02fa154..d9083d159 100644 --- a/lib/krane/kubeclient_builder.rb +++ b/lib/krane/kubeclient_builder.rb @@ -103,6 +103,14 @@ def build_storage_v1_kubeclient(context) ) end + def build_scheduling_v1beta1_kubeclient(context) + build_kubeclient( + api_version: "v1beta1", + context: context, + endpoint_path: "/apis/scheduling.k8s.io" + ) + end + def validate_config_files errors = [] if @kubeconfig_files.empty? diff --git a/test/exe/global_deploy_test.rb b/test/exe/global_deploy_test.rb index 4fc658988..992b1b38b 100644 --- a/test/exe/global_deploy_test.rb +++ b/test/exe/global_deploy_test.rb @@ -36,6 +36,11 @@ def test_deploy_parses_selector krane_global_deploy!(flags: "--selector #{selector}") end + def test_deploy_parses_prune + set_krane_global_deploy_expectations!(run_args: { prune: false }) + krane_global_deploy!(flags: '--prune false') + end + private def set_krane_global_deploy_expectations!(new_args: {}, run_args: {}) @@ -68,7 +73,7 @@ def default_options(new_args = {}, run_args = {}) }.merge(new_args), run_args: { verify_result: true, - prune: false, + prune: true, }.merge(run_args), } end diff --git a/test/fixtures/for_unit_tests/api_resources.txt b/test/fixtures/for_unit_tests/api_resources.txt new file mode 100644 index 000000000..2d2ba8706 --- /dev/null +++ b/test/fixtures/for_unit_tests/api_resources.txt @@ -0,0 +1,21 @@ +NAME SHORTNAMES APIGROUP NAMESPACED KIND VERBS +componentstatuses cs false ComponentStatus [get list] +namespaces ns false Namespace [create delete get list patch update watch] +nodes no false Node [create delete deletecollection get list patch update watch] +persistentvolumes pv false PersistentVolume [create delete deletecollection get list patch update watch] +mutatingwebhookconfigurations admissionregistration.k8s.io false MutatingWebhookConfiguration [create delete deletecollection get list patch update watch] +validatingwebhookconfigurations admissionregistration.k8s.io false ValidatingWebhookConfiguration [create delete deletecollection get list patch update watch] +customresourcedefinitions crd,crds apiextensions.k8s.io false CustomResourceDefinition [create delete deletecollection get list patch update watch] +apiservices apiregistration.k8s.io false APIService [create delete deletecollection get list patch update watch] +tokenreviews authentication.k8s.io false TokenReview [create] +selfsubjectaccessreviews authorization.k8s.io false SelfSubjectAccessReview [create] +selfsubjectrulesreviews authorization.k8s.io false SelfSubjectRulesReview [create] +subjectaccessreviews authorization.k8s.io false SubjectAccessReview [create] +certificatesigningrequests csr certificates.k8s.io false CertificateSigningRequest [create delete deletecollection get list patch update watch] +podsecuritypolicies psp extensions false PodSecurityPolicy [create delete deletecollection get list patch update watch] +podsecuritypolicies psp policy false PodSecurityPolicy [create delete deletecollection get list patch update watch] +clusterrolebindings rbac.authorization.k8s.io false ClusterRoleBinding [create delete deletecollection get list patch update watch] +clusterroles rbac.authorization.k8s.io false ClusterRole [create delete deletecollection get list patch update watch] +priorityclasses pc scheduling.k8s.io false PriorityClass [create delete deletecollection get list patch update watch] +storageclasses sc storage.k8s.io false StorageClass [create delete deletecollection get list patch update watch] +volumeattachments storage.k8s.io false VolumeAttachment [create delete deletecollection get list patch update watch] diff --git a/test/fixtures/for_unit_tests/api_versions.txt b/test/fixtures/for_unit_tests/api_versions.txt new file mode 100644 index 000000000..1f3ded43a --- /dev/null +++ b/test/fixtures/for_unit_tests/api_versions.txt @@ -0,0 +1,32 @@ +admissionregistration.k8s.io/v1 +admissionregistration.k8s.io/v1beta1 +apiextensions.k8s.io/v1 +apiextensions.k8s.io/v1beta1 +apiregistration.k8s.io/v1 +apiregistration.k8s.io/v1beta1 +apps/v1 +authentication.k8s.io/v1 +authentication.k8s.io/v1beta1 +authorization.k8s.io/v1 +authorization.k8s.io/v1beta1 +autoscaling/v1 +autoscaling/v2beta1 +autoscaling/v2beta2 +batch/v1 +batch/v1beta1 +certificates.k8s.io/v1beta1 +coordination.k8s.io/v1 +coordination.k8s.io/v1beta1 +events.k8s.io/v1beta1 +extensions/v1beta1 +networking.k8s.io/v1 +networking.k8s.io/v1beta1 +node.k8s.io/v1beta1 +policy/v1beta1 +rbac.authorization.k8s.io/v1 +rbac.authorization.k8s.io/v1beta1 +scheduling.k8s.io/v1 +scheduling.k8s.io/v1beta1 +storage.k8s.io/v1 +storage.k8s.io/v1beta1 +v1 diff --git a/test/fixtures/globals/priority_class.yml b/test/fixtures/globals/priority_class.yml new file mode 100644 index 000000000..30677c429 --- /dev/null +++ b/test/fixtures/globals/priority_class.yml @@ -0,0 +1,8 @@ +apiVersion: scheduling.k8s.io/v1beta1 +description: Used for testing global deploys and pruning. +kind: PriorityClass +metadata: + name: testing-priority-class + labels: + app: krane +value: 20 diff --git a/test/helpers/fixture_deploy_helper.rb b/test/helpers/fixture_deploy_helper.rb index b51f87aad..4a157fd84 100644 --- a/test/helpers/fixture_deploy_helper.rb +++ b/test/helpers/fixture_deploy_helper.rb @@ -46,7 +46,7 @@ def deploy_global_fixtures(set, subset: nil, **args) fixtures = load_fixtures(set, subset) raise "Cannot deploy empty template set" if fixtures.empty? args[:selector] ||= "test=#{@namespace}" - namespace_globals(fixtures) + namespace_globals(fixtures, args[:selector]) yield fixtures if block_given? @@ -104,7 +104,7 @@ def deploy_dirs_without_profiling(dirs, wait: true, allow_protected_ns: false, p ) end - def global_deploy_dirs_without_profiling(dirs, verify_result: true, prune: false, + def global_deploy_dirs_without_profiling(dirs, clean_up: true, verify_result: true, prune: true, global_timeout: 300, selector:) deploy = Krane::GlobalDeployTask.new( context: KubeclientHelper::TEST_CONTEXT, @@ -118,7 +118,7 @@ def global_deploy_dirs_without_profiling(dirs, verify_result: true, prune: false prune: prune ) ensure - delete_globals(Array(dirs)) + delete_globals(Array(dirs)) if clean_up end # Deploys all fixtures in the given directories via KubernetesDeploy::DeployTask @@ -180,13 +180,15 @@ def build_kubectl(log_failure_by_default: true, timeout: '5s') log_failure_by_default: log_failure_by_default, default_timeout: timeout) end - def namespace_globals(fixtures) + def namespace_globals(fixtures, selector) + selector_key, selector_value = selector.split("=") fixtures.each do |_, kinds_map| kinds_map.each do |_, resources| resources.each do |resource| resource["metadata"]["name"] = (resource["metadata"]["name"] + @namespace)[0..63] + resource["metadata"]["name"] += "0" if resource["metadata"]["name"].end_with?("-") resource["metadata"]["labels"] ||= {} - resource["metadata"]["labels"]["test"] = @namespace + resource["metadata"]["labels"][selector_key] = selector_value end end end diff --git a/test/helpers/kubeclient_helper.rb b/test/helpers/kubeclient_helper.rb index 174f6308c..cfb07ac81 100644 --- a/test/helpers/kubeclient_helper.rb +++ b/test/helpers/kubeclient_helper.rb @@ -52,6 +52,10 @@ def storage_v1_kubeclient @storage_v1_kubeclient ||= kubeclient_builder.build_storage_v1_kubeclient(TEST_CONTEXT) end + def scheduling_v1beta1_kubeclient + @scheduling_v1beta1_kubeclient ||= kubeclient_builder.build_scheduling_v1beta1_kubeclient(TEST_CONTEXT) + end + def kubeclient_builder @kubeclient_builder ||= Krane::KubeclientBuilder.new end diff --git a/test/integration-serial/serial_deploy_test.rb b/test/integration-serial/serial_deploy_test.rb index 8290cc9cd..63c57e53a 100644 --- a/test/integration-serial/serial_deploy_test.rb +++ b/test/integration-serial/serial_deploy_test.rb @@ -603,6 +603,32 @@ def test_global_deploy_validation_catches_namespaced_cr wait_for_all_crd_deletion end + def test_global_deploy_prune_black_box_success + namespace_name = "test-app" + setup_template_dir("globals") do |target_dir| + flags = "-f #{target_dir} --selector app=krane" + namespace_str = "apiVersion: v1\nkind: Namespace\nmetadata:\n name: #{namespace_name}"\ + "\n labels:\n app: krane" + File.write(File.join(target_dir, "namespace.yml"), namespace_str) + out, err, status = krane_black_box("global-deploy", "#{KubeclientHelper::TEST_CONTEXT} #{flags}") + assert_empty(out) + assert_match("Successfully deployed 3 resource", err) + assert_match(/#{namespace_name}\W+Exists/, err) + assert_match("Success", err) + assert_predicate(status, :success?) + + flags = "-f #{target_dir}/storage_classes.yml --selector app=krane" + out, err, status = krane_black_box("global-deploy", "#{KubeclientHelper::TEST_CONTEXT} #{flags}") + assert_empty(out) + refute_match(namespace_name, err) # Asserting that the namespace is not pruned + assert_match("Pruned 1 resource and successfully deployed 1 resource", err) + assert_predicate(status, :success?) + end + ensure + build_kubectl.run("delete", "-f", fixture_path("globals"), use_namespace: false, log_failure: false) + build_kubectl.run("delete", "namespace", namespace_name, use_namespace: false, log_failure: false) + end + private def wait_for_all_crd_deletion diff --git a/test/integration/global_deploy_test.rb b/test/integration/global_deploy_test.rb index 9585d8252..dd6ec4b5d 100644 --- a/test/integration/global_deploy_test.rb +++ b/test/integration/global_deploy_test.rb @@ -14,14 +14,17 @@ def test_global_deploy_task_success "Phase 2: Checking initial resource statuses", %r{StorageClass\/testing-storage-class[\w-]+\s+Not Found}, "Phase 3: Deploying all resources", - %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + "Deploying resources:", + %r{StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + %r{PriorityClass/testing-priority-class[\w-]+ \(timeout: 300s\)}, "Don't know how to monitor resources of type StorageClass.", %r{Assuming StorageClass\/testing-storage-class[\w-]+ deployed successfully.}, - %r{Successfully deployed in [\d.]+s: StorageClass\/testing-storage-class}, + %r{Successfully deployed in [\d.]+s: PriorityClass/testing-priority-class[\w-]+, StorageClass\/testing-storage-}, "Result: SUCCESS", - "Successfully deployed 1 resource", + "Successfully deployed 2 resources", "Successful resources", "StorageClass/testing-storage-class", + "PriorityClass/testing-priority-class", ]) end @@ -37,9 +40,9 @@ def test_global_deploy_task_success_timeout "Phase 2: Checking initial resource statuses", %r{StorageClass\/testing-storage-class[\w-]+\s+Not Found}, "Phase 3: Deploying all resources", - %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + "Deploying resources:", "Result: TIMED OUT", - "Timed out waiting for 1 resource to deploy", + "Timed out waiting for 2 resources to deploy", %r{StorageClass\/testing-storage-class[\w-]+: GLOBAL WATCH TIMEOUT \(0 seconds\)}, "If you expected it to take longer than 0 seconds for your deploy to roll out, increase --max-watch-seconds.", ]) @@ -54,12 +57,16 @@ def test_global_deploy_task_success_verify_false "All required parameters and files are present", "Discovering resources:", " - StorageClass/testing-storage-class", + " - PriorityClass/testing-priority-class", "Phase 2: Checking initial resource statuses", %r{StorageClass\/testing-storage-class[\w-]+\s+Not Found}, + %r{PriorityClass/testing-priority-class[\w-]+\s+Not Found}, "Phase 3: Deploying all resources", - %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + "Deploying resources:", + %r{StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + %r{PriorityClass/testing-priority-class[\w-]+ \(timeout: 300s\)}, "Result: SUCCESS", - "Deployed 1 resource", + "Deployed 2 resources", "Deploy result verification is disabled for this deploy.", "This means the desired changes were communicated to Kubernetes, but the"\ " deploy did not make sure they actually succeeded.", @@ -77,7 +84,7 @@ def test_global_deploy_task_empty_selector_validation_failure end def test_global_deploy_task_success_selector - selector = "app=krane" + selector = "app=krane2" assert_deploy_success(deploy_global_fixtures('globals', selector: selector)) assert_logs_match_all([ @@ -89,14 +96,17 @@ def test_global_deploy_task_success_selector "Phase 2: Checking initial resource statuses", %r{StorageClass\/testing-storage-class[\w-]+\s+Not Found}, "Phase 3: Deploying all resources", - %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + "Deploying resources:", + %r{PriorityClass/testing-priority-class[\w-]+ \(timeout: 300s\)}, + %r{StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, "Don't know how to monitor resources of type StorageClass.", %r{Assuming StorageClass\/testing-storage-class[\w-]+ deployed successfully.}, - %r{Successfully deployed in [\d.]+s: StorageClass\/testing-storage-class}, + /Successfully deployed in [\d.]+s/, "Result: SUCCESS", - "Successfully deployed 1 resource", + "Successfully deployed 2 resources", "Successful resources", "StorageClass/testing-storage-class", + "PriorityClass/testing-priority-class", ]) end @@ -116,4 +126,52 @@ def test_global_deploy_task_failure "Template validation failed", ]) end + + def test_global_deploy_prune_success + assert_deploy_success(deploy_global_fixtures('globals', clean_up: false, selector: 'test=prune1')) + reset_logger + assert_deploy_success(deploy_global_fixtures('globals', subset: 'storage_classes.yml', selector: 'test=prune1')) + assert_logs_match_all([ + "Phase 1: Initializing deploy", + "Using resource selector test=prune1", + "All required parameters and files are present", + "Discovering resources:", + " - StorageClass/testing-storage-class", + "Phase 2: Checking initial resource statuses", + %r{StorageClass\/testing-storage-class[\w-]+\s+Exists}, + "Phase 3: Deploying all resources", + %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + "The following resources were pruned: priorityclass.scheduling.k8s.io/testing-priority-class", + %r{Successfully deployed in [\d.]+s: StorageClass\/testing-storage-class}, + "Result: SUCCESS", + "Pruned 1 resource and successfully deployed 1 resource", + "Successful resources", + "StorageClass/testing-storage-class", + ]) + end + + def test_no_prune_global_deploy_success + assert_deploy_success(deploy_global_fixtures('globals', clean_up: false, selector: 'test=prune2')) + reset_logger + assert_deploy_success(deploy_global_fixtures('globals', subset: 'storage_classes.yml', + selector: "test=prune2", prune: false)) + assert_logs_match_all([ + "Phase 1: Initializing deploy", + "Using resource selector test=prune2", + "All required parameters and files are present", + "Discovering resources:", + " - StorageClass/testing-storage-class", + "Phase 2: Checking initial resource statuses", + %r{StorageClass\/testing-storage-class[\w-]+\s+Exists}, + "Phase 3: Deploying all resources", + %r{Deploying StorageClass\/testing-storage-class[\w-]+ \(timeout: 300s\)}, + %r{Successfully deployed in [\d.]+s: StorageClass\/testing-storage-class}, + "Result: SUCCESS", + "Successfully deployed 1 resource", + "Successful resources", + "StorageClass/testing-storage-class", + ]) + refute_logs_match(/[pP]runed/) + assert_deploy_success(deploy_global_fixtures('globals', selector: 'test=prune2')) + end end diff --git a/test/unit/cluster_resource_discovery_test.rb b/test/unit/cluster_resource_discovery_test.rb index 3a084ec0f..816823a44 100644 --- a/test/unit/cluster_resource_discovery_test.rb +++ b/test/unit/cluster_resource_discovery_test.rb @@ -9,44 +9,43 @@ def test_global_resource_kinds_failure end def test_global_resource_kinds_success - crd = mocked_cluster_resource_discovery(full_response) + crd = mocked_cluster_resource_discovery(api_resources_full_response) kinds = crd.global_resource_kinds - assert_equal(kinds.length, full_response.split("\n").length - 1) + assert_equal(kinds.length, api_resources_full_response.split("\n").length - 1) %w(MutatingWebhookConfiguration ComponentStatus CustomResourceDefinition).each do |kind| assert_includes(kinds, kind) end end + def test_prunable_resources + Krane::Kubectl.any_instance.stubs(:run).with("api-versions", attempts: 5, use_namespace: false) + .returns([api_versions_full_response, "", stub(success?: true)]) + crd = mocked_cluster_resource_discovery(api_resources_full_response) + kinds = crd.prunable_resources(namespaced: false) + + assert_equal(kinds.length, 13) + %w(scheduling.k8s.io/v1beta1/PriorityClass storage.k8s.io/v1beta1/StorageClass).each do |kind| + assert_includes(kinds, kind) + end + %w(node namespace).each do |black_lised_kind| + assert_empty kinds.select { |k| k.downcase.include?(black_lised_kind) } + end + end + private def mocked_cluster_resource_discovery(response, success: true) - Krane::Kubectl.any_instance.stubs(:run).returns([response, "", stub(success?: success)]) + Krane::Kubectl.any_instance.stubs(:run) + .with("api-resources", "--namespaced=false", attempts: 5, use_namespace: false, output: "wide") + .returns([response, "", stub(success?: success)]) Krane::ClusterResourceDiscovery.new(task_config: task_config, namespace_tags: []) end - # rubocop:disable Metrics/LineLength - def full_response - %(NAME SHORTNAMES APIGROUP NAMESPACED KIND VERBS -componentstatuses cs false ComponentStatus [get list] -namespaces ns false Namespace [create delete get list patch update watch] -nodes no false Node [create delete deletecollection get list patch update watch] -persistentvolumes pv false PersistentVolume [create delete deletecollection get list patch update watch] -mutatingwebhookconfigurations admissionregistration.k8s.io false MutatingWebhookConfiguration [create delete deletecollection get list patch update watch] -validatingwebhookconfigurations admissionregistration.k8s.io false ValidatingWebhookConfiguration [create delete deletecollection get list patch update watch] -customresourcedefinitions crd,crds apiextensions.k8s.io false CustomResourceDefinition [create delete deletecollection get list patch update watch] -apiservices apiregistration.k8s.io false APIService [create delete deletecollection get list patch update watch] -tokenreviews authentication.k8s.io false TokenReview [create] -selfsubjectaccessreviews authorization.k8s.io false SelfSubjectAccessReview [create] -selfsubjectrulesreviews authorization.k8s.io false SelfSubjectRulesReview [create] -subjectaccessreviews authorization.k8s.io false SubjectAccessReview [create] -certificatesigningrequests csr certificates.k8s.io false CertificateSigningRequest [create delete deletecollection get list patch update watch] -podsecuritypolicies psp extensions false PodSecurityPolicy [create delete deletecollection get list patch update watch] -podsecuritypolicies psp policy false PodSecurityPolicy [create delete deletecollection get list patch update watch] -clusterrolebindings rbac.authorization.k8s.io false ClusterRoleBinding [create delete deletecollection get list patch update watch] -clusterroles rbac.authorization.k8s.io false ClusterRole [create delete deletecollection get list patch update watch] -priorityclasses pc scheduling.k8s.io false PriorityClass [create delete deletecollection get list patch update watch] -storageclasses sc storage.k8s.io false StorageClass [create delete deletecollection get list patch update watch] -volumeattachments storage.k8s.io false VolumeAttachment [create delete deletecollection get list patch update watch]) + def api_versions_full_response + File.read(File.join(fixture_path('for_unit_tests'), "api_versions.txt")) + end + + def api_resources_full_response + File.read(File.join(fixture_path('for_unit_tests'), "api_resources.txt")) end - # rubocop:enable Metrics/LineLength: end