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/__init__.py +1 -1
- kubernator/_json_path.py +113 -0
- kubernator/_k8s_client_patches.py +617 -0
- kubernator/api.py +67 -56
- kubernator/app.py +31 -8
- kubernator/merge.py +128 -0
- kubernator/plugins/k8s.py +37 -24
- kubernator/plugins/k8s_api.py +3 -2
- kubernator/plugins/minikube.py +1 -1
- kubernator/plugins/template.py +12 -5
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/METADATA +4 -3
- kubernator-1.0.13.dist-info/RECORD +30 -0
- kubernator-1.0.11.dist-info/RECORD +0 -27
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/WHEEL +0 -0
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/entry_points.txt +0 -0
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/namespace_packages.txt +0 -0
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/top_level.txt +0 -0
- {kubernator-1.0.11.dist-info → kubernator-1.0.13.dist-info}/zip-safe +0 -0
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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,
|
|
432
|
+
logger.trace("Attempting to retrieve a normalized patch for resource %s: %s", resource, normalized_manifest)
|
|
423
433
|
try:
|
|
424
|
-
merged_resource = resource.patch(
|
|
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)
|
kubernator/plugins/k8s_api.py
CHANGED
|
@@ -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
|
kubernator/plugins/minikube.py
CHANGED
|
@@ -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:
|
kubernator/plugins/template.py
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
178
|
-
|
|
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.
|
|
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.
|
|
41
|
+
Requires-Dist: jsonpath-ng (~=1.6.1)
|
|
41
42
|
Requires-Dist: jsonschema (<4.0)
|
|
42
|
-
Requires-Dist: kubernetes (~=
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|