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) ⇒ 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
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.
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/k8s/stack.rb', line 52 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.
50 51 52 |
# File 'lib/k8s/stack.rb', line 50 def name @name end |
#resources ⇒ Object (readonly)
Returns the value of attribute resources.
50 51 52 |
# File 'lib/k8s/stack.rb', line 50 def resources @resources end |
Class Method Details
.apply(name, path, client, prune: true, **options) ⇒ Object
38 39 40 |
# File 'lib/k8s/stack.rb', line 38 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.
46 47 48 |
# File 'lib/k8s/stack.rb', line 46 def self.delete(name, client, **) new(name, **).delete(client) end |
.load(name, path, **options) ⇒ K8s::Stack
29 30 31 32 |
# File 'lib/k8s/stack.rb', line 29 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>
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/k8s/stack.rb', line 87 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[@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_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..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
159 160 161 |
# File 'lib/k8s/stack.rb', line 159 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
113 114 115 |
# File 'lib/k8s/stack.rb', line 113 def keep_resource!(resource) @keep_resources["#{resource.kind}:#{resource..name}@#{resource..namespace}"] = resource..annotations[@checksum_annotation] end |
#keep_resource?(resource) ⇒ Boolean
117 118 119 |
# File 'lib/k8s/stack.rb', line 117 def keep_resource?(resource) @keep_resources["#{resource.kind}:#{resource..name}@#{resource..namespace}"] == resource..annotations[@checksum_annotation] end |
#prepare_resource(resource, base_resource: nil) ⇒ K8s::Resource
rubocop:disable Lint/UnusedMethodArgument
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/k8s/stack.rb', line 67 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
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/k8s/stack.rb', line 122 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 ? resource..labels[@label] : nil resource_checksum = resource..annotations ? resource..annotations[@checksum_annotation] : nil 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 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 => ex # assume aliased objects in multiple API groups, like for Deployments # alternatively, a custom resource whose definition was already deleted earlier logger.debug { "Ignoring #{ex} : #{ex.}" } end end end end |