Skip to content

Commit

Permalink
rewrite me
Browse files Browse the repository at this point in the history
  • Loading branch information
renescheepers committed Aug 23, 2022
1 parent f0e0da7 commit a956fac
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 114 deletions.
6 changes: 4 additions & 2 deletions lib/krane/api_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 36 additions & 47 deletions lib/krane/cluster_resource_discovery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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?/)(?<group>[^/]*)/?(?<version>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
Expand Down
15 changes: 12 additions & 3 deletions lib/krane/deploy_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions lib/krane/global_deploy_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 34 additions & 40 deletions lib/krane/kubernetes_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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?

Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions lib/krane/resource_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 0 additions & 4 deletions lib/krane/task_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions test/helpers/fixture_deploy_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
20 changes: 10 additions & 10 deletions test/integration/krane_deploy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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|
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit a956fac

Please sign in to comment.