From a956fac1192926f1b99521fd9a9378c085858213 Mon Sep 17 00:00:00 2001 From: Rene Scheepers Date: Wed, 17 Aug 2022 20:18:26 +0200 Subject: [PATCH] rewrite me --- lib/krane/api_resource.rb | 6 +- lib/krane/cluster_resource_discovery.rb | 83 +++++++++++-------------- lib/krane/deploy_task.rb | 15 ++++- lib/krane/global_deploy_task.rb | 12 +++- lib/krane/kubernetes_resource.rb | 74 ++++++++++------------ lib/krane/resource_cache.rb | 7 ++- lib/krane/task_config.rb | 4 -- test/helpers/fixture_deploy_helper.rb | 9 ++- test/integration/krane_deploy_test.rb | 20 +++--- 9 files changed, 116 insertions(+), 114 deletions(-) diff --git a/lib/krane/api_resource.rb b/lib/krane/api_resource.rb index 78ac6b380..3aee049a4 100644 --- a/lib/krane/api_resource.rb +++ b/lib/krane/api_resource.rb @@ -2,12 +2,14 @@ module Krane class APIResource - attr_reader :group, :kind, :namespaced + attr_reader :group, :kind, :version, :namespaced, :verbs - def initialize(group, kind, namespaced) + def initialize(group, kind, version, namespaced, verbs) @group = group @kind = kind + @version = version @namespaced = namespaced + @verbs = verbs end def group_kind diff --git a/lib/krane/cluster_resource_discovery.rb b/lib/krane/cluster_resource_discovery.rb index 8962e7278..d96a1951b 100644 --- a/lib/krane/cluster_resource_discovery.rb +++ b/lib/krane/cluster_resource_discovery.rb @@ -20,47 +20,29 @@ def crds def prunable_resources(namespaced:) black_list = %w(Namespace Node ControllerRevision) - fetch_resources(namespaced: namespaced).map do |resource| - next unless resource["verbs"].one? { |v| v == "delete" } - next if black_list.include?(resource["kind"]) - [resource["apigroup"], resource["version"], resource["kind"]].compact.join("/") + fetch_resources.map do |resource| + next unless namespaced == resource.namespaced + next unless resource.verbs.one? { |v| v == "delete" } + next if black_list.include?(resource.kind) + + [ + resource.group, + resource.version, + resource.kind, + ].compact.join("/") end.compact end - def fetch_resources(namespaced: false) - responses = Concurrent::Hash.new - Krane::Concurrency.split_across_threads(api_paths) do |path| - responses[path] = fetch_api_path(path)["resources"] || [] - end - responses.flat_map do |path, resources| - resources.map { |r| resource_hash(path, namespaced, r) } - end.compact.uniq { |r| "#{r['apigroup']}/#{r['kind']}" } - end - - def fetch_group_kinds - output, err, st = kubectl.run("api-resources", "--no-headers=true", attempts: 2, use_namespace: false) - if st.success? - output.split("\n").map do |l| - matches = l.scan(/\S+/) - - if matches.length == 4 - # name, api, namespaced, kind - ::Krane::APIResource.new( - ::Krane::KubernetesResource.group_from_api_version(matches[1]), - matches[3], - matches[2] == "true" - ) - else - # name, shortname, api, namespaced, kind - ::Krane::APIResource.new( - ::Krane::KubernetesResource.group_from_api_version(matches[2]), - matches[4], - matches[3] == "true" - ) - end + def fetch_resources + @fetch_resources ||= begin + responses = Concurrent::Hash.new + Krane::Concurrency.split_across_threads(api_paths) do |path| + responses[path] = fetch_api_path(path)["resources"] || [] end - else - raise FatalKubeAPIError, "Error retrieving group kinds: #{err}" + + responses.flat_map do |path, resources| + resources.map { |r| resource_hash(path, r) } + end.compact.uniq(&:group_kind) end end @@ -114,23 +96,30 @@ def fetch_api_path(path) end end - def resource_hash(path, namespaced, blob) - return unless blob["namespaced"] == namespaced + def resource_hash(path, blob) # skip sub-resources return if blob["name"].include?("/") + path_regex = %r{(/apis?/)(?[^/]*)/?(?v.+)} match = path.match(path_regex) - { - "verbs" => blob["verbs"], - "kind" => blob["kind"], - "apigroup" => match[:group], - "version" => match[:version], - } + ::Krane::APIResource.new( + match[:group], + blob["kind"], + match[:version], + blob["namespaced"], + blob["verbs"] + ) end def fetch_crds - raw_json, err, st = kubectl.run("get", "CustomResourceDefinition.apiextensions.k8s.io", output: "json", attempts: 5, - use_namespace: false) + raw_json, err, st = kubectl.run( + "get", + "CustomResourceDefinition.apiextensions.k8s.io", + output: "json", + attempts: 5, + use_namespace: false + ) + if st.success? JSON.parse(raw_json)["items"] else diff --git a/lib/krane/deploy_task.rb b/lib/krane/deploy_task.rb index 5e992dbce..a4cf1257e 100644 --- a/lib/krane/deploy_task.rb +++ b/lib/krane/deploy_task.rb @@ -85,6 +85,7 @@ def initialize(namespace:, context:, current_sha: nil, logger: nil, kubectl_inst render_erb: false, kubeconfig: nil) @logger = logger || Krane::FormattedLogger.build(namespace, context) @template_sets = TemplateSets.from_dirs_and_files(paths: filenames, logger: @logger, render_erb: render_erb) + @task_config = Krane::TaskConfig.new(context, namespace, @logger, kubeconfig) @bindings = bindings @namespace = namespace @@ -211,14 +212,22 @@ def discover_resources @logger.info("Discovering resources:") resources = [] crds_grouped = cluster_resource_discoverer.crds.group_by(&:cr_group_kind) - group_kinds = @task_config.group_kinds + api_resources = cluster_resource_discoverer.fetch_resources @template_sets.with_resource_definitions(current_sha: @current_sha, bindings: @bindings) do |r_def| group = ::Krane::KubernetesResource.group_from_api_version(r_def["apiVersion"]) crd = crds_grouped[::Krane::KubernetesResource.combine_group_kind(group, r_def["kind"])]&.first - r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger, definition: r_def, - statsd_tags: @namespace_tags, crd: crd, group_kinds: group_kinds) + r = KubernetesResource.build( + namespace: @namespace, + context: @context, + logger: @logger, + definition: r_def, + statsd_tags: @namespace_tags, + crd: crd, + api_resources: api_resources, + ) + resources << r @logger.info(" - #{r.pretty_id}") end diff --git a/lib/krane/global_deploy_task.rb b/lib/krane/global_deploy_task.rb index 94ee42892..d27ad3115 100644 --- a/lib/krane/global_deploy_task.rb +++ b/lib/krane/global_deploy_task.rb @@ -160,15 +160,21 @@ def validate_globals(resources) def discover_resources logger.info("Discovering resources:") resources = [] - group_kinds = @task_config.group_kinds + api_resources = cluster_resource_discoverer.fetch_resources crds_grouped = cluster_resource_discoverer.crds.group_by(&:group_kind) @template_sets.with_resource_definitions do |r_def| group = ::Krane::KubernetesResource.group_from_api_version(r_def["apiVersion"]) crd = crds_grouped[::Krane::KubernetesResource.combine_group_kind(group, r_def["kind"])]&.first - r = KubernetesResource.build(context: context, logger: logger, definition: r_def, - crd: crd, group_kinds: group_kinds, statsd_tags: statsd_tags) + r = KubernetesResource.build( + context: context, + logger: logger, + definition: r_def, + crd: crd, + api_resources: api_resources, + statsd_tags: statsd_tags + ) resources << r logger.info(" - #{r.pretty_id}") end diff --git a/lib/krane/kubernetes_resource.rb b/lib/krane/kubernetes_resource.rb index 0de6faf42..0a9fc066b 100644 --- a/lib/krane/kubernetes_resource.rb +++ b/lib/krane/kubernetes_resource.rb @@ -44,7 +44,7 @@ class KubernetesResource SYNC_DEPENDENCIES = [] class << self - def build(namespace: nil, context:, definition:, logger:, statsd_tags:, crd: nil, group_kinds: []) + def build(namespace: nil, context:, definition:, logger:, statsd_tags:, crd: nil, api_resources: []) validate_definition_essentials(definition) group = ::Krane::KubernetesResource.group_from_api_version(definition["apiVersion"]) @@ -65,7 +65,7 @@ def build(namespace: nil, context:, definition:, logger:, statsd_tags:, crd: nil end inst.global = GLOBAL - if (entry = group_kinds.find { |x| x.group_kind == group_kind }) + if (entry = api_resources.find { |x| x.group_kind == group_kind }) inst.global = !entry.namespaced end inst @@ -97,19 +97,43 @@ def group def kind # Converts Krane::ApiextensionsK8sIo::CustomResourceDefinition to CustomResourceDefinition - if name.scan(/::/).length == 1 - _, c_kind = name.split("::", 2) - else - _, _, c_kind = name.split("::", 3) - end - - c_kind + name.demodulize end def group_kind ::Krane::KubernetesResource.combine_group_kind(group, kind) end + def group_from_api_version(input) + input.include?("/") ? input.split("/").first : "" + end + + def combine_group_kind(group, kind) + "#{kind}.#{group}" + end + + def group_kind_to_const(group_kind) + kind, group = group_kind.split(".", 2) + + group = group.split(".").map(&:capitalize).join("") + + if group == "" + group_const = ::Krane + elsif ::Krane.const_defined?(group) + group_const = ::Krane.const_get(group) + else + return nil + end + + unless group_const.const_defined?(kind) + return nil + end + + group_const.const_get(kind) + rescue NameError => _ + nil + end + private def validate_definition_essentials(definition) @@ -361,7 +385,7 @@ def debug_message(cause = nil, info_hash = {}) def fetch_events(kubectl) return {} unless exists? - out, err, st = kubectl.run("get", "events", "--output=go-template=#{Event.go_template_for(group, kind, name)}", + out, _err, st = kubectl.run("get", "events", "--output=go-template=#{Event.go_template_for(group, kind, name)}", log_failure: false, use_namespace: !global?) return {} unless st.success? @@ -547,36 +571,6 @@ def selected?(selector) selector.nil? || selector.to_h <= labels end - def self.group_from_api_version(input) - input.include?("/") ? input.split("/").first : "" - end - - def self.combine_group_kind(group, kind) - "#{kind}.#{group}" - end - - def self.group_kind_to_const(group_kind) - kind, group = group_kind.split(".", 2) - - group = group.split(".").map(&:capitalize).join("") - - if group == "" - group_const = ::Krane - elsif ::Krane.const_defined?(group) - group_const = ::Krane.const_get(group) - else - return nil - end - - unless group_const.const_defined?(kind) - return nil - end - - group_const.const_get(kind) - rescue NameError => _ - nil - end - private def validate_timeout_annotation diff --git a/lib/krane/resource_cache.rb b/lib/krane/resource_cache.rb index 5ca599c60..eac362557 100644 --- a/lib/krane/resource_cache.rb +++ b/lib/krane/resource_cache.rb @@ -42,7 +42,10 @@ def prewarm(resources) end group_kinds = (resources.map(&:group_kind) + sync_dependencies).uniq - Krane::Concurrency.split_across_threads(group_kinds, max_threads: group_kinds.count) { |group_kind| get_all(group_kind) } + Krane::Concurrency.split_across_threads( + group_kinds, + max_threads: group_kinds.count + ) { |group_kind| get_all(group_kind) } end private @@ -59,7 +62,7 @@ def use_or_populate_cache(group_kind) end def fetch_by_group_kind(group_kind) - group_kind_meta = @task_config.group_kinds.find { |g| g.group_kind == group_kind } + group_kind_meta = @task_config.cluster_resource_discoverer.fetch_resources.find { |g| g.group_kind == group_kind } resource_class = ::Krane::KubernetesResource.group_kind_to_const(group_kind) output_is_sensitive = resource_class.nil? ? false : resource_class::SENSITIVE_TEMPLATE_CONTENT diff --git a/lib/krane/task_config.rb b/lib/krane/task_config.rb index 5d2670123..47bc9c8ba 100644 --- a/lib/krane/task_config.rb +++ b/lib/krane/task_config.rb @@ -13,10 +13,6 @@ def initialize(context, namespace, logger = nil, kubeconfig = nil) @kubeconfig = kubeconfig || ENV['KUBECONFIG'] end - def group_kinds - @group_kinds ||= cluster_resource_discoverer.fetch_group_kinds - end - def kubeclient_builder @kubeclient_builder ||= KubeclientBuilder.new(kubeconfig: kubeconfig) end diff --git a/test/helpers/fixture_deploy_helper.rb b/test/helpers/fixture_deploy_helper.rb index 7f320cdcc..56d8820ad 100644 --- a/test/helpers/fixture_deploy_helper.rb +++ b/test/helpers/fixture_deploy_helper.rb @@ -42,12 +42,15 @@ def deploy_fixtures(set, subset: nil, **args) # extra args are passed through to success end - def deploy_global_fixtures(set, subset: nil, selector: nil, verify_result: true, prune: true, global_timeout: 300) + def deploy_global_fixtures(set, subset: nil, selector: nil, verify_result: true, prune: true, global_timeout: 300, + apply_scope_to_resources: true) fixtures = load_fixtures(set, subset) raise "Cannot deploy empty template set" if fixtures.empty? - selector = (selector == false ? "" : "#{selector},app=krane,test=#{@namespace}".sub(/^,/, '')) - apply_scope_to_resources(fixtures, labels: selector) + if apply_scope_to_resources + selector = (selector == false ? "" : "#{selector},app=krane,test=#{@namespace}".sub(/^,/, '')) + apply_scope_to_resources(fixtures, labels: selector) + end yield fixtures if block_given? diff --git a/test/integration/krane_deploy_test.rb b/test/integration/krane_deploy_test.rb index 58e12bf1f..36b0c7012 100644 --- a/test/integration/krane_deploy_test.rb +++ b/test/integration/krane_deploy_test.rb @@ -40,20 +40,20 @@ def test_deploy_fails_with_empty_yaml assert_deploy_failure(deploy_raw_fixtures("empty-resources", subset: %w[empty1.yml empty2.yml])) assert_logs_match_all([ - "All required parameters and files are present", - "Result: FAILURE", - "No deployable resources were found!", - ], in_order: true) + "All required parameters and files are present", + "Result: FAILURE", + "No deployable resources were found!", + ], in_order: true) end def test_deploy_fails_with_empty_erb assert_deploy_failure(deploy_raw_fixtures("empty-resources", subset: %w[empty3.yml.erb empty4.yml.erb], render_erb: true)) assert_logs_match_all([ - "All required parameters and files are present", - "Result: FAILURE", - "No deployable resources were found!", - ], in_order: true) + "All required parameters and files are present", + "Result: FAILURE", + "No deployable resources were found!", + ], in_order: true) end def test_service_account_predeployed_before_unmanaged_pod @@ -135,7 +135,7 @@ def test_pruning_works prune_matcher("role", "rbac.authorization.k8s.io", "role"), prune_matcher("rolebinding", "rbac.authorization.k8s.io", "role-binding"), prune_matcher("persistentvolumeclaim", "", "hello-pv-claim"), - prune_matcher("ingress", %w(networking.k8s.io extensions), "web") + prune_matcher("ingress", %w(networking.k8s.io extensions), "web"), ] # not necessarily listed in this order expected_msgs = [/Pruned 2[013] resources and successfully deployed 6 resources/] expected_pruned.map do |resource| @@ -1827,7 +1827,7 @@ def test_succeeds_with_deploy_method_override end def test_duplicate_kind_resource_definition - result = deploy_global_fixtures("crd", subset: ["deployment.yml"]) + result = deploy_global_fixtures("crd", subset: ["deployment.yml"], apply_scope_to_resources: false, selector: "app=krane") assert_deploy_success(result) result = deploy_fixtures("crd", subset: ["web.yml"], global_timeout: 30)