lib/configgin.rb in configgin-0.18.4 vs lib/configgin.rb in configgin-0.18.5
- old
+ new
@@ -1,28 +1,33 @@
+require 'json'
+
require_relative 'cli'
require_relative 'job'
require_relative 'environment_config_transmogrifier'
require_relative 'bosh_deployment_manifest_config_transmogrifier'
require_relative 'kube_link_generator'
require_relative 'bosh_deployment_manifest'
+require_relative 'property_digest'
# Configgin is the main class which puts all the pieces together and configures
# the container according to the options.
class Configgin
# SVC_ACC_PATH is the location of the service account secrets
SVC_ACC_PATH = '/var/run/secrets/kubernetes.io/serviceaccount'.freeze
- def initialize(options)
- @job_configs = JSON.parse(File.read(options[:jobs]))
- @templates = YAML.load_file(options[:env2conf])
- @bosh_deployment_manifest = options[:bosh_deployment_manifest]
+ def initialize(jobs:, env2conf:, bosh_deployment_manifest:, self_name: ENV['HOSTNAME'])
+ @job_configs = JSON.parse(File.read(jobs))
+ @templates = YAML.load_file(env2conf)
+ @bosh_deployment_manifest = bosh_deployment_manifest
+ @self_name = self_name
end
def run
jobs = generate_jobs(@job_configs, @templates)
- set_job_metadata(jobs)
+ job_digests = patch_job_metadata(jobs)
render_job_templates(jobs, @job_configs)
+ restart_affected_pods expected_annotations(@job_configs, job_digests)
end
def generate_jobs(job_configs, templates)
jobs = {}
job_configs.each do |job, job_config|
@@ -39,23 +44,41 @@
STDERR.puts e.to_s
STDERR.puts "Error generating #{job}: #{outfile} from #{infile}"
exit 1
end
- jobs[job] = Job.new(bosh_spec, kube_namespace, kube_client, kube_client_stateful_set)
+ jobs[job] = Job.new(
+ spec: bosh_spec,
+ namespace: kube_namespace,
+ client: kube_client,
+ client_stateful_set: kube_client_stateful_set,
+ self_name: @self_name
+ )
end
jobs
end
- def set_job_metadata(jobs)
+ # Set the exported properties and their digests, and return the digests.
+ def patch_job_metadata(jobs)
+ digests = {}
jobs.each do |name, job|
+ digest = property_digest(job.exported_properties)
kube_client.patch_pod(
- ENV['HOSTNAME'],
- { metadata: { annotations: { :"skiff-exported-properties-#{name}" => job.exported_properties.to_json } } },
+ @self_name,
+ {
+ metadata: {
+ annotations: {
+ :"skiff-exported-properties-#{name}" => job.exported_properties.to_json,
+ :"skiff-exported-digest-#{name}" => digest
+ }
+ }
+ },
kube_namespace
)
+ digests[name] = digest
end
+ digests
end
def render_job_templates(jobs, job_configs)
jobs.each do |job_name, job|
dns_encoder = KubeDNSEncoder.new(job.spec['links'])
@@ -64,55 +87,95 @@
job.generate(infile, outfile, dns_encoder)
end
end
end
+ # Some pods might have depended on the properties exported by this pod; given
+ # the annotations expected on the pods (keyed by the instance group name),
+ # patch the StatefulSets such that they will be restarted.
+ def restart_affected_pods(expected_annotations)
+ expected_annotations.each_pair do |instance_group_name, digests|
+ begin
+ kube_client_stateful_set.patch_stateful_set(
+ instance_group_name,
+ { spec: { template: { metadata: { annotations: digests } } } },
+ kube_namespace
+ )
+ warn "Patched StatefulSet #{instance_group_name} for new exported digests"
+ rescue KubeException => e
+ begin
+ begin
+ response = JSON.parse(e.response || '')
+ rescue JSON::ParseError
+ response = {}
+ end
+ if response['reason'] == 'NotFound'
+ # The StatefulSet can be missing if we're configured to not have an
+ # optional instance group.
+ warn "Skipping patch of non-existant StatefulSet #{instance_group_name}"
+ next
+ end
+ warn "Error patching #{instance_group_name}: #{response.to_json}"
+ raise
+ end
+ end
+ end
+ end
+
+ # Given the active jobs, and a hash of the expected annotations for each,
+ # return the annotations we expect to be on each pod based on what properties
+ # each job imports.
+ def expected_annotations(job_configs, job_digests)
+ instance_groups_to_examine = Hash.new { |h, k| h[k] = {} }
+ job_configs.values.each do |job_config|
+ base_config = JSON.parse(File.read(job_config['base']))
+ base_config.fetch('consumed_by', {}).each_pair do |provider_name, consumer_jobs|
+ consumer_jobs.each do |consumer_job|
+ digest_key = "skiff-imported-properties-#{instance_group}-#{provider_name}"
+ instance_groups_to_examine[consumer_job['role']][digest_key] = job_digests[provider_name]
+ end
+ end
+ end
+ instance_groups_to_examine
+ end
+
def kube_namespace
@kube_namespace ||= File.read("#{SVC_ACC_PATH}/namespace")
end
def kube_token
@kube_token ||= File.read("#{SVC_ACC_PATH}/token")
end
private
- def kube_client
- @kube_client ||= Kubeclient::Client.new(
+ def create_kube_client(path: nil, version: 'v1')
+ Kubeclient::Client.new(
URI::HTTPS.build(
host: ENV['KUBERNETES_SERVICE_HOST'],
- port: ENV['KUBERNETES_SERVICE_PORT_HTTPS']
+ port: ENV['KUBERNETES_SERVICE_PORT_HTTPS'],
+ path: path
),
- 'v1',
+ version,
ssl_options: {
ca_file: "#{SVC_ACC_PATH}/ca.crt",
verify_ssl: OpenSSL::SSL::VERIFY_PEER
},
auth_options: {
bearer_token: kube_token
}
)
end
+ def kube_client
+ @kube_client ||= create_kube_client
+ end
+
def kube_client_stateful_set
- @kube_client_stateful_set ||= Kubeclient::Client.new(
- URI::HTTPS.build(
- host: ENV['KUBERNETES_SERVICE_HOST'],
- port: ENV['KUBERNETES_SERVICE_PORT_HTTPS'],
- path: '/apis/apps'
- ),
- 'v1beta1',
- ssl_options: {
- ca_file: "#{SVC_ACC_PATH}/ca.crt",
- verify_ssl: OpenSSL::SSL::VERIFY_PEER
- },
- auth_options: {
- bearer_token: kube_token
- }
- )
+ @kube_client_stateful_set ||= create_kube_client(path: '/apis/apps')
end
def instance_group
- pod = kube_client.get_pod(ENV['HOSTNAME'], kube_namespace)
+ pod = kube_client.get_pod(@self_name, kube_namespace)
pod['metadata']['labels']['app.kubernetes.io/component']
end
end