Skip to content

Commit

Permalink
First pass at shared validation
Browse files Browse the repository at this point in the history
  • Loading branch information
dturn committed Aug 16, 2019
1 parent b3df20f commit 6477c39
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 15 deletions.
2 changes: 2 additions & 0 deletions lib/kubernetes-deploy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
require 'kubernetes-deploy/duration_parser'
require 'kubernetes-deploy/resource_cache'
require 'kubernetes-deploy/label_selector'
require 'kubernetes-deploy/task_config'
require 'kubernetes-deploy/validator'

module KubernetesDeploy
MIN_KUBE_VERSION = '1.10.0'
Expand Down
23 changes: 10 additions & 13 deletions lib/kubernetes-deploy/restart_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def initialize(deployment_name, response)
ANNOTATION = "shipit.shopify.io/restart"

def initialize(context:, namespace:, logger:, max_watch_seconds: nil)
@task_config = KubernetesDeploy::TaskConfig.new(context, namespace, logger)
@context = context
@namespace = namespace
@logger = logger
Expand All @@ -37,11 +38,14 @@ def perform!(deployments_names = nil, selector: nil)
@logger.reset

@logger.phase_heading("Initializing restart")
verify_namespace
deployments = identify_target_deployments(deployments_names, selector: selector)
if kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
@logger.warn(KubernetesDeploy::Errors.server_version_warning(kubectl.server_version))
errors = Validator.new(@task_config).errors
unless errors.empty?
@logger.summary.add_action("Configuration invalid")
@logger.summary.add_paragraph(errors.map { |err| "- #{err}" }.join("\n"))
raise KubernetesDeploy::TaskConfigurationError
end

deployments = identify_target_deployments(deployments_names, selector: selector)
@logger.phase_heading("Triggering restart by touching ENV[RESTARTED_AT]")
patch_kubeclient_deployments(deployments)

Expand Down Expand Up @@ -117,13 +121,6 @@ def build_watchables(kubeclient_resources, started)
end
end

def verify_namespace
kubeclient.get_namespace(@namespace)
@logger.info("Namespace #{@namespace} found in context #{@context}")
rescue Kubeclient::ResourceNotFoundError
raise NamespaceNotFoundError.new(@namespace, @context)
end

def patch_deployment_with_restart(record)
v1beta1_kubeclient.patch_deployment(
record.metadata.name,
Expand Down Expand Up @@ -178,15 +175,15 @@ def kubeclient
end

def kubectl
@kubectl ||= Kubectl.new(namespace: @namespace, context: @context, logger: @logger, log_failure_by_default: true)
@task_config.kubectl
end

def v1beta1_kubeclient
@v1beta1_kubeclient ||= kubeclient_builder.build_v1beta1_kubeclient(@context)
end

def kubeclient_builder
@kubeclient_builder ||= KubeclientBuilder.new
@task_config.kubeclient_builder
end
end
end
29 changes: 29 additions & 0 deletions lib/kubernetes-deploy/task_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true
module KubernetesDeploy
class TaskConfig
attr_reader :context, :namespace

def initialize(context, namespace, logger = nil)
@context = context
@namespace = namespace
@logger = logger
end

def logger
@logger ||= KubernetesDeploy::FormattedLogger.build(@namespace, @context)
end

def kubectl(log_failure_by_default: true)
@kubectl ||= Kubectl.new(
namespace: @namespace,
context: @context,
logger: logger,
log_failure_by_default: log_failure_by_default
)
end

def kubeclient_builder
@kubeclient ||= KubeclientBuilder.new
end
end
end
86 changes: 86 additions & 0 deletions lib/kubernetes-deploy/validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true
module KubernetesDeploy
class Validator
DEFAULT_VALIDATIONS = %i(
validate_kubeconfig
validate_context_exists
validate_namespace_exists
validate_server_version
).freeze

delegate :context, :namespace, :logger, :kubectl, :kubeclient_builder, to: :@task_config

def initialize(task_config)
@task_config = task_config
@errors = nil
end

def valid?
@errors = []
DEFAULT_VALIDATIONS.each do |validator_name|
send(validator_name)
break if @errors.present?
end
@errors.empty?
end

def errors
valid?
@errors
end

private

def validate_kubeconfig
@errors += kubeclient_builder.validate_config_files
end

def validate_context_exists
unless context.present?
return @errors << "Context can not be blank"
end

_, err, st = kubectl.run("config", "get-contexts", context, "-o", "name",
use_namespace: false, use_context: false, log_failure: false)

unless st.success?
@errors << if err.match("error: context #{context} not found")
"Context #{context} missing from your kubeconfig file(s)"
else
"Something went wrong. #{err} "
end
return
end

_, err, st = kubectl.run("get", "namespaces", "-o", "name",
use_namespace: false, log_failure: false)

unless st.success?
@errors << "Something went wrong connectting to #{context}. #{err} "
end
end

def validate_namespace_exists
unless namespace.present?
return @errors << "Namespace can not be blank"
end

_, err, st = kubectl.run("get", "namespace", "-o", "name", namespace,
use_namespace: false, log_failure: false)

unless st.success?
@errors << if err.match("Error from server [(]NotFound[)]: namespace")
"Cloud not find Namespace: #{namespace} in Context: #{context}"
else
"Could not connect to kubernetes cluster. #{err}"
end
end
end

def validate_server_version
if kubectl.server_version < Gem::Version.new(MIN_KUBE_VERSION)
logger.warn(KubernetesDeploy::Errors.server_version_warning(kubectl.server_version))
end
end
end
end
4 changes: 2 additions & 2 deletions test/integration/restart_task_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def test_restart_not_existing_context
assert_restart_failure(restart.perform(%w(web)))
assert_logs_match_all([
"Result: FAILURE",
"`walrus` context must be configured in your kubeconfig file(s)",
/- Context walrus missing from your kubeconfig file\(s\)/,
],
in_order: true)
end
Expand All @@ -188,7 +188,7 @@ def test_restart_not_existing_namespace
assert_restart_failure(restart.perform(%w(web)))
assert_logs_match_all([
"Result: FAILURE",
"Namespace `walrus` not found in context `#{TEST_CONTEXT}`",
"- Cloud not find Namespace: walrus in Context: #{KubeclientHelper::TEST_CONTEXT}",
],
in_order: true)
end
Expand Down
44 changes: 44 additions & 0 deletions test/integration/validator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'integration_test_helper'

class ValidatorTest < KubernetesDeploy::IntegrationTest
def test_valid_configuration
assert_predicate(validator(context: KubeclientHelper::TEST_CONTEXT, namespace: 'default'), :valid?)
end

def test_invalid_kubeconfig
assert_match(/Context test-context missing from/, validator.errors.join("\n"))
end

def test_context_does_not_exists
assert_match("Context test-context missing from your kubeconfig file(s)",
validator.errors.join("\n"))
end

def test_namespace_does_not_exists
assert_match(/Cloud not find Namespace: test-namespace in Context: #{KubeclientHelper::TEST_CONTEXT}/,
validator(context: KubeclientHelper::TEST_CONTEXT).errors.join("\n"))
end

def test_invalid_server_version
old_min_version = KubernetesDeploy::MIN_KUBE_VERSION
new_min_version = "99999"
KubernetesDeploy.const_set(:MIN_KUBE_VERSION, new_min_version)
validator(context: KubeclientHelper::TEST_CONTEXT, namespace: 'default', logger: @logger).valid?
assert_logs_match_all([
"Minimum cluster version requirement of #{new_min_version} not met.",
])
ensure
KubernetesDeploy.const_set(:MIN_KUBE_VERSION, old_min_version)
end

private

def validator(context: nil, namespace: nil, logger: nil)
KubernetesDeploy::Validator.new(task_config(context: context, namespace: namespace, logger: logger))
end

def task_config(context: nil, namespace: nil, logger: nil)
KubernetesDeploy::TaskConfig.new(context || "test-context", namespace || "test-namespace", logger)
end
end
27 changes: 27 additions & 0 deletions test/unit/task_config_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'test_helper'

class TaskConfigTest < KubernetesDeploy::TestCase
def test_responds_to_namespace
assert_equal(task_config.namespace, "test-namespace")
end

def test_responds_to_context
assert_equal(task_config.context, "test-context")
end

def test_builds_a_logger_if_none_provided
assert_equal(task_config.logger.class, KubernetesDeploy::FormattedLogger)
end

def test_uses_provided_logger
logger = KubernetesDeploy::FormattedLogger.build(nil, nil)
assert_equal(task_config(logger: logger).logger, logger)
end

private

def task_config(context: nil, namespace: nil, logger: nil)
KubernetesDeploy::TaskConfig.new(context || "test-context", namespace || "test-namespace", logger)
end
end

0 comments on commit 6477c39

Please sign in to comment.