Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/confcom/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Release History
===============

1.4.5
++++++
* Drop the dependency on OPA

1.4.4
++++++
* Improve the package building process
Expand Down
62 changes: 0 additions & 62 deletions src/confcom/azext_confcom/lib/opa.py

This file was deleted.

70 changes: 53 additions & 17 deletions src/confcom/azext_confcom/lib/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from dataclasses import asdict
import json
from pathlib import Path
import re

from dataclasses import asdict
from textwrap import dedent
from typing import Union

from azext_confcom.lib.opa import opa_eval
from azext_confcom.lib.policy import Container, FragmentReference, Fragment, Policy
import re


# This is a single entrypoint for serializing both Policy and Fragment objects
Expand Down Expand Up @@ -80,21 +79,58 @@ def fragment_serialize(fragment: Fragment):
def policy_deserialize(file_path: str):

with open(file_path, 'r') as f:
content = f.read()

package_match = re.search(r'package\s+(\S+)', content)
package_name = package_match.group(1)

PolicyType = Policy if package_name == "policy" else Fragment

raw_json = opa_eval(Path(file_path), f"data.{package_name}")["result"][0]["expressions"][0]["value"]

raw_fragments = raw_json.pop("fragments", [])
raw_containers = raw_json.pop("containers", [])
content = f.readlines()

def _brace_delta(line: str) -> int:
delta = 0
for char in line:
if char in ['{', '[', '(']:
delta += 1
elif char in ['}', ']', ')']:
delta -= 1
Comment on lines +86 to +90
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _brace_delta function counts all braces, brackets, and parentheses in the line, including those inside JSON string literals. This will cause incorrect depth tracking when string values contain these characters.

For example, if a JSON field contains "pattern": "prefix(tail", the opening parenthesis would incorrectly increment the depth counter, causing the parser to continue reading lines even after the JSON structure is complete, potentially including unrelated lines in the expression.

To fix this, track whether you're currently inside a quoted string and skip delimiter counting while inside strings. Consider using a state machine or a more robust JSON-aware parser for multi-line expressions. Example approach:

def _brace_delta(line: str) -> int:
    delta = 0
    in_string = False
    escape_next = False
    for char in line:
        if escape_next:
            escape_next = False
            continue
        if char == '\\':
            escape_next = True
            continue
        if char == '"':
            in_string = not in_string
            continue
        if not in_string:
            if char in ['{', '[', '(']:
                delta += 1
            elif char in ['}', ']', ')']:
                delta -= 1
    return delta
Suggested change
for char in line:
if char in ['{', '[', '(']:
delta += 1
elif char in ['}', ']', ')']:
delta -= 1
in_string = False
escape_next = False
for char in line:
if escape_next:
escape_next = False
continue
if char == '\\':
escape_next = True
continue
if char == '"':
in_string = not in_string
continue
if not in_string:
if char in ['{', '[', '(']:
delta += 1
elif char in ['}', ']', ')']:
delta -= 1

Copilot uses AI. Check for mistakes.
return delta

policy_json = {}
line_idx = 0

while line_idx < len(content):
line = content[line_idx]

packages_search = re.search(r'package\s+(\S+)', line)
if packages_search:
policy_json["package"] = packages_search.group(1)
line_idx += 1
continue

assignment = re.match(r"\s*(?P<name>[A-Za-z0-9_]+)\s*:=\s*(?P<expr>.*)", line)
if assignment:
name = assignment.group('name')
expr = assignment.group('expr').strip()
expr_parts = [expr]
depth = _brace_delta(expr)

while depth > 0 and line_idx + 1 < len(content):
line_idx += 1
continuation = content[line_idx].strip()
expr_parts.append(continuation)
depth += _brace_delta(continuation)

full_expr = "\n".join(expr_parts).strip().rstrip(",")
try:
policy_json[name] = json.loads(full_expr)
except json.JSONDecodeError:
# Skip non-literal expressions (e.g. data.framework bindings)
...

line_idx += 1

PolicyType = Policy if policy_json.get("package") == "policy" else Fragment

raw_fragments = policy_json.pop("fragments", [])
raw_containers = policy_json.pop("containers", [])

return PolicyType(
package=package_name,
**policy_json,
fragments=[FragmentReference(**fragment) for fragment in raw_fragments],
containers=[Container(**container) for container in raw_containers],
**raw_json
)
4 changes: 1 addition & 3 deletions src/confcom/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from azext_confcom.rootfs_proxy import SecurityPolicyProxy
from azext_confcom.kata_proxy import KataPolicyGenProxy
from azext_confcom.cose_proxy import CoseSignToolProxy
from azext_confcom.lib.opa import opa_get

try:
from azure_bdist_wheel import cmdclass
Expand All @@ -20,7 +19,7 @@

logger.warn("Wheel is not available, disabling bdist_wheel hook")

VERSION = "1.4.4"
VERSION = "1.4.5"

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down Expand Up @@ -49,7 +48,6 @@
SecurityPolicyProxy.download_binaries()
KataPolicyGenProxy.download_binaries()
CoseSignToolProxy.download_binaries()
opa_get()

with open("README.md", "r", encoding="utf-8") as f:
README = f.read()
Expand Down
Loading