Skip to content

Commit

Permalink
Dataclasses refactor and add new to_dict function
Browse files Browse the repository at this point in the history
  • Loading branch information
azgabur committed Jul 25, 2023
1 parent 53332f4 commit 235667d
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 34 deletions.
77 changes: 57 additions & 20 deletions testsuite/objects/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
"""Module containing base classes for common objects"""
import abc
from dataclasses import dataclass
from dataclasses import dataclass, is_dataclass, fields
from copy import deepcopy
from functools import cached_property
from typing import Literal, List
from typing import Literal, List, Any

from testsuite.objects.sections import Metadata, Identities, Authorizations, Responses


def to_dict(obj):
"""
This function converts dataclass object to dictionary.
While it works similar to `dataclasses.asdict` a notable change is usage of
overriding `to_dict()` function if dataclass contains it.
This function works recursively in lists, tuples and dicts. All other values are passed to copy.deepcopy function.
"""
if hasattr(obj, "to_dict"):
return obj.to_dict()

result = {}
if is_dataclass(obj):
for field in fields(obj):
value = getattr(obj, field.name)
if value is None:
continue # do not include None values

if is_dataclass(value):
result[field.name] = to_dict(value)
elif isinstance(value, (list, tuple)):
result[field.name] = type(value)(to_dict(i) for i in value)
elif isinstance(value, dict):
result[field.name] = type(value)((to_dict(k), to_dict(v)) for k, v in value.items())
else:
result[field.name] = to_dict(value)
else:
result = deepcopy(obj)

return result


@dataclass
class MatchExpression:
"""
Expand Down Expand Up @@ -35,40 +67,45 @@ class Rule:
value: str


class Value:
"""Dataclass for specifying a Value in Authorization, can be either constant or value from AuthJson (jsonPath)"""
@dataclass
class ValueBase:
"""
Abstract Dataclass for specifying a Value in Authorization,
can be either static or reference to value in AuthJson.
"""

# pylint: disable=invalid-name
def __init__(self, value=None, jsonPath=None) -> None:
super().__init__()
if not (value is None) ^ (jsonPath is None):
raise AttributeError("Exactly one of the `value` and `jsonPath` argument must be specified")
self.value = value
self.jsonPath = jsonPath

@dataclass
class ValueStatic(ValueBase):
"""Dataclass for static Value. Can be anything, eg. string, integer, list, dict..."""

value: Any


@dataclass
class ValueDynamic(ValueBase):
"""Dataclass for dynamic Value. It contains path to existing value in AuthJson."""

valueFrom: str # pylint: disable=invalid-name

def to_dict(self):
"""Returns dict representation of itself (shallow copy only)"""
return {"value": self.value} if self.value else {"valueFrom": {"authJson": self.jsonPath}}
"""Override `to_dict`"""
return {"valueFrom": {"authJSON": self.valueFrom}}


@dataclass
class Cache:
"""Dataclass for specifying Cache in Authorization"""

ttl: int
key: Value

def to_dict(self):
"""Returns dict representation of itself (shallow copy only)"""
return {"ttl": self.ttl, "key": self.key.to_dict()}
key: ValueBase


@dataclass
class PatternRef:
"""Dataclass for specifying Pattern reference in Authorization"""

# pylint: disable=invalid-name
patternRef: str
patternRef: str # pylint: disable=invalid-name


class LifecycleObject(abc.ABC):
Expand Down
4 changes: 2 additions & 2 deletions testsuite/objects/sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from testsuite.objects import Rule, Value
from testsuite.objects import Rule, ValueBase


class Authorizations(abc.ABC):
Expand All @@ -27,7 +27,7 @@ def auth_rule(self, name: str, rule: "Rule", **common_features):
"""Adds JSON pattern-matching authorization rule (authorization.json)"""

@abc.abstractmethod
def kubernetes(self, name: str, user: "Value", kube_attrs: dict, **common_features):
def kubernetes(self, name: str, user: "ValueBase", kube_attrs: dict, **common_features):
"""Adds kubernetes authorization rule."""


Expand Down
25 changes: 17 additions & 8 deletions testsuite/openshift/objects/auth_config/sections.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
"""AuthConfig CR object"""
from dataclasses import asdict
from typing import Dict, Literal, Iterable, TYPE_CHECKING

from testsuite.objects import Identities, Metadata, Responses, MatchExpression, Authorizations, Rule, Cache, Value
from testsuite.objects import (
to_dict,
Identities,
Metadata,
Responses,
MatchExpression,
Authorizations,
Rule,
Cache,
ValueBase,
)
from testsuite.openshift.objects import modify

if TYPE_CHECKING:
Expand Down Expand Up @@ -41,11 +50,11 @@ def add_item(
"""Adds item to the section"""
item = {"name": name, **value}
if when:
item["when"] = [asdict(x) for x in when]
item["when"] = [to_dict(x) for x in when]
if metrics:
item["metrics"] = metrics
if cache:
item["cache"] = cache.to_dict()
item["cache"] = to_dict(cache)
if priority:
item["priority"] = priority
self.section.append(item)
Expand Down Expand Up @@ -111,7 +120,7 @@ def api_key(
matcher.update({"matchLabels": {"group": match_label}})

if match_expression:
matcher.update({"matchExpressions": [asdict(match_expression)]})
matcher.update({"matchExpressions": [to_dict(match_expression)]})

self.add_item(
name,
Expand Down Expand Up @@ -185,7 +194,7 @@ class AuthorizationsSection(Section, Authorizations):
@modify
def auth_rule(self, name, rule: Rule, **common_features):
"""Adds JSON pattern-matching authorization rule (authorization.json)"""
section = {"json": {"rules": [asdict(rule)]}}
section = {"json": {"rules": [to_dict(rule)]}}
self.add_item(name, section, **common_features)

def role_rule(self, name: str, role: str, path: str, **common_features):
Expand Down Expand Up @@ -215,7 +224,7 @@ def external_opa_policy(self, name, endpoint, ttl=0, **common_features):
self.add_item(name, {"opa": {"externalRegistry": {"endpoint": endpoint, "ttl": ttl}}}, **common_features)

@modify
def kubernetes(self, name: str, user: Value, kube_attrs: dict, **common_features):
def kubernetes(self, name: str, user: ValueBase, kube_attrs: dict, **common_features):
"""Adds Kubernetes authorization
:param name: name of kubernetes authorization
Expand All @@ -226,7 +235,7 @@ def kubernetes(self, name: str, user: Value, kube_attrs: dict, **common_features
self.add_item(
name,
{
"kubernetes": {"user": user.to_dict(), "resourceAttributes": kube_attrs},
"kubernetes": {"user": to_dict(user), "resourceAttributes": kube_attrs},
},
**common_features
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from testsuite.objects import Cache, Value
from testsuite.objects import Cache, ValueDynamic
from testsuite.utils import extract_response


Expand All @@ -16,7 +16,7 @@ def cache_ttl():
@pytest.fixture(scope="module")
def authorization(authorization, module_label, expectation_path, cache_ttl):
"""Adds Cached Metadata to the AuthConfig"""
meta_cache = Cache(cache_ttl, Value(jsonPath="context.request.http.path"))
meta_cache = Cache(cache_ttl, ValueDynamic(valueFrom="context.request.http.path"))
authorization.metadata.http_metadata(module_label, expectation_path, "GET", cache=meta_cache)
return authorization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import openshift as oc
from openshift import OpenShiftPythonException

from testsuite.objects import Authorization, Rule, Value
from testsuite.objects import Authorization, Rule, ValueDynamic
from testsuite.certificates import CertInfo
from testsuite.utils import cert_builder
from testsuite.openshift.objects.ingress import Ingress
Expand Down Expand Up @@ -78,7 +78,7 @@ def authorization(authorization, openshift, module_label, authorino_domain) -> A

# add OPA policy to process admission webhook request
authorization.authorization.opa_policy("features", OPA_POLICY)
user_value = Value(jsonPath="auth.identity.username")
user_value = ValueDynamic(valueFrom="auth.identity.username")

when = [
Rule("auth.authorization.features.allow", "eq", "true"),
Expand Down

0 comments on commit 235667d

Please sign in to comment.