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

Validate that zowe.config.json file matches schema #192

Merged
merged 37 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
883a0fc
moved the validation logic into ProfileManager
aadityasinha-dotcom Jun 26, 2023
e83fb65
added validation error warning
aadityasinha-dotcom Jun 26, 2023
ffc42e8
added jsonschema exceptions
aadityasinha-dotcom Jun 27, 2023
267f700
fixed indentation
aadityasinha-dotcom Jun 27, 2023
6465b67
raised error
aadityasinha-dotcom Jun 28, 2023
b93d818
fixed test
aadityasinha-dotcom Jun 28, 2023
69d983f
enhanced the validators.py and made some changes to validate schema
aadityasinha-dotcom Jun 29, 2023
f07ebec
fixed tests
aadityasinha-dotcom Jun 29, 2023
8f8d80c
fixed validator if there is not path, fixed raising exceptions for va…
aadityasinha-dotcom Jun 30, 2023
c2ae1ec
moved the validate_schema method to ConfigFile class
aadityasinha-dotcom Jul 6, 2023
6108ebf
changelog
aadityasinha-dotcom Jul 7, 2023
042a4ac
moved the validation logic to init_from_file so the validate_schema m…
aadityasinha-dotcom Jul 9, 2023
708da24
fixed test
aadityasinha-dotcom Jul 10, 2023
19ede33
changes
aadityasinha-dotcom Jul 10, 2023
dd0be07
simplified path_config_json
aadityasinha-dotcom Jul 12, 2023
5902381
added test for valide schema more test will be added soon
aadityasinha-dotcom Jul 17, 2023
8d65e9f
changed the opt_in flag to opt_out
aadityasinha-dotcom Jul 21, 2023
81c4b44
Merge branch 'main' into validate_schema
aadityasinha-dotcom Jul 30, 2023
8a94fdc
fix test
aadityasinha-dotcom Jul 31, 2023
600439a
added test for invalid schema
aadityasinha-dotcom Aug 1, 2023
7b2673b
Merge branch 'main' into validate_schema
aadityasinha-dotcom Aug 5, 2023
e01ffb0
refactored the code
aadityasinha-dotcom Aug 10, 2023
0030a10
added test for invalid schema and updated requirements.txt
aadityasinha-dotcom Aug 11, 2023
8edec11
added test for internet URI
aadityasinha-dotcom Aug 17, 2023
6f34785
did some changes, added unit test for internet URI
aadityasinha-dotcom Aug 21, 2023
cba960c
few changes added file protocol, fixed tests
aadityasinha-dotcom Aug 28, 2023
535ca88
Merge branch 'main' into validate_schema
aadityasinha-dotcom Aug 30, 2023
c0b9d9b
Merge branch 'main' into validate_schema
aadityasinha-dotcom Aug 31, 2023
6917e60
removed verify parameter
aadityasinha-dotcom Sep 4, 2023
fc5aa3a
removed config_type parameter
aadityasinha-dotcom Sep 13, 2023
26e997f
Merge branch 'main' into validate_schema
aadityasinha-dotcom Sep 13, 2023
1ba06a2
Merge branch 'main' into validate_schema
aadityasinha-dotcom Sep 14, 2023
561b370
fix tests
aadityasinha-dotcom Sep 19, 2023
f749d60
attempt to add the entire env/lib dir
zFernand0 Sep 27, 2023
e355a7c
cannot assume that env/lib is the reason why the merging didn't work
zFernand0 Sep 27, 2023
b824ac0
try to load jsonschema
zFernand0 Sep 27, 2023
a7e61a9
Merge branch 'main' into validate_schema
zFernand0 Sep 27, 2023
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil

## Recent Changes

- BugFix: Validation of zowe.config.json file matching the schema [#192](https://github.com/zowe/zowe-client-python-sdk/issues/192)
- Feature: Added method to load profile properties from environment variables
- Feature: Added a CredentialManager class to securely retrieve values from credentials and manage multiple credential entries on Windows [#134](https://github.com/zowe/zowe-client-python-sdk/issues/134)
- Feature: Added method to load profile properties from environment variables
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ coverage==5.4
flake8==3.8.4
idna==2.10
importlib-metadata==3.6.0
jsonschema==4.14.0
jsonschema==4.17.3
keyring
lxml==4.9.3
mccabe==0.6.1
Expand Down
47 changes: 41 additions & 6 deletions src/core/zowe/core_for_zowe_sdk/config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import commentjson

from .constants import constants
from .validators import validate_config_json
from .credential_manager import CredentialManager
from .custom_warnings import (
ProfileNotFoundWarning,
Expand Down Expand Up @@ -72,8 +74,9 @@ class ConfigFile:
_location: Optional[str] = None
profiles: Optional[dict] = None
defaults: Optional[dict] = None
secure_props: Optional[dict] = None
schema_property: Optional[dict] = None
secure_props: Optional[dict] = None
jsonc: Optional[dict] = None
_missing_secure_props: list = field(default_factory=list)

@property
Expand All @@ -96,10 +99,10 @@ def filepath(self) -> Optional[str]:
@property
def location(self) -> Optional[str]:
return self._location

@property
def schema_path(self) -> Optional[str]:
self.schema_property
return self.schema_property

@location.setter
def location(self, dirname: str) -> None:
Expand All @@ -108,7 +111,11 @@ def location(self, dirname: str) -> None:
else:
raise FileNotFoundError(f"given path {dirname} is not valid")

def init_from_file(self) -> None:
def init_from_file(
self,
config_type: str,
aadityasinha-dotcom marked this conversation as resolved.
Show resolved Hide resolved
validate_schema: Optional[bool] = True,
) -> None:
"""
Initializes the class variable after
setting filepath (or if not set, autodiscover the file)
Expand All @@ -120,14 +127,40 @@ def init_from_file(self) -> None:
profile_jsonc = commentjson.load(fileobj)
aadityasinha-dotcom marked this conversation as resolved.
Show resolved Hide resolved

self.profiles = profile_jsonc.get("profiles", {})
self.defaults = profile_jsonc.get("defaults", {})
self.schema_property = profile_jsonc.get("$schema", None)
aadityasinha-dotcom marked this conversation as resolved.
Show resolved Hide resolved
self.defaults = profile_jsonc.get("defaults", {})
self.jsonc = profile_jsonc

if self.schema_property and validate_schema:
self.validate_schema()
# loading secure props is done in load_profile_properties
# since we want to try loading secure properties only when
# we know that the profile has saved properties
# self.load_secure_props()

def validate_schema(
self
) -> None:
"""
Get the $schema_property from the config and load the schema

Returns
-------
file_path to the $schema property
"""

path_schema_json = None

path_schema_json = self.schema_path
if path_schema_json is None: # check if the $schema property is not defined
warnings.warn(
f"$schema property could not found"
)

# validate the $schema property
if path_schema_json:
validate_config_json(self.jsonc, path_schema_json, cwd = self.location)

def schema_list(
self,
) -> list:
Expand Down Expand Up @@ -176,6 +209,8 @@ def get_profile(
self,
profile_name: Optional[str] = None,
profile_type: Optional[str] = None,
config_type: Optional[str] = None,
validate_schema: Optional[bool] = True,
) -> Profile:
"""
Load given profile including secure properties and excluding values from base profile
Expand All @@ -185,7 +220,7 @@ def get_profile(
Returns a namedtuple called Profile
"""
if self.profiles is None:
self.init_from_file()
self.init_from_file(config_type, validate_schema)

if profile_name is None and profile_type is None:
raise ProfileNotFound(
Expand Down
34 changes: 29 additions & 5 deletions src/core/zowe/core_for_zowe_sdk/profile_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os.path
import os
import warnings
import jsonschema
from typing import Optional

from .config_file import ConfigFile, Profile
Expand Down Expand Up @@ -160,6 +161,7 @@ def get_profile(
profile_name: Optional[str],
profile_type: Optional[str],
config_type: str,
validate_schema: Optional[bool] = True,
) -> Profile:
"""
Get just the profile from the config file (overriden with base props in the config file)
Expand All @@ -174,7 +176,27 @@ def get_profile(
cfg_profile = Profile()
try:
cfg_profile = cfg.get_profile(
profile_name=profile_name, profile_type=profile_type
profile_name=profile_name, profile_type=profile_type, config_type=config_type, validate_schema=validate_schema
)
except jsonschema.exceptions.ValidationError as exc:
raise jsonschema.exceptions.ValidationError(
f"Instance was invalid under the provided $schema property, {exc}"
)
except jsonschema.exceptions.SchemaError as exc:
raise jsonschema.exceptions.SchemaError(
f"The provided schema is invalid, {exc}"
)
except jsonschema.exceptions.UndefinedTypeCheck as exc:
raise jsonschema.exceptions.UndefinedTypeCheck(
f"A type checker was asked to check a type it did not have registered, {exc}"
)
except jsonschema.exceptions.UnknownType as exc:
raise jsonschema.exceptions.UnknownType(
f"Unknown type is found in schema_json, exc"
)
except jsonschema.exceptions.FormatError as exc:
raise jsonschema.exceptions.FormatError(
f"Validating a format config_json failed for schema_json, {exc}"
)
t1m0thyj marked this conversation as resolved.
Show resolved Hide resolved
except ProfileNotFound:
if profile_name:
Expand Down Expand Up @@ -212,14 +234,15 @@ def get_profile(
f"because {type(exc).__name__}'{exc}'.",
ConfigNotFoundWarning,
)
finally:
return cfg_profile

return cfg_profile

def load(
self,
profile_name: Optional[str] = None,
profile_type: Optional[str] = None,
check_missing_props: bool = True,
validate_schema: Optional[bool] = True,
aadityasinha-dotcom marked this conversation as resolved.
Show resolved Hide resolved
override_with_env: Optional[bool] = False,
) -> dict:
"""Load connection details from a team config profile.
Expand Down Expand Up @@ -255,13 +278,14 @@ def load(
"Global Config": self.global_config,
}
profile_props: dict = {}
schema_path = None
env_var: dict = {}

missing_secure_props = [] # track which secure props were not loaded

for i, (config_type, cfg) in enumerate(config_layers.items()):
profile_loaded = self.get_profile(
cfg, profile_name, profile_type, config_type
cfg, profile_name, profile_type, config_type, validate_schema
)
# TODO Why don't user and password show up here for Project User Config?
# Probably need to update load_profile_properties method in config_file.py
Expand All @@ -270,7 +294,7 @@ def load(
profile_loaded.name
) # Define profile name that will be merged from other layers
profile_props = {**profile_loaded.data, **profile_props}

missing_secure_props.extend(profile_loaded.missing_secure_props)

if override_with_env:
Expand Down
34 changes: 28 additions & 6 deletions src/core/zowe/core_for_zowe_sdk/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@

import commentjson
from jsonschema import validate
import os
from typing import Union, Optional
import requests


def validate_config_json(path_config_json: str, path_schema_json: str):
def validate_config_json(path_config_json: Union[str, dict], path_schema_json: str, cwd: str):
aadityasinha-dotcom marked this conversation as resolved.
Show resolved Hide resolved
"""
Function validating that zowe.config.json file matches zowe.schema.json.

Expand All @@ -31,10 +34,29 @@ def validate_config_json(path_config_json: str, path_schema_json: str):
Provides details if config.json doesn't match schema.json, otherwise it returns None.
"""

with open(path_config_json) as file:
config_json = commentjson.load(file)

with open(path_schema_json) as file:
schema_json = commentjson.load(file)
# checks if the path_schema_json point to an internet URI and download the schema using the URI
if path_schema_json.startswith("https://") or path_schema_json.startswith("http://"):
schema_json = requests.get(path_schema_json).json()

# checks if the path_schema_json is a file
elif os.path.isfile(path_schema_json) or path_schema_json.startswith("file://"):
with open(path_schema_json.replace("file://", "")) as file:
schema_json = commentjson.load(file)

# checks if the path_schema_json is absolute
elif not os.path.isabs(path_schema_json):
path_schema_json = os.path.join(cwd, path_schema_json)
with open(path_schema_json) as file:
schema_json = commentjson.load(file)

# if there is no path_schema_json it will return None
else:
return None

if isinstance(path_config_json, str):
with open(path_config_json) as file:
config_json = commentjson.load(file)
else:
config_json = path_config_json

return validate(instance=config_json, schema=schema_json)
2 changes: 1 addition & 1 deletion tests/unit/fixtures/invalid.zowe.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "./zowe.schema.json",
"$schema": "./invalid.zowe.schema.json",
"profiles": {
"zosmf": {
"type": "zosmf",
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/fixtures/invalidUri.zowe.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"$schema": "./invalidUri.zowe.schema.json",
"profiles": {
"zosmf": {
"type": "zosmf",
"properties": {
"port": 10443
},
"secure": []
},
"tso": {
"type": "tso",
"properties": {
"account": "",
"codePage": "1047",
"logonProcedure": "IZUFPROC"
},
"secure": []
},
"ssh": {
"type": "ssh",
"properties": {
"port": 22
},
"secure": ["user"]
},
"zftp": {
"type": "zftp",
"properties": {
"port": 21,
"secureFtp": true
},
"secure": []
},
"base": {
"type": "base",
"properties": {
"host": "zowe.test.cloud",
"rejectUnauthorized": false
},
"secure": [
"user",
"password"
]
}
},
"defaults": {
"zosmf": "zosmf",
"tso": "tso",
"ssh": "ssh",
"zftp": "zftp",
"base": "base"
},
"autoStore": true
}
Loading