Class: K8s::Stack
Overview
Usage: customize the LABEL and CHECKSUM_ANNOTATION
Constant Summary collapse
- LABEL =
Label used to identify resources belonging to this stack
'k8s.kontena.io/stack'- CHECKSUM_ANNOTATION =
Annotation used to identify resource versions
'k8s.kontena.io/stack-checksum'- LAST_CONFIG_ANNOTATION =
Annotation used to identify last applied configuration
'kubectl.kubernetes.io/last-applied-configuration'- PRUNE_IGNORE =
List of apiVersion:kind combinations to skip for stack prune These would lead to stack prune misbehaving if not skipped.
[ 'v1:ComponentStatus', # apiserver ignores GET /v1/componentstatuses?labelSelector=... and returns all resources 'v1:Endpoints', # inherits stack label from service, but not checksum annotation ]
Constants included from Logging
Logging::LOG_LEVEL, Logging::LOG_TARGET
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#resources ⇒ Object
readonly
Returns the value of attribute resources.
Class Method Summary collapse
- .apply(name, path, client, prune: true, **options) ⇒ Object
-
.delete(name, client, **options) ⇒ Object
Remove any installed stack resources.
- .load(name, path, **options) ⇒ K8s::Stack
Instance Method Summary collapse
- #apply(client, prune: true) ⇒ Array<K8s::Resource>
-
#delete(client) ⇒ Object
Delete all stack resources.
-
#initialize(name, resources = [], debug: false, label: self.class::LABEL, checksum_annotation: self.class::CHECKSUM_ANNOTATION, last_configuration_annotation: self.class::LAST_CONFIG_ANNOTATION) ⇒ Stack
constructor
A new instance of Stack.
-
#keep_resource!(resource) ⇒ Object
key MUST NOT include resource.apiVersion: the same kind can be aliased in different APIs.
- #keep_resource?(resource) ⇒ Boolean
- #prepare_resource(resource, base_resource: nil) ⇒ K8s::Resource
-
#prune(client, keep_resources:, skip_forbidden: true) ⇒ Object
Delete all stack resources that were not applied.
Methods included from Logging
Methods included from Logging::ModuleMethods
#debug!, #log_level, #log_level=, #quiet!, #verbose!
Constructor Details
#initialize(name, resources = [], debug: false, label: self.class::LABEL, checksum_annotation: self.class::CHECKSUM_ANNOTATION, last_configuration_annotation: self.class::LAST_CONFIG_ANNOTATION) ⇒ Stack
Returns a new instance of Stack.
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/k8s/stack.rb', line 50 def initialize(name, resources = [], debug: false, label: self.class::LABEL, checksum_annotation: self.class::CHECKSUM_ANNOTATION, last_configuration_annotation: self.class::LAST_CONFIG_ANNOTATION) @name = name @resources = resources @keep_resources = {} @label = label @checksum_annotation = checksum_annotation @last_config_annotation = last_configuration_annotation logger! progname: name, debug: debug end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
48 49 50 |
# File 'lib/k8s/stack.rb', line 48 def name @name end |
#resources ⇒ Object (readonly)
Returns the value of attribute resources.
48 49 50 |
# File 'lib/k8s/stack.rb', line 48 def resources @resources end |
Class Method Details
.apply(name, path, client, prune: true, **options) ⇒ Object
36 37 38 |
# File 'lib/k8s/stack.rb', line 36 def self.apply(name, path, client, prune: true, **) load(name, path, **).apply(client, prune: prune) end |
.delete(name, client, **options) ⇒ Object
Remove any installed stack resources.
44 45 46 |
# File 'lib/k8s/stack.rb', line 44 def self.delete(name, client, **) new(name, **).delete(client) end |
.load(name, path, **options) ⇒ K8s::Stack
27 28 29 30 |
# File 'lib/k8s/stack.rb', line 27 def self.load(name, path, **) resources = K8s::Resource.from_files(path) new(name, resources, **) end |
Instance Method Details
#apply(client, prune: true) ⇒ Array<K8s::Resource>
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/k8s/stack.rb', line 81 def apply(client, prune: true) server_resources = client.get_resources(resources) resources.zip(server_resources).map do |resource, server_resource| if !server_resource logger.info "Create resource #{resource.apiVersion}:#{resource.kind}/#{resource.metadata.name} in namespace #{resource.metadata.namespace} with checksum=#{resource.checksum}" keep_resource! client.create_resource(prepare_resource(resource)) elsif server_resource..annotations[@checksum_annotation] != resource.checksum logger.info "Update resource #{resource.apiVersion}:#{resource.kind}/#{resource.metadata.name} in namespace #{resource.metadata.namespace} with checksum=#{resource.checksum}" r = prepare_resource(resource) if server_resource.can_patch?(@last_config_annotation) keep_resource! client.patch_resource(server_resource, server_resource.merge_patch_ops(r.to_hash, @last_config_annotation)) else # try to update with PUT keep_resource! client.update_resource(server_resource.merge(prepare_resource(resource))) end else logger.info "Keep resource #{server_resource.apiVersion}:#{server_resource.kind}/#{server_resource.metadata.name} in namespace #{server_resource.metadata.namespace} with checksum=#{server_resource.metadata.annotations[@checksum_annotation]}" keep_resource! server_resource end end prune(client, keep_resources: true) if prune end |
#delete(client) ⇒ Object
Delete all stack resources
142 143 144 |
# File 'lib/k8s/stack.rb', line 142 def delete(client) prune(client, keep_resources: false) end |
#keep_resource!(resource) ⇒ Object
key MUST NOT include resource.apiVersion: the same kind can be aliased in different APIs
107 108 109 |
# File 'lib/k8s/stack.rb', line 107 def keep_resource!(resource) @keep_resources["#{resource.kind}:#{resource.metadata.name}@#{resource.metadata.namespace}"] = resource..annotations[@checksum_annotation] end |
#keep_resource?(resource) ⇒ Boolean
110 111 112 |
# File 'lib/k8s/stack.rb', line 110 def keep_resource?(resource) @keep_resources["#{resource.kind}:#{resource.metadata.name}@#{resource.metadata.namespace}"] == resource..annotations[@checksum_annotation] end |
#prepare_resource(resource, base_resource: nil) ⇒ K8s::Resource
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/k8s/stack.rb', line 64 def prepare_resource(resource, base_resource: nil) # XXX: base_resource is not really used anymore, kept for backwards compatibility for a while # calculate checksum only from the "local" source checksum = resource.checksum # add stack metadata resource.merge(metadata: { labels: { @label => name }, annotations: { @checksum_annotation => checksum, @last_config_annotation => resource.to_json }, }) end |
#prune(client, keep_resources:, skip_forbidden: true) ⇒ Object
Delete all stack resources that were not applied
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/k8s/stack.rb', line 115 def prune(client, keep_resources: , skip_forbidden: true) # using skip_forbidden: assume we can't create resource types that we are forbidden to list, so we don't need to prune them either client.list_resources(labelSelector: {@label => name}, skip_forbidden: skip_forbidden).each do |resource| next if PRUNE_IGNORE.include? "#{resource.apiVersion}:#{resource.kind}" resource_label = resource..labels ? resource..labels[@label] : nil resource_checksum = resource..annotations ? resource..annotations[@checksum_annotation] : nil logger.debug { "List resource #{resource.apiVersion}:#{resource.kind}/#{resource.metadata.name} in namespace #{resource.metadata.namespace} with checksum=#{resource_checksum}" } if resource_label != name # apiserver did not respect labelSelector elsif keep_resources && keep_resource?(resource) # resource is up-to-date else logger.info "Delete resource #{resource.apiVersion}:#{resource.kind}/#{resource.metadata.name} in namespace #{resource.metadata.namespace}" begin client.delete_resource(resource, propagationPolicy: 'Background') rescue K8s::Error::NotFound # assume aliased objects in multiple API groups, like for Deployments # alternatively, a custom resource whose definition was already deleted earlier end end end end |