Skip to content
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

Use $schema keys for menuinst JSON validation #5569

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
85 changes: 53 additions & 32 deletions conda_build/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from fnmatch import filter as fnmatch_filter
from fnmatch import fnmatch
from fnmatch import translate as fnmatch_translate
from functools import partial
from functools import cache, partial
from os.path import (
basename,
dirname,
Expand Down Expand Up @@ -1687,46 +1687,67 @@ def check_menuinst_json(files, prefix) -> None:
return

print("Validating Menu/*.json files")
for json_file in json_files:
_check_one_menuinst_json(join(prefix, json_file))


@cache
def _build_validator(url):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shall we restrict which domains can be provided so they can only come from a series of pre-approved locations? e.g. github.com/conda/menuinst, schemas.conda.org, etc. Otherwise users might be able to simply link the equivalent of "allow-everything" schema and bypass these validations. Not that they result in errors, though.

import jsonschema
import requests

log = utils.get_logger(__name__, dedupe=False)
try:
import jsonschema
from menuinst.utils import data_path
except ImportError as exc:
log.warning(
"Found 'Menu/*.json' files but couldn't validate: %s",
", ".join(json_files),
exc_info=exc,
)
r = requests.get(url)
if not r.ok:
log.error("Could not fetch '%s', status code %s", r.status_code)
return
schema = r.json()

try:
schema_path = data_path("menuinst.schema.json")
with open(schema_path) as f:
schema = json.load(f)
ValidatorClass = jsonschema.validators.validator_for(schema)
validator = ValidatorClass(schema)
return ValidatorClass(schema)
except (jsonschema.SchemaError, json.JSONDecodeError, OSError) as exc:
log.warning("'%s' is not a valid menuinst schema", schema_path, exc_info=exc)
log.error("'%s' is not a valid menuinst schema", url, exc_info=exc)
return

for json_file in json_files:
try:
with open(join(prefix, json_file)) as f:
text = f.read()
if "$schema" not in text:
log.warning(
"menuinst v1 JSON document '%s' won't be validated.", json_file
)
continue
validator.validate(json.loads(text))
except (jsonschema.ValidationError, json.JSONDecodeError, OSError) as exc:
log.warning(
"'%s' is not a valid menuinst JSON document!",
json_file,
exc_info=exc,

def _check_one_menuinst_json(json_file):
import jsonschema

log = utils.get_logger(__name__, dedupe=False)

try:
with open(json_file) as f:
text = f.read()
if "$schema" not in text:
log.warning("menuinst v1 JSON document '%s' won't be validated.", json_file)
return
loaded = json.loads(text)
schema_url = loaded.get("$schema")
if not schema_url:
log.error(
"Invalid empty value for $schema. '%s' won't be validated", json_file
)
else:
log.info("'%s' is a valid menuinst JSON document", json_file)
return
elif schema_url == "https://json-schema.org/draft-07/schema":
fallback_schema = (
"https://github.com/conda/menuinst/raw/refs/tags/2.2.0/"
"menuinst/data/menuinst.schema.json"
)
log.debug(
"Known wrong value for $schema, defaulting to '%s'", fallback_schema
)
schema_url = fallback_schema
validator = _build_validator(schema_url)
validator.validate(loaded)
except (jsonschema.ValidationError, json.JSONDecodeError, OSError) as exc:
log.error(
"'%s' is not a valid menuinst JSON document!",
json_file,
exc_info=exc,
)
else:
log.info("'%s' is a valid menuinst JSON document", json_file)


def post_build(m, files, build_python, host_prefix=None, is_already_linked=False):
Expand Down
19 changes: 19 additions & 0 deletions news/5569-jsonschema-menuinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* <news item>

### Bug fixes

* Validate menuinst JSON documents using the included `$schema` value. `menuinst` is no longer a dependency. (#5569)

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ dependencies = [
"jinja2",
"jsonschema >=4.19",
"libarchive-c",
"menuinst >=2",
"packaging",
"pkginfo",
"psutil",
Expand Down
1 change: 0 additions & 1 deletion recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ requirements:
- jinja2
- jsonschema >=4.19
- m2-patch >=2.6 # [win]
- menuinst >=2
- packaging
- patch >=2.6 # [not win]
- patchelf # [linux]
Expand Down
1 change: 0 additions & 1 deletion tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ filelock
frozendict >=2.4.2
jinja2
jsonschema >=4.19
menuinst >=2
packaging
pkginfo
psutil
Expand Down
Loading