Skip to content

Commit

Permalink
A
Browse files Browse the repository at this point in the history
  • Loading branch information
arcivanov committed Feb 7, 2024
1 parent a98d185 commit 81ee43e
Show file tree
Hide file tree
Showing 12 changed files with 503 additions and 53 deletions.
1 change: 0 additions & 1 deletion .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 3 additions & 9 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
#

import textwrap

from subprocess import check_call

from pybuilder.core import (use_plugin, init, Author, task)

use_plugin("pypi:karellen_pyb_plugin", ">=0.0.1")
Expand Down Expand Up @@ -57,7 +57,7 @@ def set_properties(project):
project.depends_on("appdirs", "~=1.4")
project.depends_on("requests", "~=2.25")
project.depends_on("jsonpatch", "~=1.32")
project.depends_on("jsonpath-ng", "~=1.5")
project.depends_on("jsonpath-ng", "~=1.6.1")
project.depends_on("jinja2", "~=3.1")
project.depends_on("coloredlogs", "~=15.0")
project.depends_on("jsonschema", "<4.0")
Expand All @@ -81,12 +81,6 @@ def set_properties(project):
project.set_property("distutils_setup_keywords", ["kubernetes", "k8s", "kube", "top", "provisioning",
"kOps", "terraform", "tf", "AWS"])

if False:
project.set_property("vendorize_target_dir", "$dir_source_main_python/kubernator/_vendor")
project.set_property("vendorize_packages", ["kubernetes~=28.0"])
project.set_property("vendorize_cleanup_globs", [])
project.set_property("vendorize_preserve_metadata", [])

project.set_property("distutils_classifiers", [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3.9",
Expand All @@ -112,7 +106,7 @@ def set_properties(project):
# -*- coding: utf-8 -*-
#
# Copyright 2020 Express Systems USA, Inc
# Copyright 2021 Karellen, Inc.
# Copyright 2024 Karellen, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down
23 changes: 23 additions & 0 deletions src/integrationtest/python/issue_48/phase1/.kubernator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# flake8: noqa
import os

ktor.app.register_plugin("minikube", k8s_version=os.environ["K8S_VERSION"],
start_fresh=True, keep_running=True, profile="issue-48")
ktor.app.register_plugin("k8s")

if False:
_old_req = ktor.k8s.client.rest_client.pool_manager.request


def request(method, url, fields=None, headers=None, **urlopen_kw):
resp = _old_req(method, url, fields=fields, headers=headers, **urlopen_kw)
logger.info("Send:\n%s %s\n\n%s\n\n%s",
method, url, "\n".join(map(lambda t: "%s: %s" % (t[0], t[1]), headers.items())),
urlopen_kw.get("body", ""))
logger.info("Recv:\n%s %s\n\n%s\n\n%s",
resp.status, resp.reason, "\n".join(map(lambda t: "%s: %s" % (t[0], t[1]), resp.headers.items())),
resp.data.decode("utf-8"))
return resp


ktor.k8s.client.rest_client.pool_manager.request = request
13 changes: 13 additions & 0 deletions src/integrationtest/python/issue_48/phase1/manifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Namespace
metadata:
name: ns1
annotations:
a: x
---
apiVersion: v1
kind: Namespace
metadata:
name: ns2
annotations:
a: x
23 changes: 23 additions & 0 deletions src/integrationtest/python/issue_48/phase2/.kubernator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# flake8: noqa
import os

ktor.app.register_plugin("minikube", k8s_version=os.environ["K8S_VERSION"],
start_fresh=False, keep_running=False, profile="issue-48")
ktor.app.register_plugin("k8s")

if False:
_old_req = ktor.k8s.client.rest_client.pool_manager.request


def request(method, url, fields=None, headers=None, **urlopen_kw):
resp = _old_req(method, url, fields=fields, headers=headers, **urlopen_kw)
logger.info("Send:\n%s %s\n\n%s\n\n%s",
method, url, "\n".join(map(lambda t: "%s: %s" % (t[0], t[1]), headers.items())),
urlopen_kw.get("body", ""))
logger.info("Recv:\n%s %s\n\n%s\n\n%s",
resp.status, resp.reason, "\n".join(map(lambda t: "%s: %s" % (t[0], t[1]), resp.headers.items())),
resp.data.decode("utf-8"))
return resp


ktor.k8s.client.rest_client.pool_manager.request = request
16 changes: 16 additions & 0 deletions src/integrationtest/python/issue_48/phase2/manifests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Namespace
metadata:
name: ns1
annotations:
$patch: replace
b: y
---
apiVersion: v1
kind: Namespace
metadata:
name: ns2
annotations:
$patch: delete
b: y

51 changes: 51 additions & 0 deletions src/integrationtest/python/issue_48_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
#
# Copyright 2020 Express Systems USA, Inc
# Copyright 2023 Karellen, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from test_support import IntegrationTestSupport, unittest

unittest # noqa
# Above import must be first

from pathlib import Path # noqa: E402
import os # noqa: E402
import tempfile # noqa: E402
import yaml # noqa: E402
from pprint import pprint # noqa: E402


class Issue48Test(IntegrationTestSupport):
def test_issue_48(self):
test_dir = Path(__file__).parent / "issue_48"
test_dir_1 = test_dir / "phase1"
test_dir_2 = test_dir / "phase2"
with tempfile.TemporaryDirectory() as results_dir:
results_file = Path(results_dir) / "results"
for k8s_version in (self.K8S_TEST_VERSIONS[-1],):
with self.subTest(k8s_version=k8s_version):
os.environ["K8S_VERSION"] = k8s_version

self.run_module_test("kubernator", "-p", str(test_dir_1), "-v", "TRACE", "apply", "--yes")
self.run_module_test("kubernator", "-p", str(test_dir_2),
"-v", "TRACE", "-f", str(results_file), "dump")

with open(results_file, "rb") as f:
results = list(yaml.safe_load_all(f))
pprint(results)


if __name__ == "__main__":
unittest.main()
113 changes: 113 additions & 0 deletions src/main/python/kubernator/_json_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
#
# Copyright 2020 Express Systems USA, Inc
# Copyright 2024 Karellen, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import re
from functools import cache

from jsonpath_ng import JSONPath, DatumInContext
from jsonpath_ng.ext import parse as jp_parse, parser
from jsonpath_ng.ext.string import DefintionInvalid

__all__ = ["jp", "JPath"]


class JPath:
def __init__(self, pattern):
self.pattern = jp_parse(pattern)

def find(self, val):
return self.pattern.find(val)

def all(self, val):
return list(map(lambda x: x.value, self.find(val)))

def first(self, val):
"""Returns the first element or None if it doesn't exist"""
try:
return next(map(lambda x: x.value, self.find(val)))
except StopIteration:
return None

def only(self, val):
"""Returns the first and only element.
Raises ValueError if more than one value found
Raises KeyError if no value found
"""
m = map(lambda x: x.value, self.find(val))
try:
v = next(m)
except StopIteration:
raise KeyError("no value found")
try:
next(m)
raise ValueError("more than one value returned")
except StopIteration:
return v


@cache
def jp(pattern) -> JPath:
return JPath(pattern)


MATCH = re.compile(r"match\(/(.*)(?<!\\)/\)")


class Match(JSONPath):
"""Direct node regex matcher
Concrete syntax is '`match(/regex/)`'
"""

def __init__(self, method=None):
m = MATCH.match(method)
if m is None:
raise DefintionInvalid("%s is not valid" % method)
self.expr = m.group(1).strip()
self.regex = re.compile(self.expr)
self.method = method

def find(self, datum):
datum = DatumInContext.wrap(datum)

if hasattr(datum.path, "fields") and self.regex.match(datum.path.fields[0]):
return [datum]
return []

def __eq__(self, other):
return isinstance(other, Match) and self.method == other.method

def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.method)

def __str__(self):
return '`match(/%s/)`' % (self.expr,)


old_p_jsonpath_named_operator = parser.ExtentedJsonPathParser.p_jsonpath_named_operator


def p_jsonpath_named_operator(self, p):
"jsonpath : NAMED_OPERATOR"
if p[1].startswith("match("):
p[0] = Match(p[1])
else:
old_p_jsonpath_named_operator(self, p)


parser.ExtentedJsonPathParser.p_jsonpath_named_operator = p_jsonpath_named_operator
43 changes: 2 additions & 41 deletions src/main/python/kubernator/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from collections.abc import Callable
from collections.abc import Iterable, MutableSet, Reversible
from enum import Enum
from functools import cache
from hashlib import sha256
from io import StringIO as io_StringIO
from pathlib import Path
Expand All @@ -46,8 +45,9 @@
make_logging_undefined,
Template as JinjaTemplate,
pass_context)
from jsonpath_ng.ext import parse as jp_parse
from jsonschema import validators

from kubernator._json_path import jp # noqa: F401
from kubernator._k8s_client_patches import (URLLIB_HEADERS_PATCH,
CUSTOM_OBJECT_PATCH_23,
CUSTOM_OBJECT_PATCH_25)
Expand All @@ -72,45 +72,6 @@ def to_patterns(*patterns):
return [re.compile(fnmatch.translate(p)) for p in patterns]


class JPath:
def __init__(self, pattern):
self.pattern = jp_parse(pattern)

def find(self, val):
return self.pattern.find(val)

def all(self, val):
return list(map(lambda x: x.value, self.find(val)))

def first(self, val):
"""Returns the first element or None if it doesn't exist"""
try:
return next(map(lambda x: x.value, self.find(val)))
except StopIteration:
return None

def only(self, val):
"""Returns the first and only element.
Raises ValueError if more than one value found
Raises KeyError if no value found
"""
m = map(lambda x: x.value, self.find(val))
try:
v = next(m)
except StopIteration:
raise KeyError("no value found")
try:
next(m)
raise ValueError("more than one value returned")
except StopIteration:
return v


@cache
def jp(pattern) -> JPath:
return JPath(pattern)


def scan_dir(logger, path: Path, path_filter: Callable[[os.DirEntry], bool], excludes, includes):
logger.debug("Scanning %s, excluding %s, including %s", path, excludes, includes)
with os.scandir(path) as it: # type: Iterable[os.DirEntry]
Expand Down
Loading

0 comments on commit 81ee43e

Please sign in to comment.