Skip to content

Commit

Permalink
Runtime resource discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanmb committed May 18, 2018
1 parent 1457445 commit 8a1aba5
Show file tree
Hide file tree
Showing 40 changed files with 721 additions and 90 deletions.
5 changes: 5 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ IFS=$'\n\t'

bundle install

if [ ! -x "$(which jq)" ]; then
echo -e "\n\033[0;33mPlease install jq: https://stedolan.github.io/jq/download/\033[0m"
exit 1
fi

if [ ! -x "$(which minikube)" ]; then
echo -e "\n\033[0;33mIf you're going to run the tests, please follow the minikube setup instructions for your operating system:\nhttps://kubernetes.io/docs/getting-started-guides/minikube/#installation\033[0m"
fi
Expand Down
7 changes: 6 additions & 1 deletion bin/test
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ if [[ ${PARALLELISM:=0} -lt 1 ]]; then
fi
fi

if [[ ${CI:="0"} == "1" ]]; then
echo "--- :linux: Install jq"
wget -qP /usr/local/bin http://stedolan.github.io/jq/download/linux64/jq && chmod a+x /usr/local/bin/jq
fi

if [[ ${CI:="0"} == "1" ]]; then
echo "--- :ruby: Bundle Install"
bundle install --jobs 4
bundle install --jobs 4 || exit 1
fi

print_header "Run Unit Tests"
Expand Down
1 change: 1 addition & 0 deletions dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ up:
- bundler
- homebrew:
- Caskroom/cask/minikube
- jq
- custom:
name: Minikube Cluster
met?: test $(minikube status | grep Running | wc -l) -eq 2 && $(minikube status | grep -q 'Correctly Configured')
Expand Down
1 change: 1 addition & 0 deletions kubernetes-deploy.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "ejson", "1.0.1"
spec.add_dependency "colorize", "~> 0.8"
spec.add_dependency "statsd-instrument", "~> 2.1"
spec.add_dependency "rgl", "0.5.3"

spec.add_development_dependency "bundler"
spec.add_development_dependency "rake", "~> 10.0"
Expand Down
94 changes: 61 additions & 33 deletions lib/kubernetes-deploy/deploy_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
require 'shellwords'
require 'tempfile'
require 'fileutils'
require 'rgl/adjacency'
require 'rgl/topsort'
require 'kubernetes-deploy/discoverable_resource'
require 'kubernetes-deploy/kubernetes_resource'

%w(
cloudsql
config_map
Expand All @@ -27,6 +31,7 @@
bucket
stateful_set
cron_job
customresourcedefinition
).each do |subresource|
require "kubernetes-deploy/kubernetes_resource/#{subresource}"
end
Expand All @@ -40,16 +45,6 @@ module KubernetesDeploy
class DeployTask
include KubeclientBuilder

PREDEPLOY_SEQUENCE = %w(
ResourceQuota
Cloudsql
Redis
Memcached
ConfigMap
PersistentVolumeClaim
ServiceAccount
Pod
)
PROTECTED_NAMESPACES = %w(
default
kube-system
Expand All @@ -64,23 +59,48 @@ class DeployTask
# extensions/v1beta1/ReplicaSet -- managed by deployments
# core/v1/Secret -- should not committed / managed by shipit
def prune_whitelist
wl = %w(
core/v1/ConfigMap
core/v1/Pod
core/v1/Service
core/v1/ResourceQuota
batch/v1/Job
extensions/v1beta1/DaemonSet
extensions/v1beta1/Deployment
apps/v1beta1/Deployment
extensions/v1beta1/Ingress
apps/v1beta1/StatefulSet
autoscaling/v1/HorizontalPodAutoscaler
)
if server_version >= Gem::Version.new('1.8.0')
wl << "batch/v1beta1/CronJob"
@prune_whitelist ||= _build_prune_whitelist
end

def _build_prune_whitelist
prunable_resources = all_resources.select(&:prunable?)
prunable_resources.map(&:qualified_kind)
end

def predeploy_sequence
@predeploy_sequence ||= _build_predeploy_sequence
end

def _build_predeploy_sequence
# Express dependencies as DAG
graph = RGL::DirectedAdjacencyGraph.new
graph.add_vertex(:ROOT_NODE)

# This is a partially ordered set, so we must make sure everything is reachable:
predeploy_resources = all_resources.select(&:predeploy?)
predeploy_resources.each { |res| graph.add_edge(:ROOT_NODE, res.kind) }

# Find resources that have explicit predeploy (inter-)dependencies:
predeploy_res_with_deps = all_resources.select(&:predeploy_dependencies)
predeploy_res_with_deps.each do |res|
# Edge [A,B] means B requires A to be deployed first
res.predeploy_dependencies.each { |dep| graph.add_edge(dep, res.kind) }
end

raise FatalDeploymentError, "Cyclic predeploy requirements: #{graph.cycles.flatten}" unless graph.cycles.empty?

# Topological sort is not unique, but will respect the requirements
predeploy_order = graph.topsort_iterator.to_a
predeploy_order.delete(:ROOT_NODE)
predeploy_order
end

def all_resources
resources = DiscoverableResource.all + KubernetesResource.all
# Omit unqualified kinds (they are unsupported on this cluster, or discovery hasn't been performed yet)
resources.select do |res|
res.constants.include?(:GROUP) && res.constants.include?(:VERSION)
end
wl
end

def server_version
Expand Down Expand Up @@ -124,7 +144,8 @@ def run!(verify_result: true, allow_protected_ns: false, prune: true)
confirm_context_exists
confirm_namespace_exists
@namespace_tags |= tags_from_namespace_labels
resources = discover_resources
discover_resources
resources = load_resource_from_file
validate_definitions(resources)

@logger.phase_heading("Checking initial resource statuses")
Expand Down Expand Up @@ -190,12 +211,12 @@ def run!(verify_result: true, allow_protected_ns: false, prune: true)
private

def deploy_has_priority_resources?(resources)
resources.any? { |r| PREDEPLOY_SEQUENCE.include?(r.type) }
resources.any? { |r| predeploy_sequence.include?(r.kind) }
end

def predeploy_priority_resources(resource_list)
PREDEPLOY_SEQUENCE.each do |resource_type|
matching_resources = resource_list.select { |r| r.type == resource_type }
predeploy_sequence.each do |resource_kind|
matching_resources = resource_list.select { |r| r.kind == resource_kind }
next if matching_resources.empty?
deploy_resources(matching_resources, verify: true, record_summary: false)

Expand Down Expand Up @@ -225,15 +246,22 @@ def validate_definitions(resources)
end

def discover_resources
# (Lazily) rebuild these lists after discovery if they were present.
@predeploy_sequence = nil
@prune_whitelist = nil
DiscoverableResource.discover(context: @context, logger: @logger, server_version: server_version)
end

def load_resource_from_file
resources = []
@logger.info("Discovering templates:")

Dir.foreach(@template_dir) do |filename|
next unless filename.end_with?(".yml.erb", ".yml", ".yaml", ".yaml.erb")

split_templates(filename) do |r_def|
r = KubernetesResource.build(namespace: @namespace, context: @context, logger: @logger,
definition: r_def, statsd_tags: @namespace_tags)
r = DiscoverableResource.build(namespace: @namespace, context: @context, logger: @logger,
definition: r_def, statsd_tags: @namespace_tags)
resources << r
@logger.info " - #{r.id}"
end
Expand Down Expand Up @@ -385,7 +413,7 @@ def apply_all(resources, prune)

if prune
command.push("--prune", "--all")
prune_whitelist.each { |type| command.push("--prune-whitelist=#{type}") }
prune_whitelist.each { |kind| command.push("--prune-whitelist=#{kind}") }
end

out, err, st = kubectl.run(*command, log_failure: false)
Expand Down
Loading

0 comments on commit 8a1aba5

Please sign in to comment.