Skip to content

Commit

Permalink
Global command with a ResourceDeployer class
Browse files Browse the repository at this point in the history
Use resource_deployer for normal deploys

Add a common module

Add tests
  • Loading branch information
dturn committed Oct 29, 2019
1 parent 50e7217 commit 09c68db
Show file tree
Hide file tree
Showing 29 changed files with 956 additions and 345 deletions.
1 change: 1 addition & 0 deletions lib/krane.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
require 'krane/render_task'
require 'krane/restart_task'
require 'krane/runner_task'
require 'krane/global_deploy_task'
44 changes: 44 additions & 0 deletions lib/krane/cli/global_deploy_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module Krane
module CLI
class GlobalDeployCommand
DEFAULT_DEPLOY_TIMEOUT = '300s'
OPTIONS = {
"filenames" => { type: :array, banner: 'config/deploy/production config/deploy/my-extra-resource.yml',
aliases: :f, required: true,
desc: "Directories and files that contains the configuration to apply" },
"global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
desc: "Max duration to monitor workloads correctly deployed" },
"verify-result" => { type: :boolean, default: true,
desc: "Verify workloads correctly deployed" },
"selector" => { type: :string, banner: "'label=value'", required: true,
desc: "Select workloads owned by selector(s)" },
}

def self.from_options(context, options)
require 'krane/global_deploy_task'
require 'krane/options_helper'
require 'krane/label_selector'
require 'krane/duration_parser'

selector = ::Krane::LabelSelector.parse(options[:selector])

::Krane::OptionsHelper.with_processed_template_paths(options[:filenames],
require_explicit_path: true) do |paths|
deploy = ::Krane::GlobalDeployTask.new(
context: context,
filenames: paths,
global_timeout: ::Krane::DurationParser.new(options["global-timeout"]).parse!.to_i,
selector: selector,
)

deploy.run!(
verify_result: options["verify-result"],
prune: false,
)
end
end
end
end
end
9 changes: 9 additions & 0 deletions lib/krane/cli/krane.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'krane/cli/run_command'
require 'krane/cli/render_command'
require 'krane/cli/deploy_command'
require 'krane/cli/global_deploy_command'

module Krane
module CLI
Expand Down Expand Up @@ -58,6 +59,14 @@ def deploy(namespace, context)
end
end

desc("global-deploy CONTEXT", "Ship non-namespaced resources to a cluster")
expand_options(GlobalDeployCommand::OPTIONS)
def global_deploy(context)
rescue_and_exit do
GlobalDeployCommand.from_options(context, options)
end
end

def self.exit_on_failure?
true
end
Expand Down
6 changes: 4 additions & 2 deletions lib/krane/cluster_resource_discovery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def global_resource_kinds
private

def fetch_globals
raw, _, st = kubectl.run("api-resources", "--namespaced=false", output: "wide", attempts: 5)
raw, _, st = kubectl.run("api-resources", "--namespaced=false", output: "wide", attempts: 5,
use_namespace: false)
if st.success?
rows = raw.split("\n")
header = rows[0]
Expand All @@ -42,7 +43,8 @@ def fetch_globals
end

def fetch_crds
raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5)
raw_json, _, st = kubectl.run("get", "CustomResourceDefinition", output: "json", attempts: 5,
use_namespace: false)
if st.success?
JSON.parse(raw_json)["items"]
else
Expand Down
29 changes: 29 additions & 0 deletions lib/krane/concerns/template_reporting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Krane
module TemplateReporting
def record_invalid_template(logger:, err:, filename:, content: nil)
debug_msg = ColorizedString.new("Invalid template: #{filename}\n").red
debug_msg += "> Error message:\n#{Krane::FormattedLogger.indent_four(err)}"
if content
debug_msg += if content =~ /kind:\s*Secret/
"\n> Template content: Suppressed because it may contain a Secret"
else
"\n> Template content:\n#{Krane::FormattedLogger.indent_four(content)}"
end
end
logger.summary.add_paragraph(debug_msg)
end

def record_warnings(logger:, warning:, filename:)
warn_msg = "Template warning: #{filename}\n"
warn_msg += "> Warning message:\n#{Krane::FormattedLogger.indent_four(warning)}"
logger.summary.add_paragraph(ColorizedString.new(warn_msg).yellow)
end

def add_para_from_list(logger:, action:, enum:)
logger.summary.add_action(action)
logger.summary.add_paragraph(enum.map { |e| "- #{e}" }.join("\n"))
end
end
end
44 changes: 44 additions & 0 deletions lib/krane/deploy_task_config_validator.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# frozen_string_literal: true
require 'krane/concerns/template_reporting'

module Krane
class DeployTaskConfigValidator < TaskConfigValidator
include Krane::TemplateReporting

def initialize(protected_namespaces, allow_protected_ns, prune, *arguments)
super(*arguments)
@protected_namespaces = protected_namespaces
Expand All @@ -9,8 +13,48 @@ def initialize(protected_namespaces, allow_protected_ns, prune, *arguments)
@validations += %i(validate_protected_namespaces)
end

def validate_resources(resources, selector, allow_globals)
validate_globals(resources, allow_globals)
Krane::Concurrency.split_across_threads(resources) do |r|
r.validate_definition(@kubectl, selector: selector)
end

resources.select(&:has_warnings?).each do |resource|
record_warnings(logger: logger, warning: resource.validation_warning_msg,
filename: File.basename(resource.file_path))
end

failed_resources = resources.select(&:validation_failed?)
if failed_resources.present?
failed_resources.each do |r|
content = File.read(r.file_path) if File.file?(r.file_path) && !r.sensitive_template_content?
record_invalid_template(logger: logger, err: r.validation_error_msg,
filename: File.basename(r.file_path), content: content)
end
raise Krane::FatalDeploymentError, "Template validation failed"
end
end

private

def validate_globals(resources, allow_globals)
return unless (global = resources.select(&:global?).presence)
global_names = global.map do |resource|
"#{resource.name} (#{resource.type}) in #{File.basename(resource.file_path)}"
end
global_names = FormattedLogger.indent_four(global_names.join("\n"))

if allow_globals
msg = "The ability for this task to deploy global resources will be removed in the next version,"\
" which will affect the following resources:"
msg += "\n#{global_names}"
logger.summary.add_paragraph(ColorizedString.new(msg).yellow)
else
logger.summary.add_paragraph(ColorizedString.new("Global resources:\n#{global_names}").yellow)
raise FatalDeploymentError, "This command is namespaced and cannot be used to deploy global resources."
end
end

def validate_protected_namespaces
if @protected_namespaces.include?(namespace)
if @allow_protected_ns && @prune
Expand Down
Loading

0 comments on commit 09c68db

Please sign in to comment.