kubernator 1.0.11__py3-none-any.whl → 1.0.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kubernator might be problematic. Click here for more details.

kubernator/api.py CHANGED
@@ -28,7 +28,6 @@ import urllib.parse
28
28
  from collections.abc import Callable
29
29
  from collections.abc import Iterable, MutableSet, Reversible
30
30
  from enum import Enum
31
- from functools import cache
32
31
  from hashlib import sha256
33
32
  from io import StringIO as io_StringIO
34
33
  from pathlib import Path
@@ -40,14 +39,19 @@ from typing import Optional, Union, MutableSequence
40
39
  import requests
41
40
  import yaml
42
41
  from appdirs import user_config_dir
42
+ from diff_match_patch import diff_match_patch
43
43
  from jinja2 import (Environment,
44
44
  ChainableUndefined,
45
45
  make_logging_undefined,
46
46
  Template as JinjaTemplate,
47
47
  pass_context)
48
- from jsonpath_ng.ext import parse as jp_parse
49
48
  from jsonschema import validators
50
49
 
50
+ from kubernator._json_path import jp # noqa: F401
51
+ from kubernator._k8s_client_patches import (URLLIB_HEADERS_PATCH,
52
+ CUSTOM_OBJECT_PATCH_23,
53
+ CUSTOM_OBJECT_PATCH_25)
54
+
51
55
  _CACHE_HEADER_TRANSLATION = {"etag": "if-none-match",
52
56
  "last-modified": "if-modified-since"}
53
57
  _CACHE_HEADERS = ("etag", "last-modified")
@@ -68,45 +72,6 @@ def to_patterns(*patterns):
68
72
  return [re.compile(fnmatch.translate(p)) for p in patterns]
69
73
 
70
74
 
71
- class JPath:
72
- def __init__(self, pattern):
73
- self.pattern = jp_parse(pattern)
74
-
75
- def find(self, val):
76
- return self.pattern.find(val)
77
-
78
- def all(self, val):
79
- return list(map(lambda x: x.value, self.find(val)))
80
-
81
- def first(self, val):
82
- """Returns the first element or None if it doesn't exist"""
83
- try:
84
- return next(map(lambda x: x.value, self.find(val)))
85
- except StopIteration:
86
- return None
87
-
88
- def only(self, val):
89
- """Returns the first and only element.
90
- Raises ValueError if more than one value found
91
- Raises KeyError if no value found
92
- """
93
- m = map(lambda x: x.value, self.find(val))
94
- try:
95
- v = next(m)
96
- except StopIteration:
97
- raise KeyError("no value found")
98
- try:
99
- next(m)
100
- raise ValueError("more than one value returned")
101
- except StopIteration:
102
- return v
103
-
104
-
105
- @cache
106
- def jp(pattern) -> JPath:
107
- return JPath(pattern)
108
-
109
-
110
75
  def scan_dir(logger, path: Path, path_filter: Callable[[os.DirEntry], bool], excludes, includes):
111
76
  logger.debug("Scanning %s, excluding %s, including %s", path, excludes, includes)
112
77
  with os.scandir(path) as it: # type: Iterable[os.DirEntry]
@@ -228,20 +193,6 @@ def validator_with_defaults(validator_class):
228
193
  return validators.extend(validator_class, {"properties": set_defaults})
229
194
 
230
195
 
231
- def install_python_k8s_client(run, package_major, logger_stdout, logger_stderr):
232
- cache_dir = get_cache_dir("python")
233
- package_major_dir = cache_dir / str(package_major)
234
-
235
- if not package_major_dir.exists():
236
- package_major_dir.mkdir(parents=True, exist_ok=True)
237
-
238
- run([sys.executable, "-m", "pip", "install", "--no-deps", "--no-cache-dir", "--no-input", "--pre",
239
- "--root-user-action=ignore", "--break-system-packages", "--disable-pip-version-check",
240
- "--target", str(package_major_dir), f"kubernetes~={package_major}.0"], logger_stdout, logger_stderr).wait()
241
-
242
- return package_major_dir
243
-
244
-
245
196
  class _PropertyList(MutableSequence):
246
197
 
247
198
  def __init__(self, seq, read_parent, name):
@@ -740,7 +691,7 @@ class Repository:
740
691
  self.local_dir)
741
692
  run(["git", "config", "remote.origin.fetch", f"+refs/heads/{ref}:refs/remotes/origin/{ref}"],
742
693
  stdout_logger, stderr_logger, cwd=git_cache).wait()
743
- run(["git", "fetch", "-pPt"], stdout_logger, stderr_logger, cwd=git_cache).wait()
694
+ run(["git", "fetch", "-pPt", "--force"], stdout_logger, stderr_logger, cwd=git_cache).wait()
744
695
  run(["git", "checkout", ref], stdout_logger, stderr_logger, cwd=git_cache).wait()
745
696
  run(["git", "clean", "-f"], stdout_logger, stderr_logger, cwd=git_cache).wait()
746
697
  run(["git", "reset", "--hard", ref, "--"], stdout_logger, stderr_logger, cwd=git_cache).wait()
@@ -795,3 +746,63 @@ class KubernatorPlugin:
795
746
 
796
747
  def handle_shutdown(self):
797
748
  pass
749
+
750
+
751
+ def install_python_k8s_client(run, package_major, logger, logger_stdout, logger_stderr, disable_patching):
752
+ cache_dir = get_cache_dir("python")
753
+ package_major_dir = cache_dir / str(package_major)
754
+ package_major_dir_str = str(package_major_dir)
755
+ patch_indicator = package_major_dir / ".patched"
756
+
757
+ if disable_patching and package_major_dir.exists() and patch_indicator.exists():
758
+ logger.info("Patching is disabled, existing Kubernetes Client %s (%s) was patched - "
759
+ "deleting current client",
760
+ str(package_major), package_major_dir)
761
+ rmtree(package_major_dir)
762
+
763
+ if not package_major_dir.exists():
764
+ package_major_dir.mkdir(parents=True, exist_ok=True)
765
+ run([sys.executable, "-m", "pip", "install", "--no-deps", "--no-input", "--pre",
766
+ "--root-user-action=ignore", "--break-system-packages", "--disable-pip-version-check",
767
+ "--target", package_major_dir_str, f"kubernetes~={package_major}.0"], logger_stdout, logger_stderr).wait()
768
+
769
+ if not patch_indicator.exists() and not disable_patching:
770
+ for patch_text, target_file, skip_if_found, min_version, max_version, name in (
771
+ URLLIB_HEADERS_PATCH, CUSTOM_OBJECT_PATCH_23, CUSTOM_OBJECT_PATCH_25):
772
+ patch_target = package_major_dir / target_file
773
+ logger.info("Applying patch %s to %s...", name, patch_target)
774
+ if min_version and int(package_major) < min_version:
775
+ logger.info("Skipping patch %s on %s due to package major version %s below minimum %d!",
776
+ name, patch_target, package_major, min_version)
777
+ continue
778
+ if max_version and int(package_major) > max_version:
779
+ logger.info("Skipping patch %s on %s due to package major version %s above maximum %d!",
780
+ name, patch_target, package_major, max_version)
781
+ continue
782
+
783
+ with open(patch_target, "rt") as f:
784
+ target_file_original = f.read()
785
+ if skip_if_found in target_file_original:
786
+ logger.info("Skipping patch %s on %s, as it already appears to be patched!", name,
787
+ patch_target)
788
+ continue
789
+
790
+ dmp = diff_match_patch()
791
+ patches = dmp.patch_fromText(patch_text)
792
+ target_file_patched, results = dmp.patch_apply(patches, target_file_original)
793
+ failed_patch = False
794
+ for idx, result in enumerate(results):
795
+ if not result:
796
+ failed_patch = True
797
+ msg = ("Failed to apply a patch to Kubernetes Client API %s, hunk #%d, patch: \n%s" % (
798
+ patch_target, idx, patches[idx]))
799
+ logger.fatal(msg)
800
+ if failed_patch:
801
+ raise RuntimeError(f"Failed to apply some Kubernetes Client API {patch_target} patches")
802
+
803
+ with open(patch_target, "wt") as f:
804
+ f.write(target_file_patched)
805
+
806
+ patch_indicator.touch(exist_ok=False)
807
+
808
+ return package_major_dir
kubernator/app.py CHANGED
@@ -32,7 +32,7 @@ from typing import Optional, Union
32
32
  import kubernator
33
33
  from kubernator.api import (KubernatorPlugin, Globs, scan_dir, PropertyDict, config_as_dict, config_parent,
34
34
  download_remote_file, load_remote_file, Repository, StripNL, jp, get_app_cache_dir,
35
- install_python_k8s_client)
35
+ get_cache_dir, install_python_k8s_client)
36
36
  from kubernator.proc import run, run_capturing_out
37
37
 
38
38
  TRACE = 5
@@ -65,8 +65,12 @@ def define_arg_parse():
65
65
  help="print version and exit")
66
66
  g.add_argument("--clear-cache", action="store_true",
67
67
  help="clear cache and exit")
68
+ g.add_argument("--clear-k8s-cache", action="store_true",
69
+ help="clear Kubernetes Client cache and exit")
68
70
  g.add_argument("--pre-cache-k8s-client", action="extend", nargs="+", type=int,
69
- help="download specified K8S client library minor(!) version(s) and exit")
71
+ help="download specified K8S client library major(!) version(s) and exit")
72
+ parser.add_argument("--pre-cache-k8s-client-no-patch", action="store_true", default=None,
73
+ help="do not patch the k8s client being pre-cached")
70
74
  parser.add_argument("--log-format", choices=["human", "json"], default="human",
71
75
  help="whether to log for human or machine consumption")
72
76
  parser.add_argument("--log-file", type=argparse.FileType("w"), default=None,
@@ -453,23 +457,37 @@ class App(KubernatorPlugin):
453
457
 
454
458
  def clear_cache():
455
459
  cache_dir = get_app_cache_dir()
456
- logger.info("Clearing application cache at %s", cache_dir)
460
+ _clear_cache("Clearing application cache at %s", cache_dir)
461
+
462
+
463
+ def clear_k8s_cache():
464
+ cache_dir = get_cache_dir("python")
465
+ _clear_cache("Clearing Kubernetes Client cache at %s", cache_dir)
466
+
467
+
468
+ def _clear_cache(msg, cache_dir):
469
+ logger.info(msg, cache_dir)
457
470
  if cache_dir.exists():
458
471
  rmtree(cache_dir)
459
472
 
460
473
 
461
- def pre_cache_k8s_clients(*versions):
474
+ def pre_cache_k8s_clients(*versions, disable_patching=False):
462
475
  proc_logger = logger.getChild("proc")
463
476
  stdout_logger = StripNL(proc_logger.info)
464
477
  stderr_logger = StripNL(proc_logger.warning)
465
478
 
466
479
  for v in versions:
467
- logger.info("Caching K8S client library ~=v%s.0...", v)
468
- install_python_k8s_client(run, v, stdout_logger, stderr_logger)
480
+ logger.info("Caching K8S client library ~=v%s.0%s...", v,
481
+ " (no patches)" if disable_patching else "")
482
+ install_python_k8s_client(run, v, logger, stdout_logger, stderr_logger, disable_patching)
469
483
 
470
484
 
471
485
  def main():
472
- args = define_arg_parse().parse_args()
486
+ argparser = define_arg_parse()
487
+ args = argparser.parse_args()
488
+ if not args.pre_cache_k8s_client and args.pre_cache_k8s_client_no_patch is not None:
489
+ argparser.error("--pre-cache-k8s-client-no-patch can only be used with --pre-cache-k8s-client")
490
+
473
491
  init_logging(args.verbose, args.log_format, args.log_file)
474
492
 
475
493
  try:
@@ -477,8 +495,13 @@ def main():
477
495
  clear_cache()
478
496
  return
479
497
 
498
+ if args.clear_k8s_cache:
499
+ clear_k8s_cache()
500
+ return
501
+
480
502
  if args.pre_cache_k8s_client:
481
- pre_cache_k8s_clients(*args.pre_cache_k8s_client)
503
+ pre_cache_k8s_clients(*args.pre_cache_k8s_client,
504
+ disable_patching=args.pre_cache_k8s_client_no_patch)
482
505
  return
483
506
 
484
507
  with App(args) as app:
kubernator/merge.py ADDED
@@ -0,0 +1,128 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020 Express Systems USA, Inc
4
+ # Copyright 2024 Karellen, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ from copy import deepcopy
19
+ from kubernator.api import jp
20
+ from jsonpath_ng.jsonpath import Index, Child
21
+
22
+
23
+ # https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
24
+
25
+ def extract_merge_instructions(manifest, resource):
26
+ normalized_manifest = deepcopy(manifest)
27
+ change_instrs = jp('$..`match(/\\\\$patch|\\\\$deleteFromPrimitiveList\\/.*/)`').find(manifest)
28
+
29
+ instructions = []
30
+ for change_instr in change_instrs:
31
+ field = change_instr.path.fields[0]
32
+ context = change_instr.context.value
33
+
34
+ list_of_maps = False
35
+ search_key = None
36
+ change_path = change_instr.full_path.left
37
+ if isinstance(change_path, Child) and isinstance(change_path.right, Index):
38
+ list_of_maps = True
39
+ index = change_path.right.index
40
+ change_path = change_path.left
41
+ clean_manifest_result: list = change_path.find(normalized_manifest)[0].value
42
+ search_key = context.copy()
43
+ del search_key[field]
44
+ del clean_manifest_result[index]
45
+ else:
46
+ clean_manifest_result = change_path.find(normalized_manifest)[0]
47
+ clean_manifest_result_context = clean_manifest_result.value
48
+ del clean_manifest_result_context[field]
49
+
50
+ instruction_value = context[field]
51
+ if field == "$patch":
52
+ if instruction_value in ("replace", "delete"):
53
+ instructions.append(("patch", instruction_value,
54
+ change_path, list_of_maps, search_key))
55
+ else:
56
+ raise ValueError("Invalid $patch instruction %r in resource %s at %s" %
57
+ (instruction_value,
58
+ resource,
59
+ change_path))
60
+ elif field.startswith("$deleteFromPrimitiveList/"):
61
+ instructions.append(("delete-from-list", instruction_value, field[25:],
62
+ change_path))
63
+
64
+ return instructions, normalized_manifest
65
+
66
+
67
+ def apply_merge_instructions(merge_instrs, source_manifest, target_manifest, logger, resource):
68
+ for merge_instr in merge_instrs:
69
+ if merge_instr[0] == "patch":
70
+ op, op_type, change_path, list_of_maps, search_key = merge_instr
71
+ else:
72
+ op, delete_list, field_name, change_path = merge_instr
73
+
74
+ if op == "patch":
75
+ source_obj = change_path.find(source_manifest)[0].value
76
+ merged_obj = change_path.find(target_manifest)[0].value
77
+ if op_type == "delete":
78
+ if list_of_maps:
79
+ logger.trace("Deleting locally in resource %s: %s from %s at %s",
80
+ resource,
81
+ search_key,
82
+ merged_obj,
83
+ change_path)
84
+ del_idxs = []
85
+ for idx, obj in enumerate(merged_obj):
86
+ for k, v in search_key.items():
87
+ if k in obj:
88
+ if v is not None and obj[k] == v:
89
+ del_idxs.append(idx)
90
+
91
+ for idx in del_idxs:
92
+ del merged_obj[idx]
93
+ else:
94
+ logger.trace("Deleting locally in resource %s: %s at %s",
95
+ resource,
96
+ merged_obj,
97
+ change_path)
98
+ merged_datum = change_path.find(target_manifest)[0]
99
+ merged_datum.context.value[merged_datum.path.fields[0]] = None
100
+ elif op_type == "replace":
101
+ logger.trace("Replacing locally in resource %s: %s with %s at %s",
102
+ resource,
103
+ merged_obj, source_obj,
104
+ change_path)
105
+ merged_obj.clear()
106
+ if list_of_maps:
107
+ merged_obj.extend(source_obj)
108
+ else:
109
+ merged_obj.update(source_obj)
110
+ else:
111
+ raise ValueError("Invalid $patch instruction %s found at %s in resource %s",
112
+ op_type, change_path, resource)
113
+
114
+ elif op == "delete-from-list":
115
+ merged_list: list = change_path.find(target_manifest)[0].value[field_name]
116
+ if not isinstance(merged_list, list):
117
+ raise ValueError("Not a list in resource %s: %s in %r at %s" %
118
+ (resource, merged_list, field_name, change_path))
119
+ logger.trace("Deleting from list locally in resource %s: %s from %s in %r at %s",
120
+ resource, delete_list, merged_list, field_name, change_path)
121
+ for v in delete_list:
122
+ try:
123
+ merged_list.remove(v)
124
+ except ValueError:
125
+ logger.warning("No value %s to delete from list %s in %r at %s in resource %s",
126
+ v, merged_list, field_name, change_path, resource)
127
+ else:
128
+ raise RuntimeError("should never reach here")
kubernator/plugins/k8s.py CHANGED
@@ -39,6 +39,7 @@ from kubernator.api import (KubernatorPlugin,
39
39
  load_remote_file,
40
40
  StripNL,
41
41
  install_python_k8s_client)
42
+ from kubernator.merge import extract_merge_instructions, apply_merge_instructions
42
43
  from kubernator.plugins.k8s_api import (K8SResourcePluginMixin,
43
44
  K8SResource,
44
45
  K8SResourcePatchType,
@@ -84,7 +85,10 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
84
85
  def set_context(self, context):
85
86
  self.context = context
86
87
 
87
- def register(self, field_validation="Strict", field_validation_warn_fatal=True):
88
+ def register(self,
89
+ field_validation="Warn",
90
+ field_validation_warn_fatal=True,
91
+ disable_client_patches=False):
88
92
  self.context.app.register_plugin("kubeconfig")
89
93
 
90
94
  if field_validation not in VALID_FIELD_VALIDATION:
@@ -113,6 +117,7 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
113
117
  add_validator=self.api_remove_validator,
114
118
  get_api_versions=self.get_api_versions,
115
119
  create_resource=self.create_resource,
120
+ disable_client_patches=disable_client_patches,
116
121
  field_validation=field_validation,
117
122
  field_validation_warn_fatal=field_validation_warn_fatal,
118
123
  field_validation_warnings=0,
@@ -142,27 +147,24 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
142
147
  self._setup_client()
143
148
 
144
149
  server_minor = k8s.server_version[1]
145
- pkg_major = self.embedded_pkg_version[0]
146
- if server_minor != pkg_major:
147
- logger.info("Bundled Kubernetes client version %s doesn't match server version %s",
148
- ".".join(self.embedded_pkg_version), ".".join(k8s.server_version))
149
- pkg_dir = install_python_k8s_client(self.context.app.run, server_minor, stdout_logger, stderr_logger)
150
-
151
- modules_to_delete = []
152
- for k, v in sys.modules.items():
153
- if k == "kubernetes" or k.startswith("kubernetes."):
154
- modules_to_delete.append(k)
155
- for k in modules_to_delete:
156
- del sys.modules[k]
157
-
158
- logger.info("Adding sys.path reference to %s", pkg_dir)
159
- sys.path.insert(0, str(pkg_dir))
160
- self.embedded_pkg_version = self._get_kubernetes_client_version()
161
- logger.info("Switching to Kubernetes client version %s", ".".join(self.embedded_pkg_version))
162
- self._setup_client()
163
- else:
164
- logger.info("Bundled Kubernetes client version %s matches server version %s",
165
- ".".join(self.embedded_pkg_version), ".".join(k8s.server_version))
150
+
151
+ logger.info("Using Kubernetes client version =~%s.0 for server version %s",
152
+ server_minor, ".".join(k8s.server_version))
153
+ pkg_dir = install_python_k8s_client(self.context.app.run, server_minor, logger,
154
+ stdout_logger, stderr_logger, k8s.disable_client_patches)
155
+
156
+ modules_to_delete = []
157
+ for k, v in sys.modules.items():
158
+ if k == "kubernetes" or k.startswith("kubernetes."):
159
+ modules_to_delete.append(k)
160
+ for k in modules_to_delete:
161
+ del sys.modules[k]
162
+
163
+ logger.info("Adding sys.path reference to %s", pkg_dir)
164
+ sys.path.insert(0, str(pkg_dir))
165
+ self.embedded_pkg_version = self._get_kubernetes_client_version()
166
+ logger.info("Switching to Kubernetes client version %s", ".".join(self.embedded_pkg_version))
167
+ self._setup_client()
166
168
 
167
169
  logger.debug("Reading Kubernetes OpenAPI spec for %s", k8s.server_git_version)
168
170
 
@@ -192,6 +194,7 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
192
194
  logger.info("Found Kubernetes %s on %s", k8s.server_git_version, k8s.client.configuration.host)
193
195
  K8SResource._k8s_client_version = tuple(map(int, pkg_version("kubernetes").split(".")))
194
196
  K8SResource._k8s_field_validation = k8s.field_validation
197
+ K8SResource._k8s_field_validation_patched = not k8s.disable_client_patches
195
198
  K8SResource._logger = self.logger
196
199
  K8SResource._api_warnings = self._api_warnings
197
200
 
@@ -404,6 +407,13 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
404
407
  return
405
408
  raise
406
409
 
410
+ merge_instrs, normalized_manifest = extract_merge_instructions(resource.manifest, resource)
411
+ if merge_instrs:
412
+ logger.trace("Normalized manifest (no merge instructions) for resource %s: %s", resource,
413
+ normalized_manifest)
414
+ else:
415
+ normalized_manifest = resource.manifest
416
+
407
417
  logger.debug("Applying resource %s%s", resource, status_msg)
408
418
  try:
409
419
  remote_resource = resource.get()
@@ -419,9 +429,9 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
419
429
  else:
420
430
  raise
421
431
  else:
422
- logger.trace("Attempting to retrieve a normalized patch for resource %s: %s", resource, resource.manifest)
432
+ logger.trace("Attempting to retrieve a normalized patch for resource %s: %s", resource, normalized_manifest)
423
433
  try:
424
- merged_resource = resource.patch(resource.manifest,
434
+ merged_resource = resource.patch(normalized_manifest,
425
435
  patch_type=K8SResourcePatchType.SERVER_SIDE_PATCH,
426
436
  dry_run=True,
427
437
  force=True)
@@ -456,6 +466,9 @@ class KubernetesPlugin(KubernatorPlugin, K8SResourcePluginMixin):
456
466
  raise
457
467
  else:
458
468
  logger.trace("Merged resource %s: %s", resource, merged_resource)
469
+ if merge_instrs:
470
+ apply_merge_instructions(merge_instrs, normalized_manifest, merged_resource, logger, resource)
471
+
459
472
  patch = jsonpatch.make_patch(remote_resource, merged_resource)
460
473
  logger.trace("Resource %s initial patches are: %s", resource, patch)
461
474
  patch = self._filter_resource_patch(patch, patch_field_excludes)
@@ -340,6 +340,7 @@ class K8SResourceKey(namedtuple("K8SResourceKey", ["group", "kind", "name", "nam
340
340
  class K8SResource:
341
341
  _k8s_client_version = None
342
342
  _k8s_field_validation = None
343
+ _k8s_field_validation_patched = None
343
344
  _logger = None
344
345
  _api_warnings = None
345
346
 
@@ -411,7 +412,7 @@ class K8SResource:
411
412
  }
412
413
 
413
414
  # `and not self.rdef.custom` to be removed after solving https://github.com/kubernetes-client/gen/issues/259
414
- if self._k8s_client_version[0] > 22 and not self.rdef.custom:
415
+ if self._k8s_client_version[0] > 22 and (self._k8s_field_validation_patched or not self.rdef.custom):
415
416
  kwargs["field_validation"] = self._k8s_field_validation
416
417
  if rdef.namespaced:
417
418
  kwargs["namespace"] = self.namespace
@@ -432,7 +433,7 @@ class K8SResource:
432
433
  }
433
434
 
434
435
  # `and not self.rdef.custom` to be removed after solving https://github.com/kubernetes-client/gen/issues/259
435
- if self._k8s_client_version[0] > 22 and not self.rdef.custom:
436
+ if self._k8s_client_version[0] > 22 and (self._k8s_field_validation_patched or not self.rdef.custom):
436
437
  kwargs["field_validation"] = self._k8s_field_validation
437
438
  if patch_type == K8SResourcePatchType.SERVER_SIDE_PATCH:
438
439
  kwargs["force"] = force
@@ -124,7 +124,7 @@ class MinikubePlugin(KubernatorPlugin):
124
124
  profile_dir = get_cache_dir("minikube")
125
125
  self.minikube_home_dir = profile_dir
126
126
  self.minikube_home_dir.mkdir(parents=True, exist_ok=True)
127
- self.kubeconfig_dir = profile_dir / ".kube"
127
+ self.kubeconfig_dir = profile_dir / ".kube" / profile
128
128
  self.kubeconfig_dir.mkdir(parents=True, exist_ok=True)
129
129
 
130
130
  if not driver:
@@ -159,12 +159,12 @@ class TemplatePlugin(KubernatorPlugin):
159
159
  raise ValueError(f"Template with a name {name} does not exist")
160
160
 
161
161
  template = self.templates[name]
162
- logger.debug("Rendering template %s from %s in %s", name, template.source, source)
162
+ templ_source = _get_source(name, template.source, source)
163
+ logger.debug("Rendering %s", templ_source)
163
164
  rendered_template = template.render(self.context, values)
164
165
 
165
166
  if self.template_engine.failures():
166
- raise ValueError(f"Unable to render template {name} from {template.source} in {source} "
167
- "due to undefined required variables")
167
+ raise ValueError(f"Unable to render template {templ_source} due to undefined required variables")
168
168
 
169
169
  return rendered_template
170
170
 
@@ -174,8 +174,9 @@ class TemplatePlugin(KubernatorPlugin):
174
174
 
175
175
  rendered_template = self.render_template(name, source, values)
176
176
  template = self.templates[name]
177
- logger.info("Applying template %s from %s", name, template.source)
178
- self.context.k8s.add_resources(rendered_template, source=template.source)
177
+ templ_source = _get_source(name, template.source, source)
178
+ logger.info("Applying %s", templ_source)
179
+ self.context.k8s.add_resources(rendered_template, source=templ_source)
179
180
 
180
181
  def _process_template_doc(self, template_doc, source):
181
182
  logger.info("Processing Kubernator template from %s", source)
@@ -209,3 +210,9 @@ class TemplatePlugin(KubernatorPlugin):
209
210
 
210
211
  def __repr__(self):
211
212
  return "Template Plugin"
213
+
214
+
215
+ def _get_source(name, template_def_source, template_appl_source):
216
+ return "template %r from %s%s" % (name, template_def_source,
217
+ "" if template_def_source == template_appl_source else
218
+ " in %s" % template_appl_source)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kubernator
3
- Version: 1.0.11
3
+ Version: 1.0.13
4
4
  Summary: Kubernator is the a pluggable framework for K8S provisioning
5
5
  Home-page: https://github.com/karellen/kubernator
6
6
  Author: Express Systems USA, Inc.
@@ -33,13 +33,14 @@ Requires-Python: >=3.9
33
33
  Description-Content-Type: text/markdown
34
34
  Requires-Dist: appdirs (~=1.4)
35
35
  Requires-Dist: coloredlogs (~=15.0)
36
+ Requires-Dist: diff-match-patch (>2023.0)
36
37
  Requires-Dist: gevent (>=21.1.2)
37
38
  Requires-Dist: jinja2 (~=3.1)
38
39
  Requires-Dist: json-log-formatter (~=0.3)
39
40
  Requires-Dist: jsonpatch (~=1.32)
40
- Requires-Dist: jsonpath-ng (~=1.5)
41
+ Requires-Dist: jsonpath-ng (~=1.6.1)
41
42
  Requires-Dist: jsonschema (<4.0)
42
- Requires-Dist: kubernetes (~=28.0)
43
+ Requires-Dist: kubernetes (~=29.0)
43
44
  Requires-Dist: openapi-schema-validator (~=0.1)
44
45
  Requires-Dist: openapi-spec-validator (~=0.3)
45
46
  Requires-Dist: requests (~=2.25)
@@ -0,0 +1,30 @@
1
+ kubernator/LICENSE,sha256=wKKdOCMTCPQRV5gDkVLAsXX8qSnRJ5owk7yWPO1KZNo,11387
2
+ kubernator/__init__.py,sha256=3INpPMTdUp9RrfskvnKR_Z6m6l_5i1Ma0SgEE9mgD4E,915
3
+ kubernator/__main__.py,sha256=f0S60wgpLu--1UlOhzfWail-xt8zyIuODodX98_yPN0,707
4
+ kubernator/_json_path.py,sha256=pjQKXxgbpQWETYBIrIuJZHgugF92IbEAM19AC7JUmAQ,3162
5
+ kubernator/_k8s_client_patches.py,sha256=PEeWPInnW38NDyK7G24_Dmw-x7xHpN3vJWZeckdqgK0,76892
6
+ kubernator/api.py,sha256=O1wJ2K_u8CoFDMzSood5_VkP4L1Hmo4VWQQxn9D1T4A,26648
7
+ kubernator/app.py,sha256=FQVWQydYPendvI6n5yJjacrD6DN85ex_4-d0Enb1xLg,20496
8
+ kubernator/merge.py,sha256=eW5fajnDdI2n8aUqRfTmdG6GWDvDtcVKPKsp3fiB5Nk,5882
9
+ kubernator/proc.py,sha256=8YlgbppyHic_51fVTPD7OP8x5yuRuL8j1kThR8MJjNI,5119
10
+ kubernator/plugins/__init__.py,sha256=h9TLYK8UFEi53ipZSZsTBjp0ljKhisWsgPpt_PkWrO8,670
11
+ kubernator/plugins/awscli.py,sha256=S6X7-qFiaZ7NDVDl2Jg0t-ih9KAJ45cUjjzd5Qe93ZM,4252
12
+ kubernator/plugins/eks.py,sha256=xe7vyPHNwuP8gEYDSzPyBkm-RkAtP64wCOqs9U5I7xI,2273
13
+ kubernator/plugins/helm.py,sha256=3BYZBPoiMUtNgmhdcbYCVRTPtaQMYzhfnMqXWyZRJZc,10525
14
+ kubernator/plugins/istio.py,sha256=rjqFyWWKVhcv8tB5sY9cAQV8XnmUYc5vn8t1R6GRjOQ,10432
15
+ kubernator/plugins/k8s.py,sha256=w_Uw8tvbeGgJ35jZs6BahWkVIlX6gxcX5-bOskudIfw,23387
16
+ kubernator/plugins/k8s_api.py,sha256=w2aB7CU0rPqRgzhI5mLMJUUSpWlJGCsX_bHl4SjfzM8,27594
17
+ kubernator/plugins/kops.py,sha256=QsrQJUF6wGJo2QRVqP92pG5vmOTYQIc25PD_DWCzrAA,9635
18
+ kubernator/plugins/kubeconfig.py,sha256=uwtHmF2I6LiTPrC3M88G5SfYxDWtuh0MqcMfrHHoNRA,2178
19
+ kubernator/plugins/kubectl.py,sha256=IgNghW1Q6s8V2o08eNY2NWfkkdV9Z6X2A3YFQinFr4g,4028
20
+ kubernator/plugins/minikube.py,sha256=kWPDIS4X1JnEoXw_rIOvgvFtjJMch39uL0mD7gQwtjE,10162
21
+ kubernator/plugins/template.py,sha256=KEiPfI7TbYXmF4a8ATWuFhxxWbcASHttFM_ekYEZ8Ps,8371
22
+ kubernator/plugins/terraform.py,sha256=a1MPl9G8Rznjd28uMRdYWrjpFDLlFAVmLFH4hFEsMsQ,5285
23
+ kubernator/plugins/terragrunt.py,sha256=-qN8tTqPUXJ9O7CEiJVUB0GSUQU3DUTkv-aWdIIsm7Q,5051
24
+ kubernator-1.0.13.dist-info/METADATA,sha256=wlY-Q2tjUEEc8UNdv2iUbAbqbKnW6z9G25co5z-4lag,10522
25
+ kubernator-1.0.13.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
26
+ kubernator-1.0.13.dist-info/entry_points.txt,sha256=IWDtHzyTleRqDSuVRXEr5GImrI7z_kh21t5DWZ8ldcQ,47
27
+ kubernator-1.0.13.dist-info/namespace_packages.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
28
+ kubernator-1.0.13.dist-info/top_level.txt,sha256=_z1CxWeKMI55ckf2vC8HqjbCn_E2Y_P5RdrhE_QWcIs,11
29
+ kubernator-1.0.13.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
30
+ kubernator-1.0.13.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- kubernator/LICENSE,sha256=wKKdOCMTCPQRV5gDkVLAsXX8qSnRJ5owk7yWPO1KZNo,11387
2
- kubernator/__init__.py,sha256=Fq_DcRKw3SzT9L-UcFqcw0DT6EC40NZSpyD6Y4T-mz4,915
3
- kubernator/__main__.py,sha256=f0S60wgpLu--1UlOhzfWail-xt8zyIuODodX98_yPN0,707
4
- kubernator/api.py,sha256=3hxYHOJt_ps9VFqqOuIHOHPLThLb98RO-gZZ_E-xd9I,24951
5
- kubernator/app.py,sha256=kGS8jVcf-nCY1mP6MdqMR27XNcEMb5Uxek9FEW4GWLg,19472
6
- kubernator/proc.py,sha256=8YlgbppyHic_51fVTPD7OP8x5yuRuL8j1kThR8MJjNI,5119
7
- kubernator/plugins/__init__.py,sha256=h9TLYK8UFEi53ipZSZsTBjp0ljKhisWsgPpt_PkWrO8,670
8
- kubernator/plugins/awscli.py,sha256=S6X7-qFiaZ7NDVDl2Jg0t-ih9KAJ45cUjjzd5Qe93ZM,4252
9
- kubernator/plugins/eks.py,sha256=xe7vyPHNwuP8gEYDSzPyBkm-RkAtP64wCOqs9U5I7xI,2273
10
- kubernator/plugins/helm.py,sha256=3BYZBPoiMUtNgmhdcbYCVRTPtaQMYzhfnMqXWyZRJZc,10525
11
- kubernator/plugins/istio.py,sha256=rjqFyWWKVhcv8tB5sY9cAQV8XnmUYc5vn8t1R6GRjOQ,10432
12
- kubernator/plugins/k8s.py,sha256=hUchwibVpvuHiC2HHCcbKgtSdlbg98P3NRweJn_4Hs4,22856
13
- kubernator/plugins/k8s_api.py,sha256=RlpbcdTheox2Tq5iz1_ghjJ6846wsSrJzjOEKpee4h8,27473
14
- kubernator/plugins/kops.py,sha256=QsrQJUF6wGJo2QRVqP92pG5vmOTYQIc25PD_DWCzrAA,9635
15
- kubernator/plugins/kubeconfig.py,sha256=uwtHmF2I6LiTPrC3M88G5SfYxDWtuh0MqcMfrHHoNRA,2178
16
- kubernator/plugins/kubectl.py,sha256=IgNghW1Q6s8V2o08eNY2NWfkkdV9Z6X2A3YFQinFr4g,4028
17
- kubernator/plugins/minikube.py,sha256=Gm1aI5QlYFd8eyWm7g85z2aZmPcscYD63ww3sPcvxVQ,10152
18
- kubernator/plugins/template.py,sha256=o6UqLw1wK91gCfN7q-Wwc3P5iycAeELUryXh-egA2vQ,8070
19
- kubernator/plugins/terraform.py,sha256=a1MPl9G8Rznjd28uMRdYWrjpFDLlFAVmLFH4hFEsMsQ,5285
20
- kubernator/plugins/terragrunt.py,sha256=-qN8tTqPUXJ9O7CEiJVUB0GSUQU3DUTkv-aWdIIsm7Q,5051
21
- kubernator-1.0.11.dist-info/METADATA,sha256=mCyyX7mv3-pOIT_eofB9PG5ksf6VifM6B-W7M4TkNRI,10478
22
- kubernator-1.0.11.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
23
- kubernator-1.0.11.dist-info/entry_points.txt,sha256=IWDtHzyTleRqDSuVRXEr5GImrI7z_kh21t5DWZ8ldcQ,47
24
- kubernator-1.0.11.dist-info/namespace_packages.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
25
- kubernator-1.0.11.dist-info/top_level.txt,sha256=_z1CxWeKMI55ckf2vC8HqjbCn_E2Y_P5RdrhE_QWcIs,11
26
- kubernator-1.0.11.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
27
- kubernator-1.0.11.dist-info/RECORD,,