-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[confcom] Add more thorough tests for --with-containers
#9428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
562139c
Define which policy fields care about ordering
DomAyre 850c23c
Prep for PR
DomAyre 205bdc7
Merge branch 'main' into with-containers-tests
DomAyre 719e669
Update version and history
DomAyre 19286d6
Fix style checks
DomAyre 02dc5fe
Attempt to fix style not flagged locally
DomAyre 68ad74e
Add missing opa_get command to setup.py
DomAyre ce747e1
Update expected sha for opa
DomAyre cc13f01
Fix variable name typo
DomAyre a57d75e
Pin OPA version
DomAyre e47304f
Fix formatting
DomAyre 99be5f0
Use SSL verification when getting OPA binary
DomAyre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| import os | ||
|
|
||
|
|
||
| def get_binaries_dir(): | ||
| binaries_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "bin") | ||
| if not os.path.exists(binaries_dir): | ||
| os.makedirs(binaries_dir) | ||
| return binaries_dir |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| import hashlib | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| import platform | ||
| import subprocess | ||
| from typing import Iterable | ||
|
|
||
| import requests | ||
|
|
||
| from azext_confcom.lib.binaries import get_binaries_dir | ||
|
|
||
| _opa_path = os.path.abspath(os.path.join(get_binaries_dir(), "opa")) | ||
| _expected_sha256 = "fe8e191d44fec33db2a3d0ca788b9f83f866d980c5371063620c3c6822792877" | ||
|
|
||
|
|
||
| def opa_get(): | ||
|
|
||
| opa_fetch_resp = requests.get( | ||
| f"https://openpolicyagent.org/downloads/v1.10.1/opa_{platform.system().lower()}_amd64", | ||
| verify=True, | ||
| ) | ||
| opa_fetch_resp.raise_for_status() | ||
|
|
||
| assert hashlib.sha256(opa_fetch_resp.content).hexdigest() == _expected_sha256 | ||
|
|
||
| with open(_opa_path, "wb") as f: | ||
| f.write(opa_fetch_resp.content) | ||
|
|
||
| os.chmod(_opa_path, 0o755) | ||
| return _opa_path | ||
|
|
||
|
|
||
| def opa_run(args: Iterable[str]) -> subprocess.CompletedProcess: | ||
| return subprocess.run( | ||
| [_opa_path, *args], | ||
| check=True, | ||
| stdout=subprocess.PIPE, | ||
| text=True, | ||
| ) | ||
|
|
||
|
|
||
| def opa_eval(data_path: Path, query: str): | ||
| return json.loads(opa_run([ | ||
| "eval", | ||
| "--format", "json", | ||
| "--data", str(data_path), | ||
| query, | ||
| ]).stdout.strip()) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| from typing import Any | ||
| from pydantic.dataclasses import dataclass as _dataclass, Field | ||
| from pydantic import field_serializer | ||
|
|
||
|
|
||
| # The policy model is represented as pydantic dataclasses, this makes | ||
| # serialisation to/from JSON trivial. | ||
|
|
||
| # For some collections in the model, the order has no semantic meaning | ||
| # (e.g. environment rules). We mark such fields using a custom OrderlessField | ||
| # class which is an extension of the pydantic Field class. This custom class | ||
| # just sets a metadata flag we can read later. | ||
|
|
||
| # We then also extend the dataclass decorator to sort these fields with this | ||
| # flag before serialisation and comparison. | ||
|
|
||
|
|
||
| def dataclass(cls=None, **dataclass_kwargs): | ||
| def wrap(inner_cls): | ||
|
|
||
| # This method uses a pydantic field serializer to operate on fields | ||
| # before serialisation. Here we look for "orderless" fields and sort them. | ||
| @field_serializer("*") | ||
| def _sort_orderless(self, value, info): | ||
| field = type(self).__pydantic_fields__[info.field_name] | ||
| if (field.json_schema_extra or {}).get("orderless"): | ||
| return sorted(value, key=repr) | ||
| return value | ||
| setattr(inner_cls, "_sort_orderless", _sort_orderless) | ||
|
|
||
| # This custom equality method sorts "orderless" fields before comparison. | ||
| def __eq__(self, other): | ||
| def compare_field(name, field_info): | ||
| if (field_info.json_schema_extra or {}).get("orderless"): | ||
| return ( | ||
| sorted(getattr(self, name), key=repr) == | ||
| sorted(getattr(other, name), key=repr) | ||
| ) | ||
| return getattr(self, name) == getattr(other, name) | ||
|
|
||
| return ( | ||
| type(self) is type(other) and | ||
| all( | ||
| compare_field(name, field_info) | ||
| for name, field_info in self.__pydantic_fields__.items() | ||
| ) | ||
| ) | ||
| setattr(inner_cls, "__eq__", __eq__) | ||
|
|
||
| return _dataclass(inner_cls, eq=False, **dataclass_kwargs) | ||
|
|
||
| # This adds support for using the decorator with or without parentheses. | ||
| if cls is None: | ||
| return wrap | ||
| return wrap(cls) | ||
|
|
||
|
|
||
| def OrderlessField(**kwargs: Any): | ||
| return Field(json_schema_extra={"orderless": True}, **kwargs) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
|
|
||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # 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 | ||
| 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 | ||
| def policy_serialize(policy: Union[Policy, Fragment]): | ||
|
|
||
| if isinstance(policy, Fragment): | ||
| return fragment_serialize(policy) | ||
|
|
||
| policy_dict = asdict(policy) | ||
| fragments_json = json.dumps(policy_dict.pop("fragments"), indent=2) | ||
| containers_json = json.dumps(policy_dict.pop("containers"), indent=2) | ||
|
|
||
| return dedent(f""" | ||
| package {policy_dict.pop('package')} | ||
|
|
||
| api_version := "{policy_dict.pop('api_version')}" | ||
| framework_version := "{policy_dict.pop('framework_version')}" | ||
|
|
||
| fragments := {fragments_json} | ||
|
|
||
| containers := {containers_json} | ||
|
|
||
| {chr(10).join(f"{key} := {str(value).lower()}" for key, value in policy_dict.items() if key.startswith("allow"))} | ||
|
|
||
| mount_device := data.framework.mount_device | ||
| unmount_device := data.framework.unmount_device | ||
| mount_overlay := data.framework.mount_overlay | ||
| unmount_overlay := data.framework.unmount_overlay | ||
| create_container := data.framework.create_container | ||
| exec_in_container := data.framework.exec_in_container | ||
| exec_external := data.framework.exec_external | ||
| shutdown_container := data.framework.shutdown_container | ||
| signal_container_process := data.framework.signal_container_process | ||
| plan9_mount := data.framework.plan9_mount | ||
| plan9_unmount := data.framework.plan9_unmount | ||
| get_properties := data.framework.get_properties | ||
| dump_stacks := data.framework.dump_stacks | ||
| runtime_logging := data.framework.runtime_logging | ||
| load_fragment := data.framework.load_fragment | ||
| scratch_mount := data.framework.scratch_mount | ||
| scratch_unmount := data.framework.scratch_unmount | ||
|
|
||
| reason := {{"errors": data.framework.errors}} | ||
| """) | ||
|
|
||
|
|
||
| def fragment_serialize(fragment: Fragment): | ||
|
|
||
| fragment_dict = asdict(fragment) | ||
| fragments_json = json.dumps(fragment_dict.pop("fragments"), indent=2) | ||
| containers_json = json.dumps(fragment_dict.pop("containers"), indent=2) | ||
|
|
||
| return dedent(f""" | ||
| package {fragment_dict.pop('package')} | ||
|
|
||
| svn := "{fragment_dict.pop('svn')}" | ||
| framework_version := "{fragment_dict.pop('framework_version')}" | ||
|
|
||
| fragments := {fragments_json} | ||
|
|
||
| containers := {containers_json} | ||
| """) | ||
|
|
||
|
|
||
| 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", []) | ||
|
|
||
| return PolicyType( | ||
| package=package_name, | ||
| fragments=[FragmentReference(**fragment) for fragment in raw_fragments], | ||
| containers=[Container(**container) for container in raw_containers], | ||
| **raw_json | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.