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 ].freeze
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) ⇒ K8s::Stack
-
.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) ⇒ K8s::Resource
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
rubocop:disable Lint/UnusedMethodArgument.
-
#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.
61 62 63 64 65 66 67 68 69 70 |
# File 'lib/k8s/stack.rb', line 61 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.
53 54 55 |
# File 'lib/k8s/stack.rb', line 53 def name @name end |
#resources ⇒ Object (readonly)
Returns the value of attribute resources.
53 54 55 |
# File 'lib/k8s/stack.rb', line 53 def resources @resources end |
Class Method Details
.apply(name, path, client, prune: true, **options) ⇒ K8s::Stack
41 42 43 |
# File 'lib/k8s/stack.rb', line 41 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.
49 50 51 |
# File 'lib/k8s/stack.rb', line 49 def self.delete(name, client, **) new(name, **).delete(client) end |
.load(name, path, **options) ⇒ K8s::Stack
30 31 32 33 |
# File 'lib/k8s/stack.rb', line 30 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>
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/k8s/stack.rb', line 97 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..name} in namespace #{resource..namespace} with checksum=#{resource.checksum}" keep_resource! client.create_resource(prepare_resource(resource)) elsif server_resource.&.annotations&.dig(@checksum_annotation) != resource.checksum logger.info "Update resource #{resource.apiVersion}:#{resource.kind}/#{resource..name} in namespace #{resource..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_h, @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..name} in namespace #{server_resource..namespace} with checksum=#{server_resource..annotations[@checksum_annotation]}" keep_resource! server_resource end end prune(client, keep_resources: true) if prune end |
#delete(client) ⇒ Object
Delete all stack resources
183 184 185 |
# File 'lib/k8s/stack.rb', line 183 def delete(client) prune(client, keep_resources: false) end |
#keep_resource!(resource) ⇒ K8s::Resource
key MUST NOT include resource.apiVersion: the same kind can be aliased in different APIs
125 126 127 |
# File 'lib/k8s/stack.rb', line 125 def keep_resource!(resource) @keep_resources["#{resource.kind}:#{resource..name}@#{resource..namespace}"] = resource.&.annotations.dig(@checksum_annotation) end |
#keep_resource?(resource) ⇒ Boolean
131 132 133 134 135 136 |
# File 'lib/k8s/stack.rb', line 131 def keep_resource?(resource) keep_annotation = @keep_resources["#{resource.kind}:#{resource..name}@#{resource..namespace}"] return false unless keep_annotation keep_annotation == resource.&.annotations&.dig(@checksum_annotation) end |
#prepare_resource(resource, base_resource: nil) ⇒ K8s::Resource
rubocop:disable Lint/UnusedMethodArgument
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/k8s/stack.rb', line 76 def prepare_resource(resource, base_resource: nil) # TODO: base_resource is not 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 => Util.recursive_compact(resource.to_h).to_json } } ) end |
#prune(client, keep_resources:, skip_forbidden: true) ⇒ Object
Delete all stack resources that were not applied
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/k8s/stack.rb', line 142 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).sort do |a, b| # Sort resources so that namespaced objects are deleted first if a..namespace == b..namespace 0 elsif a..namespace.nil? && !b..namespace.nil? 1 else -1 end end.each do |resource| next if PRUNE_IGNORE.include? "#{resource.apiVersion}:#{resource.kind}" resource_label = resource.&.labels&.dig(@label) resource_checksum = resource.&.annotations&.dig(@checksum_annotation) logger.debug { "List resource #{resource.apiVersion}:#{resource.kind}/#{resource..name} in namespace #{resource..namespace} with checksum=#{resource_checksum}" } if resource_label != name # apiserver did not respect labelSelector elsif resource.&.ownerReferences && !resource..ownerReferences.empty? logger.info "Server resource #{resource.apiVersion}:#{resource.apiKind}/#{resource..name} in namespace #{resource..namespace} has ownerReferences and will be kept" elsif keep_resources && keep_resource?(resource) # resource is up-to-date else logger.info "Delete resource #{resource.apiVersion}:#{resource.kind}/#{resource..name} in namespace #{resource..namespace}" begin client.delete_resource(resource, propagationPolicy: 'Background') rescue K8s::Error::NotFound => e # assume aliased objects in multiple API groups, like for Deployments # alternatively, a custom resource whose definition was already deleted earlier logger.debug { "Ignoring #{e} : #{e.}" } end end end end |